Exosphere is a set of small, modular, self-hostable community tools built on the AT Protocol. app.exosphere.site
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: initial commit

Hugo 21bd2209

+13529
+6
.dockerignore
··· 1 + node_modules 2 + .git 3 + .env 4 + *.sqlite* 5 + dist 6 + .claude
+14
.env.example
··· 1 + # Public URL where the app is served (no trailing slash) 2 + # Use 127.0.0.1 (not localhost) so the browser treats the PDS OAuth redirect 3 + # as cross-site — the PDS rejects same-site sec-fetch-site headers. 4 + PUBLIC_URL=http://127.0.0.1:5173 5 + 6 + # Local PDS URL for development (optional) 7 + # When set, enables local dev mode: loopback OAuth, handle resolution via PDS, 8 + # and URL rewriting from the PDS hostname to localhost. 9 + PDS_URL=http://localhost:3000 10 + 11 + # ES256 private key in JWK format for OAuth client authentication 12 + # Only needed in production (local dev uses loopback auth with no key). 13 + # Generate one with: bun run packages/core/scripts/generate-key.ts 14 + # OAUTH_PRIVATE_KEY='{"kty":"EC","use":"sig","crv":"P-256","x":"...","y":"...","d":"..."}'
+18
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + .env 4 + !.env.example 5 + 6 + # Local PDS 7 + pds/pds.env 8 + 9 + # SQLite 10 + *.sqlite 11 + *.sqlite-shm 12 + *.sqlite-wal 13 + 14 + # Claude Code 15 + .claude/ 16 + 17 + # OSes 18 + .DS_Store
+321
ARCHITECTURE.md
··· 1 + # Architecture 2 + 3 + ## Project Structure 4 + 5 + Monorepo using Bun workspaces. Each module (feeds, polls, forms, etc.) is an independent package with its own backend routes and frontend components. Modules share common infrastructure but can be developed and deployed independently. 6 + 7 + ``` 8 + exosphere/ 9 + ├── packages/ 10 + │ ├── core/ # Shared server-side infrastructure 11 + │ │ ├── auth/ # AT Protocol OAuth, session management 12 + │ │ ├── db/ # SQLite setup, base migrations 13 + │ │ └── types/ # Common types and Zod schemas 14 + │ ├── client/ # Shared client-side utilities 15 + │ │ └── src/ # API helpers, auth, router, hooks, theme, styles, types 16 + │ ├── indexer/ # Jetstream consumer & shared module registry 17 + │ │ ├── src/ 18 + │ │ │ ├── modules.ts # Shared module list (used by app + standalone) 19 + │ │ │ ├── start.ts # startJetstream() — WebSocket consumer 20 + │ │ │ ├── cursor.ts # Cursor persistence for resume-after-crash 21 + │ │ │ └── main.ts # Standalone entry point 22 + │ ├── <module>/ # Feed & discussions module 23 + │ │ ├── api/ # Hono routes 24 + │ │ ├── ui/ # Preact page components 25 + │ │ └── schemas/ # Module-specific types and schemas 26 + │ └── app/ # Host app — assembles selected modules 27 + ├── bunfig.toml 28 + └── package.json 29 + ``` 30 + 31 + The `app` package is the host: it imports the desired modules, mounts their routes and UI, and produces the final deployable artifact. A Sphere operator picks which modules to enable. 32 + 33 + ## Stack 34 + 35 + ### Backend 36 + 37 + - **Runtime**: Bun 38 + - **Framework**: Hono.js 39 + - **Database**: SQLite (via `bun:sqlite`) 40 + - **Auth**: AT Protocol OAuth via `@atproto/*` packages 41 + 42 + ### Frontend 43 + 44 + - **Framework**: Preact 45 + - **State management**: Preact Signals 46 + - **Styling**: vanilla-extract 47 + 48 + ### Shared (core) 49 + 50 + - **Language**: TypeScript with strict types 51 + - **Validation**: Zod schemas at system boundaries 52 + 53 + ## Module Design 54 + 55 + Each module: 56 + 57 + - Exposes a Hono sub-app for its API routes 58 + - Exposes Preact components for its UI 59 + - Manages its own SQLite tables (migrations scoped per module) 60 + - Can depend on `core` and `client` but not on other modules 61 + 62 + ### Server / Client Separation 63 + 64 + Backend and frontend code must stay in separate entry points to avoid bundling server-only dependencies (e.g. `bun:sqlite`) into the browser bundle. 65 + 66 + Each module exposes two entry points in its `package.json` exports: 67 + 68 + | Export | File | Purpose | 69 + | ------------ | --------------- | ---------------------------------------------------------------------------------- | 70 + | `"."` | `src/index.ts` | Backend — `ExosphereModule` with Hono API routes. Imported by `app/src/server.ts`. | 71 + | `"./client"` | `src/client.ts` | Frontend — `ClientModule` with page routes. Imported by `app/src/app.tsx`. | 72 + 73 + `ExosphereModule` (backend) and `ClientModule` (frontend) are independent types — `ClientModule` does not extend `ExosphereModule` to prevent any transitive import of server code. 74 + 75 + ### Client Module and Route Registration 76 + 77 + The `@exosphere/client` package (`packages/client`) provides shared client-side utilities (API helpers, auth state, router, hooks, theme, styles) and the `ClientModule` type: 78 + 79 + ```typescript 80 + interface ModuleRoute { 81 + path: string; // e.g. "/feeds" or "/feature-requests/:number" 82 + component: ComponentType; 83 + } 84 + 85 + interface ClientModule { 86 + name: string; 87 + routes?: ModuleRoute[]; 88 + } 89 + ``` 90 + 91 + Each module's `src/client.ts` exports a `ClientModule` that declares its routes and page components. The host app collects all client modules into an array and maps them to `<Route>` elements inside a preact-iso `<Router>` — adding a new module's pages requires only importing its `ClientModule` and appending it to the array. 92 + 93 + ### Server-Side Rendering (SSR) 94 + 95 + The app server-renders pages so the browser receives ready-to-display HTML. SSR runs in both dev (via a Vite plugin) and production (Bun serves pre-built assets). 96 + 97 + #### How it works 98 + 99 + 1. **Template** — the built `index.html` is loaded once at startup. In production, all CSS from the Vite manifest is injected as render-blocking `<link>` tags to prevent FOUC. 100 + 2. **Data prefetch** — for each request, the server resolves auth (from the `sid` cookie), loads the current Sphere, and calls its own API routes internally (`app.request()`) to prefetch page data. Dependent prefetches (e.g. comments after a feature request) run sequentially. 101 + 3. **Render** — `entry-server.tsx` sets Preact signals (`auth`, `sphereState`, `ssrPageData`) from the prefetched data, then calls `preact-iso/prerender` to produce HTML. 102 + 4. **Hydration** — the HTML and serialized `__SSR_DATA__` are injected into the template. The client reads `__SSR_DATA__` to populate signals before hydration, so components skip their initial fetch via `useQuery`'s `initialData` option. 103 + 104 + #### Eager vs lazy imports 105 + 106 + Module routes use `lazy()` (from `preact-iso`) on the client for code splitting. During SSR, lazy components throw promises that the router can't resolve. Each module therefore exposes a `./client-ssr` entry with direct (eager) imports of the same page components. `entry-server.tsx` imports these eager modules; the client entry uses the default lazy ones. 107 + 108 + | Entry point | Module imports | Code splitting | 109 + | ---------------------- | --------------- | -------------- | 110 + | `src/client.tsx` | Lazy (`lazy()`) | Yes | 111 + | `src/entry-server.tsx` | Eager (direct) | N/A | 112 + 113 + #### Dev vs production 114 + 115 + - **Dev** — a Vite plugin (`vite-ssr-plugin.ts`) intercepts page requests before the SPA fallback. It fetches auth/sphere/page data from the running API server (`localhost:3001`) via HTTP, SSR-renders via `server.ssrLoadModule`, and inlines CSS collected from the module graph. 116 + - **Production** — the Hono catch-all route in `server.ts` handles SSR. The template and CSS links are prepared once at startup. Data prefetch uses `app.request()` to call API routes in-process (no HTTP round-trip). 117 + - Both environments share the same prefetch logic (`ssr-prefetch.ts`) — only the transport differs (HTTP in dev, in-process in prod). 118 + 119 + ## AT Protocol Integration 120 + 121 + - Authentication via AT Protocol OAuth (handled in `core/auth`) 122 + - User data (posts, votes, etc.) is written to each user's PDS 123 + - The backend indexes relevant data from PDS for fast querying 124 + - Sphere configuration and membership are published on PDS for interoperability (see below) 125 + - The local SQLite database caches/indexes all PDS data for fast querying 126 + - Every record type written to a user's PDS has a formal Lexicon schema definition in `packages/core/src/lexicons/` 127 + 128 + ### Lexicon schemas 129 + 130 + All AT Protocol record types are defined as Lexicon JSON files: 131 + 132 + | Lexicon ID | Published by | Purpose | 133 + | ------------------------------------------ | ------------ | ------------------------------------------------------------------------ | 134 + | `site.exosphere.sphere` | Sphere owner | Sphere declaration (name, slug, visibility, modules) — enables discovery | 135 + | `site.exosphere.sphereMember` | Member | "I am a member of this Sphere" — member-side of bilateral membership | 136 + | `site.exosphere.sphereMemberApproval` | Owner/admin | "This user is an approved member" — admin-side of bilateral membership | 137 + | `site.exosphere.featureRequest` | Author | Feature request content | 138 + | `site.exosphere.featureRequestVote` | Voter | Upvote on a feature request | 139 + | `site.exosphere.featureRequestComment` | Commenter | Comment on a feature request | 140 + | `site.exosphere.featureRequestCommentVote` | Voter | Upvote on a comment | 141 + | `site.exosphere.featureRequestStatus` | Admin/owner | Status change on a feature request | 142 + | `site.exosphere.moderation` | Admin/owner | Moderation action on any content (e.g. comment removal) | 143 + 144 + ## Data Ownership 145 + 146 + - Logged-in users own their content via their PDS 147 + - The local SQLite database acts as a cache/index, not the source of truth for user content 148 + - Sphere configuration lives on the owner's PDS, membership is bilateral (both parties publish records) 149 + - A third party can reconstruct any public Sphere entirely from PDS data — no dependency on a specific Exosphere instance 150 + 151 + ### PDS as source of truth 152 + 153 + For public Spheres, **no meaningful action should exist only in the local database**. Every user action (creating content, voting, commenting) and every admin action (status changes, moderation, role updates) must be represented as an AT Protocol record on someone's PDS. The local SQLite database indexes these records for fast querying, but it is always rebuildable from PDS data. 154 + 155 + This means: 156 + 157 + - **User actions** are published on the **user's PDS** (the user owns their data). 158 + - **Admin actions** that affect other users' content (e.g. removing a comment, changing a feature request's status) are published on the **admin's PDS** — not by modifying or deleting the original author's record. The admin has no authority over another user's PDS repo. 159 + - **Moderation** follows this pattern: when an admin removes content, they publish a `site.exosphere.moderation` record on their own PDS referencing the content's AT URI. The original content stays on the author's PDS. The local DB marks the content as hidden for fast filtering, but the moderation decision is reconstructable from protocol data. 160 + 161 + If a piece of state exists only in SQLite with no corresponding PDS record, it cannot survive a database rebuild and is invisible to third-party indexers. The exception is **private Spheres**, where content intentionally stays off-protocol (AT Protocol repos are public by design). 162 + 163 + ## Deployment 164 + 165 + - Docker container for self-hosting 166 + - Single image containing both backend and frontend (backend serves static frontend assets) 167 + 168 + ## Sphere Access Models 169 + 170 + A Sphere can be configured as private or public, each with different data storage and access patterns. 171 + 172 + ### Sphere declaration on PDS 173 + 174 + When a Sphere is created, the owner publishes a `site.exosphere.sphere` record on their PDS. This makes the Sphere discoverable by anyone crawling the AT Protocol network and serves as the canonical reference that other records (membership, content) point to via AT URI. 175 + 176 + ### Private Spheres 177 + 178 + Private Spheres are invitation-only. All content is hidden from the public. 179 + 180 + - **Authentication**: Required. Users must authenticate with their AT Protocol DID. 181 + - **Data storage**: All content (posts, reactions, comments, polls, etc.) is stored in the Sphere's local SQLite database — not on users' PDS. AT Protocol repos are public by design, so private content must stay off-protocol. 182 + - **Sphere record**: The Sphere declaration is still published on the owner's PDS (with `visibility: "private"`), making it discoverable but not its content. 183 + - **Identity**: Users are still identified by their AT Protocol DID, which provides portable identity and verification without exposing content to the public network. 184 + 185 + ``` 186 + User (authenticated via AT Proto OAuth) 187 + → Sphere API (checks membership) 188 + → SQLite (read/write private data) 189 + ``` 190 + 191 + ### Public Spheres 192 + 193 + Public Spheres content is readable by anyone. Write access depends on the Sphere's configuration. 194 + 195 + #### Authentication 196 + 197 + All interactions (posts, reactions, comments) require AT Protocol authentication. Users log in via AT Protocol OAuth and their content is written to their PDS as records using Exosphere's Lexicon schemas. This makes user data portable and user-owned. 198 + 199 + Public Spheres are publicly **readable** without authentication — anyone with the URL can view the content. But all **write** actions require a DID. 200 + 201 + #### Future: unauthenticated write access 202 + 203 + Unauthenticated write access (e.g. anonymous poll votes, guest comments) is deferred to a later phase. It would allow users without an AT Protocol account to interact with specific modules, potentially helping adoption by lowering the entry barrier. 204 + 205 + However, it introduces a hybrid data architecture: the AppView would need to merge data from two sources (user PDS records and local SQLite records) into a single unified view. This adds complexity to every query path and leaks into every module's implementation. 206 + 207 + When revisited, unauthenticated write should be: 208 + 209 + - Opt-in per module (not a core concern) 210 + - Limited to modules where it clearly adds value (polls, forms) rather than applied broadly 211 + - Designed with account linking in mind (what happens when an anonymous user later signs up) 212 + 213 + #### Write access modes 214 + 215 + - **Open**: Any authenticated user can participate (react, comment, post). 216 + - **Members only**: Public read, permissioned write. Anyone can view the content, but only invited members can interact (react, comment, post). 217 + 218 + Important: AT Protocol cannot prevent a user from writing records to their own PDS that reference a Sphere's content. Write restrictions are enforced at the AppView level — only interactions from approved members are indexed and displayed. Non-member interactions are silently ignored. 219 + 220 + ## AppView and Data Consumption 221 + 222 + The AppView is the service that consumes, indexes, and serves AT Protocol data for a Sphere. It is the layer where access control and data aggregation happen. 223 + 224 + ### Jetstream 225 + 226 + Jetstream is a lightweight alternative to the full AT Protocol firehose. Instead of decoding binary CBOR/CAR data, it provides a WebSocket stream of JSON events. 227 + 228 + - The AppView connects to a Jetstream instance and subscribes to events matching Exosphere's Lexicon record types. 229 + - Jetstream can be consumed from Bluesky's public instances, or self-hosted for full independence. 230 + - Self-hosting Jetstream also requires running a relay. For most deployments, using a public Jetstream instance is sufficient — the Sphere still self-hosts all indexing, storage, and access control. 231 + 232 + ### Indexing pipeline 233 + 234 + The indexer lives in its own package (`packages/indexer`) and can run either **in-process** with the API server or as a **standalone service**. Both modes use the same `startJetstream()` function and share the same module registry (`packages/indexer/src/modules.ts`). 235 + 236 + #### Deployment modes 237 + 238 + | Mode | Command | Description | 239 + | ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- | 240 + | **Combined** (default) | `bun run start` | API server starts the indexer in-process. Simplest deployment — one process, one start command. | 241 + | **API only** | `DISABLE_INDEXER=1 bun run start` | API server without the Jetstream consumer. Use when the indexer runs separately. | 242 + | **Indexer only** | `bun run start:indexer` | Standalone Jetstream consumer. Useful for scaling or isolating the indexer from the API. | 243 + 244 + The shared module registry (`@exosphere/indexer/modules`) is the single source of truth for which modules are loaded. Both the API server and the standalone indexer import from it, so adding a new module requires updating only one file. 245 + 246 + #### Event flow 247 + 248 + For Public Spheres with authenticated users, the AppView indexes data from the AT Protocol network: 249 + 250 + ``` 251 + Jetstream (WebSocket, filtered by Exosphere Lexicons) 252 + → Event received (e.g. a reaction record created on a user's PDS) 253 + → Is this record referencing a known Sphere? → No → discard 254 + → Is this Sphere open or is this user a member? → No → discard 255 + → Index the record into SQLite 256 + → Available via Sphere API 257 + ``` 258 + 259 + For Private Spheres and unauthenticated data, there is no Jetstream consumption. Data is written directly to the Sphere database through the API: 260 + 261 + ``` 262 + User → Sphere API (auth + membership check) → SQLite 263 + ``` 264 + 265 + ### Data flow summary 266 + 267 + | Sphere type | Data written to | Data read from | Jetstream needed | 268 + | ----------- | --------------- | ---------------------- | ---------------- | 269 + | Private | Sphere SQLite | Sphere SQLite | No | 270 + | Public | User's PDS | AppView index (SQLite) | Yes | 271 + 272 + ## Membership Model 273 + 274 + Membership uses a **bilateral model** inspired by AT Protocol follows. Both the Sphere admin and the member publish records on their respective PDS, creating a verifiable two-sided proof of membership. The local SQLite database indexes these records for fast access control checks. 275 + 276 + ### Bilateral membership records 277 + 278 + Membership is represented by two complementary AT Protocol records: 279 + 280 + 1. **`site.exosphere.sphereMemberApproval`** — published on the **owner/admin's PDS** when they invite or approve a member. Contains the Sphere AT URI, the member's DID, and their role. 281 + 2. **`site.exosphere.sphereMember`** — published on the **member's PDS** when they accept the invitation. Contains the Sphere AT URI. 282 + 283 + Both records must exist for a membership to be considered fully established. This mirrors how AT Protocol handles social relationships (e.g. follows) and enables third-party indexers to reconstruct the full membership graph from PDS data alone. 284 + 285 + ### Local index (SQLite) 286 + 287 + The `sphere_members` table caches membership state for fast access control: 288 + 289 + - **DID**: The user's AT Protocol decentralized identifier 290 + - **Role**: Owner, admin, member (extensible per Sphere needs) 291 + - **Status**: Active, invited, revoked 292 + - **Invited by**: DID of the user who sent the invitation 293 + - **Joined at**: Timestamp 294 + 295 + This table is the authoritative source for real-time access control decisions (API checks, indexer filtering). It is kept in sync with PDS records but allows the server to make fast membership lookups without querying the network. 296 + 297 + ### Invitation flow 298 + 299 + 1. A Sphere admin invites a user by their AT Protocol handle or DID. 300 + 2. The handle is resolved to a DID (if needed). 301 + 3. The admin publishes a `sphereMemberApproval` record on their PDS. 302 + 4. An invitation record is created in the local membership table (status: invited). 303 + 5. The invited user accepts via the Sphere UI. 304 + 6. On acceptance, the user publishes a `sphereMember` record on their PDS. 305 + 7. Local status is set to active and the user can participate according to the Sphere's write access mode. 306 + 307 + ### Private Spheres and membership privacy 308 + 309 + For **private Spheres**, the Sphere record itself (`site.exosphere.sphere`) has `visibility: "private"`. The bilateral membership records are still published on PDS (since AT Protocol repos are public), but the Sphere's content remains off-protocol. A third party could see that a user is a member of a private Sphere, but cannot access the Sphere's content. This is an acceptable trade-off — membership is public, content is private — similar to how a private GitHub repository's collaborator list can be partially visible. 310 + 311 + If full membership privacy is required, operators can skip PDS writes for membership and rely solely on the local SQLite table. This sacrifices interoperability for privacy. 312 + 313 + ### Enforcement points 314 + 315 + | Layer | Role | 316 + | --------------------- | -------------------------------------------------------------------------------------------------------------- | 317 + | **AppView / Indexer** | Filters incoming Jetstream events. Only indexes interactions from active members (for members-only Spheres). | 318 + | **Sphere API** | Checks membership on write requests. Rejects unauthorized actions before they reach the database. | 319 + | **Client UI** | Hides write controls (comment box, reaction buttons) for non-members. UX convenience, not a security boundary. | 320 + 321 + Membership checks happen at both the API level (for direct writes) and the indexer level (for PDS records arriving via Jetstream). Both paths must enforce the same rules.
+49
CLAUDE.md
··· 1 + # Exosphere 2 + 3 + Modular, self-hostable community platform built on the AT Protocol. 4 + 5 + ## Tools 6 + 7 + - **Runtime & package manager**: Bun (workspaces monorepo) 8 + - **Backend**: Hono.js on Bun, SQLite via `bun:sqlite` 9 + - **Frontend**: Preact + Preact Signals, vanilla-extract for styling, Vite for dev/build 10 + - **Auth**: AT Protocol OAuth (`@atproto/*` packages) 11 + - **Validation**: Zod at system boundaries 12 + - **Language**: TypeScript (strict mode) 13 + - **Dev PDS**: Docker Compose (`docker-compose.dev.yml`) runs a local Bluesky PDS 14 + 15 + ### Commands 16 + 17 + Check project TypeScript types with `bunx tsc --emit` or rely on the LSP plugin. 18 + 19 + ## Architecture 20 + 21 + Monorepo with Bun workspaces under `packages/`: 22 + 23 + | Package | Role | 24 + | ------------------ | --------------------------------------------------------------------------------------------------------- | 25 + | `core` | Shared infra: auth, db, sphere management, types | 26 + | `client` | Shared client-side utilities: API helpers, auth, router, theme, UI styles, reusable components | 27 + | `app` | Host app — assembles modules, Hono server entry (`src/server.ts`), Preact client entry (`src/client.tsx`) | 28 + | `feeds` | Feed & discussions module | 29 + | `feature-requests` | Feature requests module | 30 + 31 + Each module exposes: a Hono sub-app (API routes), Preact components (UI), its own SQLite migrations, and a `ExosphereModule` interface. Modules depend on `core` but not on each other. 32 + 33 + The `app` package registers modules in `src/server.ts` by mounting their API routes under `/api/<module-name>`. 34 + 35 + See `ARCHITECTURE.md` for full details on data flow, AT Protocol integration, membership model, and access modes. 36 + 37 + ## TS/TSX coding style 38 + 39 + - Strong typing and type safety 40 + - Avoid barrel files when possible 41 + - We are using Preact, in JSX, prefer `class` over `className` 42 + - Make optimistic updates when possible 43 + 44 + ## Styling 45 + 46 + - **No universal `box-sizing: border-box`** — let elements use the default `content-box`. Only apply `border-box` on specific elements that need it (e.g. form controls with `width: 100%`). 47 + - **Prefer CSS logical properties** for margins, paddings, and borders (`margin-inline`, `padding-block`, `border-inline-start`, etc.) over physical properties (`margin-left`, `padding-top`, etc.). 48 + - **Prefer modern CSS** — use contemporary features and patterns over legacy approaches. 49 + - Avoid inline styling, prefer re-usable components from ./packages/client
+29
Dockerfile
··· 1 + FROM oven/bun:1 AS base 2 + 3 + WORKDIR /app 4 + 5 + # Install dependencies 6 + FROM base AS deps 7 + COPY package.json bun.lock ./ 8 + COPY packages/core/package.json packages/core/package.json 9 + COPY packages/client/package.json packages/client/package.json 10 + COPY packages/indexer/package.json packages/indexer/package.json 11 + COPY packages/feeds/package.json packages/feeds/package.json 12 + COPY packages/feature-requests/package.json packages/feature-requests/package.json 13 + COPY packages/mcp/package.json packages/mcp/package.json 14 + COPY packages/app/package.json packages/app/package.json 15 + RUN bun install --frozen-lockfile --ignore-scripts 16 + 17 + FROM deps AS build 18 + COPY . . 19 + RUN bun run db:generate && bun run build 20 + 21 + FROM base AS production 22 + WORKDIR /app 23 + COPY --from=build /app/packages ./packages 24 + COPY --from=build /app/drizzle ./drizzle 25 + COPY package.json bun.lock ./ 26 + RUN bun install --frozen-lockfile --production 27 + ENV NODE_ENV=production 28 + EXPOSE 3001 29 + CMD ["sh", "-c", "chown -R bun:bun /data && exec su -s /bin/sh bun -c 'bun run db:migrate && bun run start'"]
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2026 Hugo (exosphere.site) 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+130
README.md
··· 1 + # Context 2 + 3 + Small groups of people and communities must rely on centralized and big tech tools to organize themselves or communicate. 4 + 5 + There should be alternatives where communities can self host their platforms and own their data. 6 + With full independence and no dependency on big tech. 7 + 8 + Some tools exist (forums or small centralized platforms), but they often add friction. 9 + 10 + We believe that AT Protocol can solve many of these issues. 11 + 12 + # Exosphere 13 + 14 + Exosphere is a set of small and modular tools. 15 + 16 + ## Principle 17 + 18 + - AT Protocol Authentication 19 + - Public spheres are publicly readable, all interactions require an AT Protocol account 20 + - Data belongs to the users (stored on their PDS) 21 + - Self hostable 22 + 23 + ## What kind of tools 24 + 25 + - Feature requests 26 + - Kanban board 27 + - Feed and discussions 28 + - Micro blogging 29 + - Forums 30 + - Polls 31 + - Event organization 32 + - Forms 33 + 34 + # Spheres 35 + 36 + A Sphere is a community, a small group of friends, a family, a company, whatever. 37 + It can be self hosted, or rely on a Sphere hosting provider. 38 + 39 + A Sphere can be public or private. 40 + 41 + ## Private Spheres 42 + 43 + Private Spheres are invitation only, users must have an AT Protocol DID. 44 + All content is stored on the Sphere database to preserve privacy (AT Protocol repos are public by design). 45 + 46 + ## Public Spheres 47 + 48 + Public Spheres content is readable by anyone. All interactions require AT Protocol authentication. 49 + 50 + ### Write access modes 51 + 52 + - **Open** - Any authenticated user can participate (react, comment, post). 53 + - **Members only** - Public read, but only invited members can participate. Anyone can view the content, but interactions (reactions, comments) are restricted to approved members. Enforced at the AppView/indexer level. 54 + 55 + ### Future: unauthenticated access 56 + 57 + Unauthenticated write access (e.g. anonymous poll votes) may be added later as an opt-in feature for specific modules. See [ARCHITECTURE.md](./ARCHITECTURE.md) for details on the tradeoffs. 58 + 59 + # Technical implementation 60 + 61 + We want the look and feel of Exosphere to be friendly but professional. 62 + Everything must be super reliable and fast from the ground up. 63 + 64 + - Bun 65 + - AT Protocol 66 + - TypeScript 67 + - Strong types everywhere 68 + - Zod schemas where needed 69 + - Backend: Hono.js 70 + - Frontend: Preact 71 + - Preact Signals for state management 72 + - CSS / Design systems : vanilla-extract 73 + 74 + # Getting started 75 + 76 + ## Prerequisites 77 + 78 + - [Bun](https://bun.sh) 79 + - [Docker](https://www.docker.com/) (for the local PDS) 80 + 81 + ## Setup 82 + 83 + ```bash 84 + bun install 85 + 86 + # Generate the OAuth private key (one-time) 87 + bun run packages/core/scripts/generate-key.ts 88 + ``` 89 + 90 + Copy `.env.example` to `.env` and fill in the generated `OAUTH_PRIVATE_KEY`. 91 + 92 + ## Local PDS 93 + 94 + A local PDS (Personal Data Server) lets you develop against AT Protocol without hitting production infrastructure. 95 + 96 + ```bash 97 + # Generate PDS config (one-time, creates pds/pds.env) 98 + bun run pds:init 99 + 100 + # Start the PDS container 101 + bun run pds:up 102 + 103 + # Create test accounts 104 + bun run pds:account alice 105 + bun run pds:account bob 106 + ``` 107 + 108 + Each account is created with a handle like `alice.pds.dev` and a DID registered on `plc.directory`. The PDS runs at http://localhost:2583. 109 + 110 + **Login with the DID**, not the handle. Local PDS handles (e.g. `alice.pds.dev`) don't resolve via DNS. The DID (`did:plc:...`) is printed when the account is created. 111 + 112 + Other PDS commands: 113 + 114 + | Command | Description | 115 + | ------------------ | ------------------------- | 116 + | `bun run pds:logs` | Follow PDS container logs | 117 + | `bun run pds:down` | Stop the PDS container | 118 + 119 + ## Run 120 + 121 + ```bash 122 + # Start both backend (port 3000) and frontend (port 5173) 123 + bun run dev 124 + 125 + # Or start them separately 126 + bun run dev:server 127 + bun run dev:client 128 + ``` 129 + 130 + Open http://localhost:5173 in your browser. The Vite dev server proxies `/api` requests to the backend.
+79
TODO.md
··· 1 + # TODO 2 + 3 + ## Scaffolding 4 + 5 + - [x] Monorepo setup (Bun workspaces) 6 + - [x] Root TypeScript config 7 + - [x] `@exosphere/core` package (db, auth placeholder, types) 8 + - [x] `@exosphere/feeds` package (API routes, schemas, migrations, UI) 9 + - [x] `@exosphere/app` package (Hono server + Vite dev setup) 10 + - [x] `ExosphereModule` interface for modular architecture 11 + - [x] SQLite setup with WAL mode 12 + - [x] Vite config with Preact and vanilla-extract plugins 13 + - [x] API proxy from Vite dev server to Hono backend 14 + 15 + ## Core 16 + 17 + - [x] AT Protocol OAuth authentication (`core/auth`) 18 + - [x] Session middleware for Hono (protect routes, expose user DID) 19 + - [ ] Shared UI components (`core/ui` — layout, buttons, inputs) 20 + - [ ] Design tokens with vanilla-extract (colors, spacing, typography) 21 + - [x] Base migration system (run module migrations in order) 22 + 23 + ## Spheres 24 + 25 + - [ ] Sphere creation and settings 26 + - [ ] Public vs private Spheres 27 + - [ ] Sphere-scoped feature requests 28 + - [ ] Sphere-scoped feeds 29 + - [ ] Cross-admin member revocation: when admin B revokes a member invited by admin A, the approval record on admin A's PDS can't be deleted. Consider publishing a revocation record on the revoker's own PDS (similar to `site.exosphere.moderation`) so third-party indexers see the revocation on-protocol. 30 + 31 + ## Feature Requests Module 32 + 33 + - [x] Submit feature requests (title, description, category) 34 + - [ ] Handle feature request edition 35 + - [ ] Should we have the ability to do it? 36 + - [ ] How do we show to users that a feature request was indeed edited 37 + - [ ] Should we reset vote count? 38 + - [x] Upvote requests 39 + - [x] Status workflow (open → approved/declined → done) 40 + - [x] Admin ability to update status 41 + - [ ] Post official/pinned responses 42 + - [x] Sort by top voted, newest 43 + - [ ] Filter by status by category 44 + - [ ] Comments on requests 45 + - [x] Simple comments 46 + - [ ] Comments threads on requests 47 + - [x] Merge duplicate requests 48 + - [ ] Request categories management 49 + 50 + ## Feeds Module 51 + 52 + - [ ] Full CRUD for posts (create, read, update, delete) 53 + - [ ] Write post records to user PDS 54 + - [ ] Index PDS records in local SQLite 55 + - [ ] Reply threads (nested replies, thread view) 56 + - [ ] Feed pagination 57 + - [ ] Post composer UI component 58 + - [ ] Thread view UI component 59 + 60 + ## Lexicons 61 + 62 + - [x] Define formal Lexicon schemas for all AT Protocol record types (exact record shapes are still being defined) 63 + - [x] `site.exosphere.featureRequest` 64 + - [ ] Feed/post record types 65 + - [ ] Set up Lexicon codegen for type-safe record handling 66 + 67 + ## Infrastructure 68 + 69 + - [ ] Docker 70 + - [x] Vite production build (serve static assets from Hono) 71 + - [x] Environment config (.env handling) 72 + 73 + ## Future Modules 74 + 75 + - [ ] Kanban board 76 + - [ ] Forums 77 + - [ ] Polls 78 + - [ ] Event organization 79 + - [ ] Forms
+960
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "exosphere", 7 + "devDependencies": { 8 + "@playwright/test": "^1.58.2", 9 + "@types/better-sqlite3": "^7.6.13", 10 + "@types/bun": "^1.3.11", 11 + "better-sqlite3": "^12.8.0", 12 + "drizzle-kit": "^0.31.10", 13 + "oxfmt": "^0.41.0", 14 + "vitest": "^4.1.0", 15 + }, 16 + }, 17 + "packages/app": { 18 + "name": "@exosphere/app", 19 + "version": "0.0.1", 20 + "dependencies": { 21 + "@exosphere/client": "workspace:*", 22 + "@exosphere/core": "workspace:*", 23 + "@exosphere/feature-requests": "workspace:*", 24 + "@exosphere/feeds": "workspace:*", 25 + "@exosphere/indexer": "workspace:*", 26 + "@exosphere/mcp": "workspace:*", 27 + "@preact/signals": "^2.0.0", 28 + "@vanilla-extract/css": "^1.17.0", 29 + "hono": "^4.7.0", 30 + "lucide-preact": "^0.577.0", 31 + "preact": "^10.25.0", 32 + "preact-iso": "^2.0.0", 33 + "preact-render-to-string": "^6.6.6", 34 + }, 35 + "devDependencies": { 36 + "@preact/preset-vite": "^2.9.0", 37 + "@types/bun": "latest", 38 + "@vanilla-extract/vite-plugin": "^5.1.4", 39 + "typescript": "^5.7.0", 40 + "vite": "^8.0.0", 41 + }, 42 + }, 43 + "packages/client": { 44 + "name": "@exosphere/client", 45 + "peerDependencies": { 46 + "@preact/signals": "^2.0.0", 47 + "@vanilla-extract/css": "^1.0.0", 48 + "preact": "^10.25.0", 49 + "preact-iso": "^2.0.0", 50 + }, 51 + }, 52 + "packages/core": { 53 + "name": "@exosphere/core", 54 + "version": "0.0.1", 55 + "dependencies": { 56 + "@atproto/common-web": "^0.4.18", 57 + "@atproto/jwk-jose": "^0.1.0", 58 + "@atproto/oauth-client-node": "^0.3.17", 59 + "drizzle-orm": "^0.45.1", 60 + "hono": "^4.7.0", 61 + "zod": "^4.3.6", 62 + }, 63 + "devDependencies": { 64 + "@types/bun": "latest", 65 + "typescript": "^5.7.0", 66 + }, 67 + }, 68 + "packages/feature-requests": { 69 + "name": "@exosphere/feature-requests", 70 + "version": "0.0.1", 71 + "dependencies": { 72 + "@exosphere/client": "workspace:*", 73 + "@exosphere/core": "workspace:*", 74 + "@preact/signals": "^2.0.0", 75 + "@vanilla-extract/css": "^1.0.0", 76 + "drizzle-orm": "^0.45.1", 77 + "hono": "^4.7.0", 78 + "preact": "^10.25.0", 79 + "zod": "^4.3.6", 80 + }, 81 + "devDependencies": { 82 + "@types/bun": "latest", 83 + "typescript": "^5.7.0", 84 + }, 85 + }, 86 + "packages/feeds": { 87 + "name": "@exosphere/feeds", 88 + "version": "0.0.1", 89 + "dependencies": { 90 + "@exosphere/client": "workspace:*", 91 + "@exosphere/core": "workspace:*", 92 + "@preact/signals": "^2.0.0", 93 + "drizzle-orm": "^0.45.1", 94 + "hono": "^4.7.0", 95 + "preact": "^10.25.0", 96 + "zod": "^4.3.6", 97 + }, 98 + "devDependencies": { 99 + "@types/bun": "latest", 100 + "typescript": "^5.7.0", 101 + }, 102 + }, 103 + "packages/indexer": { 104 + "name": "@exosphere/indexer", 105 + "version": "0.0.1", 106 + "dependencies": { 107 + "@exosphere/core": "workspace:*", 108 + "@exosphere/feature-requests": "workspace:*", 109 + "@exosphere/feeds": "workspace:*", 110 + "drizzle-orm": "^0.45.1", 111 + }, 112 + "devDependencies": { 113 + "@types/bun": "latest", 114 + "typescript": "^5.7.0", 115 + }, 116 + }, 117 + "packages/mcp": { 118 + "name": "@exosphere/mcp", 119 + "version": "0.0.1", 120 + "dependencies": { 121 + "hono": "^4.7.0", 122 + }, 123 + "devDependencies": { 124 + "@types/bun": "latest", 125 + "typescript": "^5.7.0", 126 + }, 127 + }, 128 + }, 129 + "packages": { 130 + "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.6", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg=="], 131 + 132 + "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="], 133 + 134 + "@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q=="], 135 + 136 + "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA=="], 137 + 138 + "@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.25", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.6", "@atproto/did": "0.3.0" } }, "sha512-NY9WYM2VLd3IuMGRkkmvGBg8xqVEaK/fitv1vD8SMXqFTekdpjOLCCyv7EFtqVHouzmDcL83VOvWRfHVa8V9Yw=="], 139 + 140 + "@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.6", "@atproto-labs/handle-resolver": "0.3.6" } }, "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg=="], 141 + 142 + "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="], 143 + 144 + "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.3.0", "", {}, "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ=="], 145 + 146 + "@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.1.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "lru-cache": "^10.2.0" } }, "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw=="], 147 + 148 + "@atproto/common-web": ["@atproto/common-web@0.4.19", "", { "dependencies": { "@atproto/lex-data": "^0.0.14", "@atproto/lex-json": "^0.0.14", "@atproto/syntax": "^0.5.1", "zod": "^3.23.8" } }, "sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw=="], 149 + 150 + "@atproto/did": ["@atproto/did@0.3.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA=="], 151 + 152 + "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], 153 + 154 + "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.11", "", { "dependencies": { "@atproto/jwk": "0.6.0", "jose": "^5.2.0" } }, "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q=="], 155 + 156 + "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.2.0", "", { "dependencies": { "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "zod": "^3.23.8" } }, "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg=="], 157 + 158 + "@atproto/lex-data": ["@atproto/lex-data@0.0.14", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-53DUa9664SS76nGAMYopWsO10OH0AAdf7P/HSKB6Wzx3iqe6lk/K61QZnKxOG1LreYl5CfvIJU6eNf4txI6GlQ=="], 159 + 160 + "@atproto/lex-json": ["@atproto/lex-json@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.14", "tslib": "^2.8.1" } }, "sha512-6lPkDKqe7teEu4WrN5q7400cvZKgYS3uwUMvzG3F9XkgVYhOwSDCtouV/nSLBbpvo3l9OP0kiigtclcNcyekww=="], 161 + 162 + "@atproto/lexicon": ["@atproto/lexicon@0.6.2", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/syntax": "^0.5.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw=="], 163 + 164 + "@atproto/oauth-client": ["@atproto/oauth-client@0.6.0", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.2.6", "@atproto-labs/fetch": "^0.2.3", "@atproto-labs/handle-resolver": "^0.3.6", "@atproto-labs/identity-resolver": "^0.3.6", "@atproto-labs/simple-store": "^0.3.0", "@atproto-labs/simple-store-memory": "^0.1.4", "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "@atproto/oauth-types": "^0.6.3", "@atproto/xrpc": "^0.7.7", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-F7ZTKzFptXgyihMkd7QTdRSkrh4XqrS+qTw+V81k5Q6Bh3MB1L3ypvfSJ6v7SSUJa6XxoZYJTCahHC1e+ndE6Q=="], 165 + 166 + "@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.3.17", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.2.6", "@atproto-labs/handle-resolver-node": "^0.1.25", "@atproto-labs/simple-store": "^0.3.0", "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "@atproto/jwk-jose": "^0.1.11", "@atproto/jwk-webcrypto": "^0.2.0", "@atproto/oauth-client": "^0.6.0", "@atproto/oauth-types": "^0.6.3" } }, "sha512-67LNuKAlC35Exe7CB5S0QCAnEqr6fKV9Nvp64jAHFof1N+Vc9Ltt1K9oekE5Ctf7dvpGByrHRF0noUw9l9sWLA=="], 167 + 168 + "@atproto/oauth-types": ["@atproto/oauth-types@0.6.3", "", { "dependencies": { "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "zod": "^3.23.8" } }, "sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng=="], 169 + 170 + "@atproto/syntax": ["@atproto/syntax@0.5.1", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-J8DJjgKgACIyCTbpfvoTnf7+ofTx1kxTGO7KAftkC+jczaMdQhKdgIBAg2DaYy+80cvYGTHy5q/HI9qMAwGbWw=="], 171 + 172 + "@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], 173 + 174 + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], 175 + 176 + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], 177 + 178 + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], 179 + 180 + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], 181 + 182 + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], 183 + 184 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], 185 + 186 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 187 + 188 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], 189 + 190 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], 191 + 192 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], 193 + 194 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 195 + 196 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 197 + 198 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 199 + 200 + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], 201 + 202 + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], 203 + 204 + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], 205 + 206 + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], 207 + 208 + "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="], 209 + 210 + "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], 211 + 212 + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], 213 + 214 + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], 215 + 216 + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], 217 + 218 + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], 219 + 220 + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], 221 + 222 + "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], 223 + 224 + "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], 225 + 226 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], 227 + 228 + "@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], 229 + 230 + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], 231 + 232 + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], 233 + 234 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 235 + 236 + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 237 + 238 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 239 + 240 + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 241 + 242 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 243 + 244 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 245 + 246 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 247 + 248 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 249 + 250 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 251 + 252 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 253 + 254 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 255 + 256 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 257 + 258 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 259 + 260 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 261 + 262 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 263 + 264 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 265 + 266 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 267 + 268 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 269 + 270 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 271 + 272 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 273 + 274 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 275 + 276 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 277 + 278 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 279 + 280 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 281 + 282 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 283 + 284 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 285 + 286 + "@exosphere/app": ["@exosphere/app@workspace:packages/app"], 287 + 288 + "@exosphere/client": ["@exosphere/client@workspace:packages/client"], 289 + 290 + "@exosphere/core": ["@exosphere/core@workspace:packages/core"], 291 + 292 + "@exosphere/feature-requests": ["@exosphere/feature-requests@workspace:packages/feature-requests"], 293 + 294 + "@exosphere/feeds": ["@exosphere/feeds@workspace:packages/feeds"], 295 + 296 + "@exosphere/indexer": ["@exosphere/indexer@workspace:packages/indexer"], 297 + 298 + "@exosphere/mcp": ["@exosphere/mcp@workspace:packages/mcp"], 299 + 300 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 301 + 302 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 303 + 304 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 305 + 306 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 307 + 308 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 309 + 310 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], 311 + 312 + "@oxc-project/types": ["@oxc-project/types@0.120.0", "", {}, "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg=="], 313 + 314 + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.41.0", "", { "os": "android", "cpu": "arm" }, "sha512-REfrqeMKGkfMP+m/ScX4f5jJBSmVNYcpoDF8vP8f8eYPDuPGZmzp56NIUsYmx3h7f6NzC6cE3gqh8GDWrJHCKw=="], 315 + 316 + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.41.0", "", { "os": "android", "cpu": "arm64" }, "sha512-s0b1dxNgb2KomspFV2LfogC2XtSJB42POXF4bMCLJyvQmAGos4ZtjGPfQreToQEaY0FQFjz3030ggI36rF1q5g=="], 317 + 318 + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.41.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EGXGualADbv/ZmamE7/2DbsrYmjoPlAmHEpTL4vapLF4EfVD6fr8/uQDFnPJkUBjiSWFJZtFNsGeN1B6V3owmA=="], 319 + 320 + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.41.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-WxySJEvdQQYMmyvISH3qDpTvoS0ebnIP63IMxLLWowJyPp/AAH0hdWtlo+iGNK5y3eVfa5jZguwNaQkDKWpGSw=="], 321 + 322 + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.41.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Y2kzMkv3U3oyuYaR4wTfGjOTYTXiFC/hXmG0yVASKkbh02BJkvD98Ij8bIevr45hNZ0DmZEgqiXF+9buD4yMYQ=="], 323 + 324 + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.41.0", "", { "os": "linux", "cpu": "arm" }, "sha512-ptazDjdUyhket01IjPTT6ULS1KFuBfTUU97osTP96X5y/0oso+AgAaJzuH81oP0+XXyrWIHbRzozSAuQm4p48g=="], 325 + 326 + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.41.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UkoL2OKxFD+56bPEBcdGn+4juTW4HRv/T6w1dIDLnvKKWr6DbarB/mtHXlADKlFiJubJz8pRkttOR7qjYR6lTA=="], 327 + 328 + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gofu0PuumSOHYczD8p62CPY4UF6ee+rSLZJdUXkpwxg6pILiwSDBIouPskjF/5nF3A7QZTz2O9KFNkNxxFN9tA=="], 329 + 330 + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g=="], 331 + 332 + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.41.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg=="], 333 + 334 + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.41.0", "", { "os": "linux", "cpu": "none" }, "sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA=="], 335 + 336 + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.41.0", "", { "os": "linux", "cpu": "none" }, "sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w=="], 337 + 338 + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.41.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw=="], 339 + 340 + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w=="], 341 + 342 + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA=="], 343 + 344 + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.41.0", "", { "os": "none", "cpu": "arm64" }, "sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg=="], 345 + 346 + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.41.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z7NAtu/RN8kjCQ1y5oDD0nTAeRswh3GJ93qwcW51srmidP7XPBmZbLlwERu1W5veCevQJtPS9xmkpcDTYsGIwQ=="], 347 + 348 + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.41.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-uNxxP3l4bJ6VyzIeRqCmBU2Q0SkCFgIhvx9/9dJ9V8t/v+jP1IBsuaLwCXGR8JPHtkj4tFp+RHtUmU2ZYAUpMA=="], 349 + 350 + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.41.0", "", { "os": "win32", "cpu": "x64" }, "sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A=="], 351 + 352 + "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="], 353 + 354 + "@preact/preset-vite": ["@preact/preset-vite@2.10.5", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@prefresh/vite": "^2.4.11", "@rollup/pluginutils": "^5.0.0", "babel-plugin-transform-hook-names": "^1.0.2", "debug": "^4.4.3", "magic-string": "^0.30.21", "picocolors": "^1.1.1", "vite-prerender-plugin": "^0.5.8", "zimmerframe": "^1.1.4" }, "peerDependencies": { "@babel/core": "7.x", "vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x" } }, "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw=="], 355 + 356 + "@preact/signals": ["@preact/signals@2.8.2", "", { "dependencies": { "@preact/signals-core": "^1.14.0" }, "peerDependencies": { "preact": ">= 10.25.0 || >=11.0.0-0" } }, "sha512-gym5yoa64c+0w2kL7zRAAjY548qzWXbbuOfjsK9F1nWrEqooDwyWnih5SNdonjhQSp27zUqYh7UrxIRnkCyFCA=="], 357 + 358 + "@preact/signals-core": ["@preact/signals-core@1.14.0", "", {}, "sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ=="], 359 + 360 + "@prefresh/babel-plugin": ["@prefresh/babel-plugin@0.5.3", "", {}, "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ=="], 361 + 362 + "@prefresh/core": ["@prefresh/core@1.5.9", "", { "peerDependencies": { "preact": "^10.0.0 || ^11.0.0-0" } }, "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ=="], 363 + 364 + "@prefresh/utils": ["@prefresh/utils@1.2.1", "", {}, "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw=="], 365 + 366 + "@prefresh/vite": ["@prefresh/vite@2.4.12", "", { "dependencies": { "@babel/core": "^7.22.1", "@prefresh/babel-plugin": "^0.5.2", "@prefresh/core": "^1.5.0", "@prefresh/utils": "^1.2.0", "@rollup/pluginutils": "^4.2.1" }, "peerDependencies": { "preact": "^10.4.0 || ^11.0.0-0", "vite": ">=2.0.0" } }, "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA=="], 367 + 368 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.10", "", { "os": "android", "cpu": "arm64" }, "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg=="], 369 + 370 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w=="], 371 + 372 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A=="], 373 + 374 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w=="], 375 + 376 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm" }, "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA=="], 377 + 378 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg=="], 379 + 380 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g=="], 381 + 382 + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w=="], 383 + 384 + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg=="], 385 + 386 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw=="], 387 + 388 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA=="], 389 + 390 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.10", "", { "os": "none", "cpu": "arm64" }, "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q=="], 391 + 392 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.10", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA=="], 393 + 394 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ=="], 395 + 396 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "x64" }, "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w=="], 397 + 398 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.10", "", {}, "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg=="], 399 + 400 + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], 401 + 402 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 403 + 404 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 405 + 406 + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], 407 + 408 + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 409 + 410 + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], 411 + 412 + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], 413 + 414 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 415 + 416 + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], 417 + 418 + "@vanilla-extract/babel-plugin-debug-ids": ["@vanilla-extract/babel-plugin-debug-ids@1.2.2", "", { "dependencies": { "@babel/core": "^7.23.9" } }, "sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw=="], 419 + 420 + "@vanilla-extract/compiler": ["@vanilla-extract/compiler@0.6.0", "", { "dependencies": { "@vanilla-extract/css": "^1.20.0", "@vanilla-extract/integration": "^8.0.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vite-node": "^3.2.2 || ^5.0.0 || ^6.0.0" } }, "sha512-FlZM8s/h1obGHdYSTo05iIXUr6hsNvoE/okv/e9Sq7GN+niofhUKyuZPSwZNVYMK49xxeWNH9mopOlGRRPV4mw=="], 421 + 422 + "@vanilla-extract/css": ["@vanilla-extract/css@1.20.0", "", { "dependencies": { "@emotion/hash": "^0.9.0", "@vanilla-extract/private": "^1.0.9", "css-what": "^6.1.0", "cssesc": "^3.0.0", "csstype": "^3.2.3", "dedent": "^1.5.3", "deep-object-diff": "^1.1.9", "deepmerge": "^4.2.2", "lru-cache": "^10.4.3", "media-query-parser": "^2.0.2", "modern-ahocorasick": "^1.0.0", "picocolors": "^1.0.0" } }, "sha512-yKuajXFlghIjRZmEfy95z6MYj+mzJPoD3nbNLVAUB8Np6I1P9g5vBlznQPD+0A46osCn0za/wIvp/cg8HU3aig=="], 423 + 424 + "@vanilla-extract/integration": ["@vanilla-extract/integration@8.0.9", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/plugin-syntax-typescript": "^7.23.3", "@vanilla-extract/babel-plugin-debug-ids": "^1.2.2", "@vanilla-extract/css": "^1.19.1", "dedent": "^1.5.3", "esbuild": "npm:esbuild@>=0.17.6 <0.28.0", "eval": "0.1.8", "find-up": "^5.0.0", "javascript-stringify": "^2.0.1", "mlly": "^1.4.2" } }, "sha512-NP+CSo5IYHDmkMMy5vAxY4R9i2+CAg4sxgvVaxuHiuY9q30i6dNUTujNNKZGW2urEkd4HVVI6NggeIyYjbGPwA=="], 425 + 426 + "@vanilla-extract/private": ["@vanilla-extract/private@1.0.9", "", {}, "sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA=="], 427 + 428 + "@vanilla-extract/vite-plugin": ["@vanilla-extract/vite-plugin@5.2.1", "", { "dependencies": { "@vanilla-extract/compiler": "^0.6.0", "@vanilla-extract/integration": "^8.0.9" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-1dmCgmTmls/c4G+t453vZIzZ+82ftr+JC2J48C1drVkiwtZ7DscYSIko9Ci0CyDptBLWz5EO9fWnqzfHnns8tg=="], 429 + 430 + "@vitest/expect": ["@vitest/expect@4.1.0", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA=="], 431 + 432 + "@vitest/mocker": ["@vitest/mocker@4.1.0", "", { "dependencies": { "@vitest/spy": "4.1.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw=="], 433 + 434 + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.0", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A=="], 435 + 436 + "@vitest/runner": ["@vitest/runner@4.1.0", "", { "dependencies": { "@vitest/utils": "4.1.0", "pathe": "^2.0.3" } }, "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ=="], 437 + 438 + "@vitest/snapshot": ["@vitest/snapshot@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "@vitest/utils": "4.1.0", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg=="], 439 + 440 + "@vitest/spy": ["@vitest/spy@4.1.0", "", {}, "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw=="], 441 + 442 + "@vitest/utils": ["@vitest/utils@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw=="], 443 + 444 + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], 445 + 446 + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], 447 + 448 + "babel-plugin-transform-hook-names": ["babel-plugin-transform-hook-names@1.0.2", "", { "peerDependencies": { "@babel/core": "^7.12.10" } }, "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw=="], 449 + 450 + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 451 + 452 + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.9", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg=="], 453 + 454 + "better-sqlite3": ["better-sqlite3@12.8.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ=="], 455 + 456 + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], 457 + 458 + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], 459 + 460 + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], 461 + 462 + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], 463 + 464 + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 465 + 466 + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 467 + 468 + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 469 + 470 + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], 471 + 472 + "caniuse-lite": ["caniuse-lite@1.0.30001780", "", {}, "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ=="], 473 + 474 + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], 475 + 476 + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 477 + 478 + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 479 + 480 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 481 + 482 + "core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="], 483 + 484 + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], 485 + 486 + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], 487 + 488 + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], 489 + 490 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 491 + 492 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 493 + 494 + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], 495 + 496 + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], 497 + 498 + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], 499 + 500 + "deep-object-diff": ["deep-object-diff@1.1.9", "", {}, "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA=="], 501 + 502 + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], 503 + 504 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 505 + 506 + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], 507 + 508 + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], 509 + 510 + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], 511 + 512 + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], 513 + 514 + "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], 515 + 516 + "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], 517 + 518 + "electron-to-chromium": ["electron-to-chromium@1.5.321", "", {}, "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ=="], 519 + 520 + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], 521 + 522 + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 523 + 524 + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], 525 + 526 + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 527 + 528 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 529 + 530 + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], 531 + 532 + "eval": ["eval@0.1.8", "", { "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" } }, "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw=="], 533 + 534 + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], 535 + 536 + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], 537 + 538 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 539 + 540 + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], 541 + 542 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 543 + 544 + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], 545 + 546 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 547 + 548 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 549 + 550 + "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], 551 + 552 + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], 553 + 554 + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], 555 + 556 + "hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="], 557 + 558 + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 559 + 560 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 561 + 562 + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 563 + 564 + "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], 565 + 566 + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="], 567 + 568 + "javascript-stringify": ["javascript-stringify@2.1.0", "", {}, "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="], 569 + 570 + "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], 571 + 572 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 573 + 574 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 575 + 576 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 577 + 578 + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], 579 + 580 + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], 581 + 582 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], 583 + 584 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], 585 + 586 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], 587 + 588 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], 589 + 590 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], 591 + 592 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], 593 + 594 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], 595 + 596 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], 597 + 598 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], 599 + 600 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], 601 + 602 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], 603 + 604 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 605 + 606 + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 607 + 608 + "lucide-preact": ["lucide-preact@0.577.0", "", { "peerDependencies": { "preact": "^10.27.2" } }, "sha512-fCY59YQ2OMYWqE1V7k8HwfXyiBMHAfTI1roCOasdc+Cekya7BIObSJ/cil+tVMSbU6siv4uZlaz5twAGmkYqIQ=="], 609 + 610 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 611 + 612 + "media-query-parser": ["media-query-parser@2.0.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" } }, "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w=="], 613 + 614 + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], 615 + 616 + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 617 + 618 + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 619 + 620 + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], 621 + 622 + "modern-ahocorasick": ["modern-ahocorasick@1.1.0", "", {}, "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ=="], 623 + 624 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 625 + 626 + "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 627 + 628 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 629 + 630 + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 631 + 632 + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], 633 + 634 + "node-html-parser": ["node-html-parser@6.1.13", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg=="], 635 + 636 + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], 637 + 638 + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], 639 + 640 + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], 641 + 642 + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 643 + 644 + "oxfmt": ["oxfmt@0.41.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.41.0", "@oxfmt/binding-android-arm64": "0.41.0", "@oxfmt/binding-darwin-arm64": "0.41.0", "@oxfmt/binding-darwin-x64": "0.41.0", "@oxfmt/binding-freebsd-x64": "0.41.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.41.0", "@oxfmt/binding-linux-arm-musleabihf": "0.41.0", "@oxfmt/binding-linux-arm64-gnu": "0.41.0", "@oxfmt/binding-linux-arm64-musl": "0.41.0", "@oxfmt/binding-linux-ppc64-gnu": "0.41.0", "@oxfmt/binding-linux-riscv64-gnu": "0.41.0", "@oxfmt/binding-linux-riscv64-musl": "0.41.0", "@oxfmt/binding-linux-s390x-gnu": "0.41.0", "@oxfmt/binding-linux-x64-gnu": "0.41.0", "@oxfmt/binding-linux-x64-musl": "0.41.0", "@oxfmt/binding-openharmony-arm64": "0.41.0", "@oxfmt/binding-win32-arm64-msvc": "0.41.0", "@oxfmt/binding-win32-ia32-msvc": "0.41.0", "@oxfmt/binding-win32-x64-msvc": "0.41.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg=="], 645 + 646 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 647 + 648 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 649 + 650 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 651 + 652 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 653 + 654 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 655 + 656 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 657 + 658 + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], 659 + 660 + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], 661 + 662 + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], 663 + 664 + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], 665 + 666 + "preact": ["preact@10.29.0", "", {}, "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg=="], 667 + 668 + "preact-iso": ["preact-iso@2.11.1", "", { "peerDependencies": { "preact": ">=10 || >= 11.0.0-0", "preact-render-to-string": ">=6.4.0" } }, "sha512-rLy0RmzP/hrDjnFdnEblxFgKtzUj4njkHrpGJBGS7S4QuYw1zv0lA38qsWpeAAB10JAz/hF2CsHrLen9ufCtbw=="], 669 + 670 + "preact-render-to-string": ["preact-render-to-string@6.6.6", "", { "peerDependencies": { "preact": ">=10 || >= 11.0.0-0" } }, "sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ=="], 671 + 672 + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], 673 + 674 + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], 675 + 676 + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 677 + 678 + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 679 + 680 + "require-like": ["require-like@0.1.2", "", {}, "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A=="], 681 + 682 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 683 + 684 + "rolldown": ["rolldown@1.0.0-rc.10", "", { "dependencies": { "@oxc-project/types": "=0.120.0", "@rolldown/pluginutils": "1.0.0-rc.10" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-x64": "1.0.0-rc.10", "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA=="], 685 + 686 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 687 + 688 + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 689 + 690 + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], 691 + 692 + "simple-code-frame": ["simple-code-frame@1.3.0", "", { "dependencies": { "kolorist": "^1.6.0" } }, "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w=="], 693 + 694 + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], 695 + 696 + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], 697 + 698 + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], 699 + 700 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 701 + 702 + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], 703 + 704 + "stack-trace": ["stack-trace@1.0.0-pre2", "", {}, "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A=="], 705 + 706 + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], 707 + 708 + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], 709 + 710 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 711 + 712 + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], 713 + 714 + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], 715 + 716 + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], 717 + 718 + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], 719 + 720 + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], 721 + 722 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 723 + 724 + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], 725 + 726 + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], 727 + 728 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 729 + 730 + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 731 + 732 + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 733 + 734 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 735 + 736 + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], 737 + 738 + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], 739 + 740 + "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], 741 + 742 + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 743 + 744 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 745 + 746 + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 747 + 748 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 749 + 750 + "vite": ["vite@8.0.1", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw=="], 751 + 752 + "vite-node": ["vite-node@6.0.0", "", { "dependencies": { "cac": "^7.0.0", "es-module-lexer": "^2.0.0", "obug": "^2.1.1", "pathe": "^2.0.3", "vite": "^8.0.0" }, "bin": { "vite-node": "dist/cli.mjs" } }, "sha512-oj4PVrT+pDh6GYf5wfUXkcZyekYS8kKPfLPXVl8qe324Ec6l4K2DUKNadRbZ3LQl0qGcDz+PyOo7ZAh00Y+JjQ=="], 753 + 754 + "vite-prerender-plugin": ["vite-prerender-plugin@0.5.13", "", { "dependencies": { "kolorist": "^1.8.0", "magic-string": "0.x >= 0.26.0", "node-html-parser": "^6.1.12", "simple-code-frame": "^1.3.0", "source-map": "^0.7.4", "stack-trace": "^1.0.0-pre2" }, "peerDependencies": { "vite": "5.x || 6.x || 7.x || 8.x" } }, "sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g=="], 755 + 756 + "vitest": ["vitest@4.1.0", "", { "dependencies": { "@vitest/expect": "4.1.0", "@vitest/mocker": "4.1.0", "@vitest/pretty-format": "4.1.0", "@vitest/runner": "4.1.0", "@vitest/snapshot": "4.1.0", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.0", "@vitest/browser-preview": "4.1.0", "@vitest/browser-webdriverio": "4.1.0", "@vitest/ui": "4.1.0", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw=="], 757 + 758 + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], 759 + 760 + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 761 + 762 + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 763 + 764 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 765 + 766 + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], 767 + 768 + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 769 + 770 + "@atproto-labs/did-resolver/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 771 + 772 + "@atproto-labs/handle-resolver/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 773 + 774 + "@atproto/common-web/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 775 + 776 + "@atproto/did/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 777 + 778 + "@atproto/jwk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 779 + 780 + "@atproto/jwk-webcrypto/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 781 + 782 + "@atproto/lexicon/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 783 + 784 + "@atproto/oauth-client/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 785 + 786 + "@atproto/oauth-types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 787 + 788 + "@atproto/xrpc/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 789 + 790 + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 791 + 792 + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], 793 + 794 + "@prefresh/vite/@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], 795 + 796 + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 797 + 798 + "@vanilla-extract/integration/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 799 + 800 + "node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 801 + 802 + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], 803 + 804 + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 805 + 806 + "tsx/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 807 + 808 + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], 809 + 810 + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], 811 + 812 + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], 813 + 814 + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], 815 + 816 + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], 817 + 818 + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], 819 + 820 + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], 821 + 822 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], 823 + 824 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], 825 + 826 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], 827 + 828 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], 829 + 830 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], 831 + 832 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], 833 + 834 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], 835 + 836 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], 837 + 838 + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], 839 + 840 + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], 841 + 842 + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], 843 + 844 + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], 845 + 846 + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], 847 + 848 + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], 849 + 850 + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], 851 + 852 + "@prefresh/vite/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 853 + 854 + "@prefresh/vite/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 855 + 856 + "@vanilla-extract/integration/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 857 + 858 + "@vanilla-extract/integration/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 859 + 860 + "@vanilla-extract/integration/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 861 + 862 + "@vanilla-extract/integration/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 863 + 864 + "@vanilla-extract/integration/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 865 + 866 + "@vanilla-extract/integration/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 867 + 868 + "@vanilla-extract/integration/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 869 + 870 + "@vanilla-extract/integration/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 871 + 872 + "@vanilla-extract/integration/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 873 + 874 + "@vanilla-extract/integration/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 875 + 876 + "@vanilla-extract/integration/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 877 + 878 + "@vanilla-extract/integration/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 879 + 880 + "@vanilla-extract/integration/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 881 + 882 + "@vanilla-extract/integration/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 883 + 884 + "@vanilla-extract/integration/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 885 + 886 + "@vanilla-extract/integration/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 887 + 888 + "@vanilla-extract/integration/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 889 + 890 + "@vanilla-extract/integration/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 891 + 892 + "@vanilla-extract/integration/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 893 + 894 + "@vanilla-extract/integration/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 895 + 896 + "@vanilla-extract/integration/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 897 + 898 + "@vanilla-extract/integration/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 899 + 900 + "@vanilla-extract/integration/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 901 + 902 + "@vanilla-extract/integration/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 903 + 904 + "@vanilla-extract/integration/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 905 + 906 + "@vanilla-extract/integration/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 907 + 908 + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 909 + 910 + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 911 + 912 + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 913 + 914 + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 915 + 916 + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 917 + 918 + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 919 + 920 + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 921 + 922 + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 923 + 924 + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 925 + 926 + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 927 + 928 + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 929 + 930 + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 931 + 932 + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 933 + 934 + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 935 + 936 + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 937 + 938 + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 939 + 940 + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 941 + 942 + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 943 + 944 + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 945 + 946 + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 947 + 948 + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 949 + 950 + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 951 + 952 + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 953 + 954 + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 955 + 956 + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 957 + 958 + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 959 + } 960 + }
+12
docker-compose.dev.yml
··· 1 + services: 2 + pds: 3 + image: ghcr.io/bluesky-social/pds:latest 4 + restart: unless-stopped 5 + ports: 6 + - "3000:3000" 7 + env_file: ./pds/pds.env 8 + volumes: 9 + - pds-data:/pds/data 10 + 11 + volumes: 12 + pds-data:
+13
drizzle.config.ts
··· 1 + import { defineConfig } from "drizzle-kit"; 2 + 3 + export default defineConfig({ 4 + dialect: "sqlite", 5 + schema: [ 6 + "./packages/core/src/db/schema/*.ts", 7 + "./packages/feature-requests/src/db/schema.ts", 8 + "./packages/feeds/src/db/schema.ts", 9 + ], 10 + dbCredentials: { 11 + url: "exosphere.sqlite", 12 + }, 13 + });
+133
drizzle/0000_famous_wallop.sql
··· 1 + CREATE TABLE `oauth_sessions` ( 2 + `key` text PRIMARY KEY NOT NULL, 3 + `session` text NOT NULL, 4 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 5 + `updated_at` text DEFAULT (datetime('now')) NOT NULL 6 + ); 7 + --> statement-breakpoint 8 + CREATE TABLE `oauth_states` ( 9 + `key` text PRIMARY KEY NOT NULL, 10 + `state` text NOT NULL, 11 + `created_at` text DEFAULT (datetime('now')) NOT NULL 12 + ); 13 + --> statement-breakpoint 14 + CREATE TABLE `indexer_cursor` ( 15 + `id` text PRIMARY KEY DEFAULT 'jetstream' NOT NULL, 16 + `cursor` integer NOT NULL, 17 + `updated_at` text DEFAULT (datetime('now')) NOT NULL 18 + ); 19 + --> statement-breakpoint 20 + CREATE TABLE `sphere_members` ( 21 + `sphere_id` text NOT NULL, 22 + `did` text NOT NULL, 23 + `role` text DEFAULT 'member' NOT NULL, 24 + `status` text DEFAULT 'invited' NOT NULL, 25 + `invited_by` text, 26 + `pds_uri` text, 27 + `approval_pds_uri` text, 28 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 29 + PRIMARY KEY(`sphere_id`, `did`), 30 + FOREIGN KEY (`sphere_id`) REFERENCES `spheres`(`id`) ON UPDATE no action ON DELETE no action 31 + ); 32 + --> statement-breakpoint 33 + CREATE INDEX `idx_sphere_members_did` ON `sphere_members` (`did`);--> statement-breakpoint 34 + CREATE TABLE `sphere_modules` ( 35 + `sphere_id` text NOT NULL, 36 + `module_name` text NOT NULL, 37 + `enabled_at` text DEFAULT (datetime('now')) NOT NULL, 38 + PRIMARY KEY(`sphere_id`, `module_name`), 39 + FOREIGN KEY (`sphere_id`) REFERENCES `spheres`(`id`) ON UPDATE no action ON DELETE no action 40 + ); 41 + --> statement-breakpoint 42 + CREATE TABLE `spheres` ( 43 + `id` text PRIMARY KEY NOT NULL, 44 + `slug` text NOT NULL, 45 + `name` text NOT NULL, 46 + `description` text, 47 + `visibility` text DEFAULT 'public' NOT NULL, 48 + `write_access` text DEFAULT 'open' NOT NULL, 49 + `owner_did` text NOT NULL, 50 + `pds_uri` text, 51 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 52 + `updated_at` text DEFAULT (datetime('now')) NOT NULL 53 + ); 54 + --> statement-breakpoint 55 + CREATE UNIQUE INDEX `spheres_slug_unique` ON `spheres` (`slug`);--> statement-breakpoint 56 + CREATE TABLE `feature_request_comment_votes` ( 57 + `comment_id` text NOT NULL, 58 + `author_did` text NOT NULL, 59 + `pds_uri` text, 60 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 61 + PRIMARY KEY(`comment_id`, `author_did`), 62 + FOREIGN KEY (`comment_id`) REFERENCES `feature_request_comments`(`id`) ON UPDATE no action ON DELETE no action 63 + ); 64 + --> statement-breakpoint 65 + CREATE INDEX `idx_feature_request_comment_votes_comment` ON `feature_request_comment_votes` (`comment_id`);--> statement-breakpoint 66 + CREATE TABLE `feature_request_comments` ( 67 + `id` text PRIMARY KEY NOT NULL, 68 + `request_id` text NOT NULL, 69 + `author_did` text NOT NULL, 70 + `content` text NOT NULL, 71 + `pds_uri` text, 72 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 73 + `updated_at` text DEFAULT (datetime('now')) NOT NULL, 74 + `hidden_at` text, 75 + `moderated_by` text, 76 + FOREIGN KEY (`request_id`) REFERENCES `feature_requests`(`id`) ON UPDATE no action ON DELETE no action 77 + ); 78 + --> statement-breakpoint 79 + CREATE INDEX `idx_feature_request_comments_request` ON `feature_request_comments` (`request_id`);--> statement-breakpoint 80 + CREATE INDEX `idx_feature_request_comments_author_request` ON `feature_request_comments` (`author_did`,`request_id`);--> statement-breakpoint 81 + CREATE TABLE `feature_request_statuses` ( 82 + `id` text PRIMARY KEY NOT NULL, 83 + `request_id` text NOT NULL, 84 + `author_did` text NOT NULL, 85 + `status` text NOT NULL, 86 + `pds_uri` text, 87 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 88 + FOREIGN KEY (`request_id`) REFERENCES `feature_requests`(`id`) ON UPDATE no action ON DELETE no action 89 + ); 90 + --> statement-breakpoint 91 + CREATE INDEX `idx_feature_request_statuses_request` ON `feature_request_statuses` (`request_id`);--> statement-breakpoint 92 + CREATE TABLE `feature_request_votes` ( 93 + `request_id` text NOT NULL, 94 + `author_did` text NOT NULL, 95 + `pds_uri` text, 96 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 97 + PRIMARY KEY(`request_id`, `author_did`), 98 + FOREIGN KEY (`request_id`) REFERENCES `feature_requests`(`id`) ON UPDATE no action ON DELETE no action 99 + ); 100 + --> statement-breakpoint 101 + CREATE INDEX `idx_feature_request_votes_request` ON `feature_request_votes` (`request_id`);--> statement-breakpoint 102 + CREATE TABLE `feature_requests` ( 103 + `id` text PRIMARY KEY NOT NULL, 104 + `number` integer NOT NULL, 105 + `author_did` text NOT NULL, 106 + `title` text NOT NULL, 107 + `description` text NOT NULL, 108 + `category` text DEFAULT 'general' NOT NULL, 109 + `status` text DEFAULT 'requested' NOT NULL, 110 + `duplicate_of_id` text, 111 + `pds_uri` text, 112 + `hidden_at` text, 113 + `moderated_by` text, 114 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 115 + `updated_at` text DEFAULT (datetime('now')) NOT NULL 116 + ); 117 + --> statement-breakpoint 118 + CREATE UNIQUE INDEX `feature_requests_number_unique` ON `feature_requests` (`number`);--> statement-breakpoint 119 + CREATE INDEX `idx_feature_requests_status` ON `feature_requests` (`status`);--> statement-breakpoint 120 + CREATE INDEX `idx_feature_requests_created` ON `feature_requests` (`created_at`);--> statement-breakpoint 121 + CREATE INDEX `idx_feature_requests_category` ON `feature_requests` (`category`);--> statement-breakpoint 122 + CREATE TABLE `feed_posts` ( 123 + `id` text PRIMARY KEY NOT NULL, 124 + `author_did` text NOT NULL, 125 + `content` text NOT NULL, 126 + `parent_id` text, 127 + `pds_uri` text, 128 + `created_at` text DEFAULT (datetime('now')) NOT NULL, 129 + `updated_at` text DEFAULT (datetime('now')) NOT NULL 130 + ); 131 + --> statement-breakpoint 132 + CREATE INDEX `idx_feed_posts_parent` ON `feed_posts` (`parent_id`);--> statement-breakpoint 133 + CREATE INDEX `idx_feed_posts_created` ON `feed_posts` (`created_at`);
+833
drizzle/meta/0000_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "0a0e0853-16f1-4551-939a-ba2839138eed", 5 + "prevId": "00000000-0000-0000-0000-000000000000", 6 + "tables": { 7 + "oauth_sessions": { 8 + "name": "oauth_sessions", 9 + "columns": { 10 + "key": { 11 + "name": "key", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "session": { 18 + "name": "session", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "created_at": { 25 + "name": "created_at", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false, 30 + "default": "(datetime('now'))" 31 + }, 32 + "updated_at": { 33 + "name": "updated_at", 34 + "type": "text", 35 + "primaryKey": false, 36 + "notNull": true, 37 + "autoincrement": false, 38 + "default": "(datetime('now'))" 39 + } 40 + }, 41 + "indexes": {}, 42 + "foreignKeys": {}, 43 + "compositePrimaryKeys": {}, 44 + "uniqueConstraints": {}, 45 + "checkConstraints": {} 46 + }, 47 + "oauth_states": { 48 + "name": "oauth_states", 49 + "columns": { 50 + "key": { 51 + "name": "key", 52 + "type": "text", 53 + "primaryKey": true, 54 + "notNull": true, 55 + "autoincrement": false 56 + }, 57 + "state": { 58 + "name": "state", 59 + "type": "text", 60 + "primaryKey": false, 61 + "notNull": true, 62 + "autoincrement": false 63 + }, 64 + "created_at": { 65 + "name": "created_at", 66 + "type": "text", 67 + "primaryKey": false, 68 + "notNull": true, 69 + "autoincrement": false, 70 + "default": "(datetime('now'))" 71 + } 72 + }, 73 + "indexes": {}, 74 + "foreignKeys": {}, 75 + "compositePrimaryKeys": {}, 76 + "uniqueConstraints": {}, 77 + "checkConstraints": {} 78 + }, 79 + "indexer_cursor": { 80 + "name": "indexer_cursor", 81 + "columns": { 82 + "id": { 83 + "name": "id", 84 + "type": "text", 85 + "primaryKey": true, 86 + "notNull": true, 87 + "autoincrement": false, 88 + "default": "'jetstream'" 89 + }, 90 + "cursor": { 91 + "name": "cursor", 92 + "type": "integer", 93 + "primaryKey": false, 94 + "notNull": true, 95 + "autoincrement": false 96 + }, 97 + "updated_at": { 98 + "name": "updated_at", 99 + "type": "text", 100 + "primaryKey": false, 101 + "notNull": true, 102 + "autoincrement": false, 103 + "default": "(datetime('now'))" 104 + } 105 + }, 106 + "indexes": {}, 107 + "foreignKeys": {}, 108 + "compositePrimaryKeys": {}, 109 + "uniqueConstraints": {}, 110 + "checkConstraints": {} 111 + }, 112 + "sphere_members": { 113 + "name": "sphere_members", 114 + "columns": { 115 + "sphere_id": { 116 + "name": "sphere_id", 117 + "type": "text", 118 + "primaryKey": false, 119 + "notNull": true, 120 + "autoincrement": false 121 + }, 122 + "did": { 123 + "name": "did", 124 + "type": "text", 125 + "primaryKey": false, 126 + "notNull": true, 127 + "autoincrement": false 128 + }, 129 + "role": { 130 + "name": "role", 131 + "type": "text", 132 + "primaryKey": false, 133 + "notNull": true, 134 + "autoincrement": false, 135 + "default": "'member'" 136 + }, 137 + "status": { 138 + "name": "status", 139 + "type": "text", 140 + "primaryKey": false, 141 + "notNull": true, 142 + "autoincrement": false, 143 + "default": "'invited'" 144 + }, 145 + "invited_by": { 146 + "name": "invited_by", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": false, 150 + "autoincrement": false 151 + }, 152 + "pds_uri": { 153 + "name": "pds_uri", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": false, 157 + "autoincrement": false 158 + }, 159 + "approval_pds_uri": { 160 + "name": "approval_pds_uri", 161 + "type": "text", 162 + "primaryKey": false, 163 + "notNull": false, 164 + "autoincrement": false 165 + }, 166 + "created_at": { 167 + "name": "created_at", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": true, 171 + "autoincrement": false, 172 + "default": "(datetime('now'))" 173 + } 174 + }, 175 + "indexes": { 176 + "idx_sphere_members_did": { 177 + "name": "idx_sphere_members_did", 178 + "columns": ["did"], 179 + "isUnique": false 180 + } 181 + }, 182 + "foreignKeys": { 183 + "sphere_members_sphere_id_spheres_id_fk": { 184 + "name": "sphere_members_sphere_id_spheres_id_fk", 185 + "tableFrom": "sphere_members", 186 + "tableTo": "spheres", 187 + "columnsFrom": ["sphere_id"], 188 + "columnsTo": ["id"], 189 + "onDelete": "no action", 190 + "onUpdate": "no action" 191 + } 192 + }, 193 + "compositePrimaryKeys": { 194 + "sphere_members_sphere_id_did_pk": { 195 + "columns": ["sphere_id", "did"], 196 + "name": "sphere_members_sphere_id_did_pk" 197 + } 198 + }, 199 + "uniqueConstraints": {}, 200 + "checkConstraints": {} 201 + }, 202 + "sphere_modules": { 203 + "name": "sphere_modules", 204 + "columns": { 205 + "sphere_id": { 206 + "name": "sphere_id", 207 + "type": "text", 208 + "primaryKey": false, 209 + "notNull": true, 210 + "autoincrement": false 211 + }, 212 + "module_name": { 213 + "name": "module_name", 214 + "type": "text", 215 + "primaryKey": false, 216 + "notNull": true, 217 + "autoincrement": false 218 + }, 219 + "enabled_at": { 220 + "name": "enabled_at", 221 + "type": "text", 222 + "primaryKey": false, 223 + "notNull": true, 224 + "autoincrement": false, 225 + "default": "(datetime('now'))" 226 + } 227 + }, 228 + "indexes": {}, 229 + "foreignKeys": { 230 + "sphere_modules_sphere_id_spheres_id_fk": { 231 + "name": "sphere_modules_sphere_id_spheres_id_fk", 232 + "tableFrom": "sphere_modules", 233 + "tableTo": "spheres", 234 + "columnsFrom": ["sphere_id"], 235 + "columnsTo": ["id"], 236 + "onDelete": "no action", 237 + "onUpdate": "no action" 238 + } 239 + }, 240 + "compositePrimaryKeys": { 241 + "sphere_modules_sphere_id_module_name_pk": { 242 + "columns": ["sphere_id", "module_name"], 243 + "name": "sphere_modules_sphere_id_module_name_pk" 244 + } 245 + }, 246 + "uniqueConstraints": {}, 247 + "checkConstraints": {} 248 + }, 249 + "spheres": { 250 + "name": "spheres", 251 + "columns": { 252 + "id": { 253 + "name": "id", 254 + "type": "text", 255 + "primaryKey": true, 256 + "notNull": true, 257 + "autoincrement": false 258 + }, 259 + "slug": { 260 + "name": "slug", 261 + "type": "text", 262 + "primaryKey": false, 263 + "notNull": true, 264 + "autoincrement": false 265 + }, 266 + "name": { 267 + "name": "name", 268 + "type": "text", 269 + "primaryKey": false, 270 + "notNull": true, 271 + "autoincrement": false 272 + }, 273 + "description": { 274 + "name": "description", 275 + "type": "text", 276 + "primaryKey": false, 277 + "notNull": false, 278 + "autoincrement": false 279 + }, 280 + "visibility": { 281 + "name": "visibility", 282 + "type": "text", 283 + "primaryKey": false, 284 + "notNull": true, 285 + "autoincrement": false, 286 + "default": "'public'" 287 + }, 288 + "write_access": { 289 + "name": "write_access", 290 + "type": "text", 291 + "primaryKey": false, 292 + "notNull": true, 293 + "autoincrement": false, 294 + "default": "'open'" 295 + }, 296 + "owner_did": { 297 + "name": "owner_did", 298 + "type": "text", 299 + "primaryKey": false, 300 + "notNull": true, 301 + "autoincrement": false 302 + }, 303 + "pds_uri": { 304 + "name": "pds_uri", 305 + "type": "text", 306 + "primaryKey": false, 307 + "notNull": false, 308 + "autoincrement": false 309 + }, 310 + "created_at": { 311 + "name": "created_at", 312 + "type": "text", 313 + "primaryKey": false, 314 + "notNull": true, 315 + "autoincrement": false, 316 + "default": "(datetime('now'))" 317 + }, 318 + "updated_at": { 319 + "name": "updated_at", 320 + "type": "text", 321 + "primaryKey": false, 322 + "notNull": true, 323 + "autoincrement": false, 324 + "default": "(datetime('now'))" 325 + } 326 + }, 327 + "indexes": { 328 + "spheres_slug_unique": { 329 + "name": "spheres_slug_unique", 330 + "columns": ["slug"], 331 + "isUnique": true 332 + } 333 + }, 334 + "foreignKeys": {}, 335 + "compositePrimaryKeys": {}, 336 + "uniqueConstraints": {}, 337 + "checkConstraints": {} 338 + }, 339 + "feature_request_comment_votes": { 340 + "name": "feature_request_comment_votes", 341 + "columns": { 342 + "comment_id": { 343 + "name": "comment_id", 344 + "type": "text", 345 + "primaryKey": false, 346 + "notNull": true, 347 + "autoincrement": false 348 + }, 349 + "author_did": { 350 + "name": "author_did", 351 + "type": "text", 352 + "primaryKey": false, 353 + "notNull": true, 354 + "autoincrement": false 355 + }, 356 + "pds_uri": { 357 + "name": "pds_uri", 358 + "type": "text", 359 + "primaryKey": false, 360 + "notNull": false, 361 + "autoincrement": false 362 + }, 363 + "created_at": { 364 + "name": "created_at", 365 + "type": "text", 366 + "primaryKey": false, 367 + "notNull": true, 368 + "autoincrement": false, 369 + "default": "(datetime('now'))" 370 + } 371 + }, 372 + "indexes": { 373 + "idx_feature_request_comment_votes_comment": { 374 + "name": "idx_feature_request_comment_votes_comment", 375 + "columns": ["comment_id"], 376 + "isUnique": false 377 + } 378 + }, 379 + "foreignKeys": { 380 + "feature_request_comment_votes_comment_id_feature_request_comments_id_fk": { 381 + "name": "feature_request_comment_votes_comment_id_feature_request_comments_id_fk", 382 + "tableFrom": "feature_request_comment_votes", 383 + "tableTo": "feature_request_comments", 384 + "columnsFrom": ["comment_id"], 385 + "columnsTo": ["id"], 386 + "onDelete": "no action", 387 + "onUpdate": "no action" 388 + } 389 + }, 390 + "compositePrimaryKeys": { 391 + "feature_request_comment_votes_comment_id_author_did_pk": { 392 + "columns": ["comment_id", "author_did"], 393 + "name": "feature_request_comment_votes_comment_id_author_did_pk" 394 + } 395 + }, 396 + "uniqueConstraints": {}, 397 + "checkConstraints": {} 398 + }, 399 + "feature_request_comments": { 400 + "name": "feature_request_comments", 401 + "columns": { 402 + "id": { 403 + "name": "id", 404 + "type": "text", 405 + "primaryKey": true, 406 + "notNull": true, 407 + "autoincrement": false 408 + }, 409 + "request_id": { 410 + "name": "request_id", 411 + "type": "text", 412 + "primaryKey": false, 413 + "notNull": true, 414 + "autoincrement": false 415 + }, 416 + "author_did": { 417 + "name": "author_did", 418 + "type": "text", 419 + "primaryKey": false, 420 + "notNull": true, 421 + "autoincrement": false 422 + }, 423 + "content": { 424 + "name": "content", 425 + "type": "text", 426 + "primaryKey": false, 427 + "notNull": true, 428 + "autoincrement": false 429 + }, 430 + "pds_uri": { 431 + "name": "pds_uri", 432 + "type": "text", 433 + "primaryKey": false, 434 + "notNull": false, 435 + "autoincrement": false 436 + }, 437 + "created_at": { 438 + "name": "created_at", 439 + "type": "text", 440 + "primaryKey": false, 441 + "notNull": true, 442 + "autoincrement": false, 443 + "default": "(datetime('now'))" 444 + }, 445 + "updated_at": { 446 + "name": "updated_at", 447 + "type": "text", 448 + "primaryKey": false, 449 + "notNull": true, 450 + "autoincrement": false, 451 + "default": "(datetime('now'))" 452 + }, 453 + "hidden_at": { 454 + "name": "hidden_at", 455 + "type": "text", 456 + "primaryKey": false, 457 + "notNull": false, 458 + "autoincrement": false 459 + }, 460 + "moderated_by": { 461 + "name": "moderated_by", 462 + "type": "text", 463 + "primaryKey": false, 464 + "notNull": false, 465 + "autoincrement": false 466 + } 467 + }, 468 + "indexes": { 469 + "idx_feature_request_comments_request": { 470 + "name": "idx_feature_request_comments_request", 471 + "columns": ["request_id"], 472 + "isUnique": false 473 + }, 474 + "idx_feature_request_comments_author_request": { 475 + "name": "idx_feature_request_comments_author_request", 476 + "columns": ["author_did", "request_id"], 477 + "isUnique": false 478 + } 479 + }, 480 + "foreignKeys": { 481 + "feature_request_comments_request_id_feature_requests_id_fk": { 482 + "name": "feature_request_comments_request_id_feature_requests_id_fk", 483 + "tableFrom": "feature_request_comments", 484 + "tableTo": "feature_requests", 485 + "columnsFrom": ["request_id"], 486 + "columnsTo": ["id"], 487 + "onDelete": "no action", 488 + "onUpdate": "no action" 489 + } 490 + }, 491 + "compositePrimaryKeys": {}, 492 + "uniqueConstraints": {}, 493 + "checkConstraints": {} 494 + }, 495 + "feature_request_statuses": { 496 + "name": "feature_request_statuses", 497 + "columns": { 498 + "id": { 499 + "name": "id", 500 + "type": "text", 501 + "primaryKey": true, 502 + "notNull": true, 503 + "autoincrement": false 504 + }, 505 + "request_id": { 506 + "name": "request_id", 507 + "type": "text", 508 + "primaryKey": false, 509 + "notNull": true, 510 + "autoincrement": false 511 + }, 512 + "author_did": { 513 + "name": "author_did", 514 + "type": "text", 515 + "primaryKey": false, 516 + "notNull": true, 517 + "autoincrement": false 518 + }, 519 + "status": { 520 + "name": "status", 521 + "type": "text", 522 + "primaryKey": false, 523 + "notNull": true, 524 + "autoincrement": false 525 + }, 526 + "pds_uri": { 527 + "name": "pds_uri", 528 + "type": "text", 529 + "primaryKey": false, 530 + "notNull": false, 531 + "autoincrement": false 532 + }, 533 + "created_at": { 534 + "name": "created_at", 535 + "type": "text", 536 + "primaryKey": false, 537 + "notNull": true, 538 + "autoincrement": false, 539 + "default": "(datetime('now'))" 540 + } 541 + }, 542 + "indexes": { 543 + "idx_feature_request_statuses_request": { 544 + "name": "idx_feature_request_statuses_request", 545 + "columns": ["request_id"], 546 + "isUnique": false 547 + } 548 + }, 549 + "foreignKeys": { 550 + "feature_request_statuses_request_id_feature_requests_id_fk": { 551 + "name": "feature_request_statuses_request_id_feature_requests_id_fk", 552 + "tableFrom": "feature_request_statuses", 553 + "tableTo": "feature_requests", 554 + "columnsFrom": ["request_id"], 555 + "columnsTo": ["id"], 556 + "onDelete": "no action", 557 + "onUpdate": "no action" 558 + } 559 + }, 560 + "compositePrimaryKeys": {}, 561 + "uniqueConstraints": {}, 562 + "checkConstraints": {} 563 + }, 564 + "feature_request_votes": { 565 + "name": "feature_request_votes", 566 + "columns": { 567 + "request_id": { 568 + "name": "request_id", 569 + "type": "text", 570 + "primaryKey": false, 571 + "notNull": true, 572 + "autoincrement": false 573 + }, 574 + "author_did": { 575 + "name": "author_did", 576 + "type": "text", 577 + "primaryKey": false, 578 + "notNull": true, 579 + "autoincrement": false 580 + }, 581 + "pds_uri": { 582 + "name": "pds_uri", 583 + "type": "text", 584 + "primaryKey": false, 585 + "notNull": false, 586 + "autoincrement": false 587 + }, 588 + "created_at": { 589 + "name": "created_at", 590 + "type": "text", 591 + "primaryKey": false, 592 + "notNull": true, 593 + "autoincrement": false, 594 + "default": "(datetime('now'))" 595 + } 596 + }, 597 + "indexes": { 598 + "idx_feature_request_votes_request": { 599 + "name": "idx_feature_request_votes_request", 600 + "columns": ["request_id"], 601 + "isUnique": false 602 + } 603 + }, 604 + "foreignKeys": { 605 + "feature_request_votes_request_id_feature_requests_id_fk": { 606 + "name": "feature_request_votes_request_id_feature_requests_id_fk", 607 + "tableFrom": "feature_request_votes", 608 + "tableTo": "feature_requests", 609 + "columnsFrom": ["request_id"], 610 + "columnsTo": ["id"], 611 + "onDelete": "no action", 612 + "onUpdate": "no action" 613 + } 614 + }, 615 + "compositePrimaryKeys": { 616 + "feature_request_votes_request_id_author_did_pk": { 617 + "columns": ["request_id", "author_did"], 618 + "name": "feature_request_votes_request_id_author_did_pk" 619 + } 620 + }, 621 + "uniqueConstraints": {}, 622 + "checkConstraints": {} 623 + }, 624 + "feature_requests": { 625 + "name": "feature_requests", 626 + "columns": { 627 + "id": { 628 + "name": "id", 629 + "type": "text", 630 + "primaryKey": true, 631 + "notNull": true, 632 + "autoincrement": false 633 + }, 634 + "number": { 635 + "name": "number", 636 + "type": "integer", 637 + "primaryKey": false, 638 + "notNull": true, 639 + "autoincrement": false 640 + }, 641 + "author_did": { 642 + "name": "author_did", 643 + "type": "text", 644 + "primaryKey": false, 645 + "notNull": true, 646 + "autoincrement": false 647 + }, 648 + "title": { 649 + "name": "title", 650 + "type": "text", 651 + "primaryKey": false, 652 + "notNull": true, 653 + "autoincrement": false 654 + }, 655 + "description": { 656 + "name": "description", 657 + "type": "text", 658 + "primaryKey": false, 659 + "notNull": true, 660 + "autoincrement": false 661 + }, 662 + "category": { 663 + "name": "category", 664 + "type": "text", 665 + "primaryKey": false, 666 + "notNull": true, 667 + "autoincrement": false, 668 + "default": "'general'" 669 + }, 670 + "status": { 671 + "name": "status", 672 + "type": "text", 673 + "primaryKey": false, 674 + "notNull": true, 675 + "autoincrement": false, 676 + "default": "'requested'" 677 + }, 678 + "duplicate_of_id": { 679 + "name": "duplicate_of_id", 680 + "type": "text", 681 + "primaryKey": false, 682 + "notNull": false, 683 + "autoincrement": false 684 + }, 685 + "pds_uri": { 686 + "name": "pds_uri", 687 + "type": "text", 688 + "primaryKey": false, 689 + "notNull": false, 690 + "autoincrement": false 691 + }, 692 + "hidden_at": { 693 + "name": "hidden_at", 694 + "type": "text", 695 + "primaryKey": false, 696 + "notNull": false, 697 + "autoincrement": false 698 + }, 699 + "moderated_by": { 700 + "name": "moderated_by", 701 + "type": "text", 702 + "primaryKey": false, 703 + "notNull": false, 704 + "autoincrement": false 705 + }, 706 + "created_at": { 707 + "name": "created_at", 708 + "type": "text", 709 + "primaryKey": false, 710 + "notNull": true, 711 + "autoincrement": false, 712 + "default": "(datetime('now'))" 713 + }, 714 + "updated_at": { 715 + "name": "updated_at", 716 + "type": "text", 717 + "primaryKey": false, 718 + "notNull": true, 719 + "autoincrement": false, 720 + "default": "(datetime('now'))" 721 + } 722 + }, 723 + "indexes": { 724 + "feature_requests_number_unique": { 725 + "name": "feature_requests_number_unique", 726 + "columns": ["number"], 727 + "isUnique": true 728 + }, 729 + "idx_feature_requests_status": { 730 + "name": "idx_feature_requests_status", 731 + "columns": ["status"], 732 + "isUnique": false 733 + }, 734 + "idx_feature_requests_created": { 735 + "name": "idx_feature_requests_created", 736 + "columns": ["created_at"], 737 + "isUnique": false 738 + }, 739 + "idx_feature_requests_category": { 740 + "name": "idx_feature_requests_category", 741 + "columns": ["category"], 742 + "isUnique": false 743 + } 744 + }, 745 + "foreignKeys": {}, 746 + "compositePrimaryKeys": {}, 747 + "uniqueConstraints": {}, 748 + "checkConstraints": {} 749 + }, 750 + "feed_posts": { 751 + "name": "feed_posts", 752 + "columns": { 753 + "id": { 754 + "name": "id", 755 + "type": "text", 756 + "primaryKey": true, 757 + "notNull": true, 758 + "autoincrement": false 759 + }, 760 + "author_did": { 761 + "name": "author_did", 762 + "type": "text", 763 + "primaryKey": false, 764 + "notNull": true, 765 + "autoincrement": false 766 + }, 767 + "content": { 768 + "name": "content", 769 + "type": "text", 770 + "primaryKey": false, 771 + "notNull": true, 772 + "autoincrement": false 773 + }, 774 + "parent_id": { 775 + "name": "parent_id", 776 + "type": "text", 777 + "primaryKey": false, 778 + "notNull": false, 779 + "autoincrement": false 780 + }, 781 + "pds_uri": { 782 + "name": "pds_uri", 783 + "type": "text", 784 + "primaryKey": false, 785 + "notNull": false, 786 + "autoincrement": false 787 + }, 788 + "created_at": { 789 + "name": "created_at", 790 + "type": "text", 791 + "primaryKey": false, 792 + "notNull": true, 793 + "autoincrement": false, 794 + "default": "(datetime('now'))" 795 + }, 796 + "updated_at": { 797 + "name": "updated_at", 798 + "type": "text", 799 + "primaryKey": false, 800 + "notNull": true, 801 + "autoincrement": false, 802 + "default": "(datetime('now'))" 803 + } 804 + }, 805 + "indexes": { 806 + "idx_feed_posts_parent": { 807 + "name": "idx_feed_posts_parent", 808 + "columns": ["parent_id"], 809 + "isUnique": false 810 + }, 811 + "idx_feed_posts_created": { 812 + "name": "idx_feed_posts_created", 813 + "columns": ["created_at"], 814 + "isUnique": false 815 + } 816 + }, 817 + "foreignKeys": {}, 818 + "compositePrimaryKeys": {}, 819 + "uniqueConstraints": {}, 820 + "checkConstraints": {} 821 + } 822 + }, 823 + "views": {}, 824 + "enums": {}, 825 + "_meta": { 826 + "schemas": {}, 827 + "tables": {}, 828 + "columns": {} 829 + }, 830 + "internal": { 831 + "indexes": {} 832 + } 833 + }
+13
drizzle/meta/_journal.json
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "entries": [ 5 + { 6 + "idx": 0, 7 + "version": "6", 8 + "when": 1774005081703, 9 + "tag": "0000_famous_wallop", 10 + "breakpoints": true 11 + } 12 + ] 13 + }
+37
package.json
··· 1 + { 2 + "name": "exosphere", 3 + "private": true, 4 + "workspaces": [ 5 + "packages/*" 6 + ], 7 + "scripts": { 8 + "dev": "bun run dev:server & bun run dev:client", 9 + "dev:server": "bun run --hot packages/app/src/server.ts", 10 + "dev:client": "bun run --filter '@exosphere/app' dev:client", 11 + "fmt": "oxfmt", 12 + "fmt:check": "oxfmt --check", 13 + "pds:init": "bun run scripts/pds-init.ts", 14 + "pds:up": "docker compose -f docker-compose.dev.yml up -d", 15 + "pds:down": "docker compose -f docker-compose.dev.yml down", 16 + "pds:logs": "docker compose -f docker-compose.dev.yml logs -f pds", 17 + "pds:account": "bun run scripts/pds-account.ts", 18 + "db:generate": "drizzle-kit generate", 19 + "db:migrate": "bun run packages/core/src/db/migrate.ts", 20 + "build": "bun run --filter '@exosphere/app' build", 21 + "start": "NODE_ENV=production bun run packages/app/src/server.ts", 22 + "start:indexer": "bun run packages/indexer/src/main.ts", 23 + "preview": "bun run db:generate && bun run db:migrate && bun run build && bun run start", 24 + "test": "vitest run", 25 + "test:watch": "vitest", 26 + "test:e2e": "playwright test --config packages/app/e2e/playwright.config.ts" 27 + }, 28 + "devDependencies": { 29 + "@playwright/test": "^1.58.2", 30 + "@types/better-sqlite3": "^7.6.13", 31 + "@types/bun": "^1.3.11", 32 + "better-sqlite3": "^12.8.0", 33 + "drizzle-kit": "^0.31.10", 34 + "oxfmt": "^0.41.0", 35 + "vitest": "^4.1.0" 36 + } 37 + }
+13
packages/app/e2e/global-setup.ts
··· 1 + import { execSync } from "node:child_process"; 2 + import { resolve } from "node:path"; 3 + 4 + const ROOT = resolve(import.meta.dirname, "../../.."); 5 + 6 + export default function globalSetup() { 7 + // Build the frontend for production SSR 8 + execSync("bun run build", { stdio: "inherit", cwd: ROOT }); 9 + 10 + // Seed the test database 11 + const seedScript = resolve(import.meta.dirname, "seed.ts"); 12 + execSync(`bun run ${seedScript}`, { stdio: "inherit", cwd: ROOT }); 13 + }
+29
packages/app/e2e/playwright.config.ts
··· 1 + import { defineConfig, devices } from "@playwright/test"; 2 + 3 + const PORT = 3002; 4 + 5 + export default defineConfig({ 6 + testDir: "./tests", 7 + fullyParallel: true, 8 + forbidOnly: !!process.env.CI, 9 + retries: process.env.CI ? 2 : 0, 10 + workers: process.env.CI ? 1 : undefined, 11 + reporter: "html", 12 + globalSetup: "./global-setup.ts", 13 + use: { 14 + baseURL: `http://localhost:${PORT}`, 15 + trace: "on-first-retry", 16 + }, 17 + projects: [ 18 + { 19 + name: "chromium", 20 + use: { ...devices["Desktop Chrome"] }, 21 + }, 22 + ], 23 + webServer: { 24 + command: `NODE_ENV=production DATABASE_PATH=/tmp/exosphere-e2e/db.sqlite DISABLE_INDEXER=1 PORT=${PORT} bun run packages/app/src/server.ts`, 25 + port: PORT, 26 + reuseExistingServer: !process.env.CI, 27 + cwd: "../../../", 28 + }, 29 + });
+149
packages/app/e2e/seed.ts
··· 1 + /** 2 + * E2E test seed script — runs under Bun to access bun:sqlite. 3 + * 4 + * Creates a temporary SQLite database with migrations applied and 5 + * seeds it with a sphere, feature requests, and comments. 6 + */ 7 + 8 + import { Database } from "bun:sqlite"; 9 + import { readFileSync } from "node:fs"; 10 + import { mkdirSync, rmSync } from "node:fs"; 11 + import { resolve } from "node:path"; 12 + 13 + const E2E_DB_DIR = "/tmp/exosphere-e2e"; 14 + const E2E_DB_PATH = `${E2E_DB_DIR}/db.sqlite`; 15 + const MIGRATIONS_DIR = resolve(import.meta.dirname, "../../../drizzle"); 16 + 17 + // Clean previous test DB 18 + rmSync(E2E_DB_DIR, { recursive: true, force: true }); 19 + mkdirSync(E2E_DB_DIR, { recursive: true }); 20 + 21 + // Create database 22 + const sqlite = new Database(E2E_DB_PATH, { create: true }); 23 + sqlite.run("PRAGMA journal_mode = WAL;"); 24 + sqlite.run("PRAGMA foreign_keys = ON;"); 25 + 26 + // Apply migrations — read the SQL files and split on drizzle's statement breakpoints 27 + const journal = JSON.parse(readFileSync(resolve(MIGRATIONS_DIR, "meta/_journal.json"), "utf-8")); 28 + for (const entry of journal.entries) { 29 + const sql = readFileSync(resolve(MIGRATIONS_DIR, `${entry.tag}.sql`), "utf-8"); 30 + const statements = sql.split("--> statement-breakpoint"); 31 + for (const stmt of statements) { 32 + const trimmed = stmt.trim(); 33 + if (trimmed) sqlite.run(trimmed); 34 + } 35 + } 36 + 37 + // ---- Seed data ---- 38 + 39 + const OWNER_DID = "did:plc:e2e-test-owner"; 40 + const MEMBER_DID = "did:plc:e2e-test-member"; 41 + const SPHERE_ID = "e2e-sphere-001"; 42 + 43 + // Sphere 44 + sqlite.run( 45 + `INSERT INTO spheres (id, slug, name, description, visibility, write_access, owner_did) 46 + VALUES (?, ?, ?, ?, 'public', 'open', ?)`, 47 + [SPHERE_ID, "test-sphere", "Test Sphere", "A sphere for E2E testing", OWNER_DID], 48 + ); 49 + 50 + // Owner membership 51 + sqlite.run( 52 + `INSERT INTO sphere_members (sphere_id, did, role, status) 53 + VALUES (?, ?, 'owner', 'active')`, 54 + [SPHERE_ID, OWNER_DID], 55 + ); 56 + 57 + // Enable feature-requests module 58 + sqlite.run( 59 + `INSERT INTO sphere_modules (sphere_id, module_name) 60 + VALUES (?, 'feature-requests')`, 61 + [SPHERE_ID], 62 + ); 63 + 64 + // Feature requests 65 + const featureRequests = [ 66 + { 67 + id: "fr-001", 68 + number: 1, 69 + title: "Add dark mode support", 70 + description: "It would be great to have a dark mode option for better readability at night.", 71 + category: "enhancement", 72 + status: "approved", 73 + authorDid: MEMBER_DID, 74 + }, 75 + { 76 + id: "fr-002", 77 + number: 2, 78 + title: "Export data as CSV", 79 + description: "Allow users to export their data in CSV format for analysis.", 80 + category: "general", 81 + status: "requested", 82 + authorDid: OWNER_DID, 83 + }, 84 + { 85 + id: "fr-003", 86 + number: 3, 87 + title: "Mobile app", 88 + description: "A native mobile application would improve the user experience significantly.", 89 + category: "general", 90 + status: "requested", 91 + authorDid: MEMBER_DID, 92 + }, 93 + { 94 + id: "fr-004", 95 + number: 4, 96 + title: "Keyboard shortcuts", 97 + description: "Add keyboard shortcuts for common actions like voting and navigation.", 98 + category: "enhancement", 99 + status: "done", 100 + authorDid: OWNER_DID, 101 + }, 102 + { 103 + id: "fr-005", 104 + number: 5, 105 + title: "Windows phone support", 106 + description: "Please add support for Windows Phone platform.", 107 + category: "general", 108 + status: "not-planned", 109 + authorDid: MEMBER_DID, 110 + }, 111 + ]; 112 + 113 + for (const fr of featureRequests) { 114 + sqlite.run( 115 + `INSERT INTO feature_requests (id, number, title, description, category, status, author_did) 116 + VALUES (?, ?, ?, ?, ?, ?, ?)`, 117 + [fr.id, fr.number, fr.title, fr.description, fr.category, fr.status, fr.authorDid], 118 + ); 119 + } 120 + 121 + // Votes for fr-001 (2 votes) and fr-002 (1 vote) 122 + sqlite.run(`INSERT INTO feature_request_votes (request_id, author_did) VALUES (?, ?)`, [ 123 + "fr-001", 124 + OWNER_DID, 125 + ]); 126 + sqlite.run(`INSERT INTO feature_request_votes (request_id, author_did) VALUES (?, ?)`, [ 127 + "fr-001", 128 + MEMBER_DID, 129 + ]); 130 + sqlite.run(`INSERT INTO feature_request_votes (request_id, author_did) VALUES (?, ?)`, [ 131 + "fr-002", 132 + MEMBER_DID, 133 + ]); 134 + 135 + // Comments on fr-001 136 + sqlite.run( 137 + `INSERT INTO feature_request_comments (id, request_id, author_did, content) 138 + VALUES (?, ?, ?, ?)`, 139 + ["comment-001", "fr-001", OWNER_DID, "Great idea! We should prioritize this."], 140 + ); 141 + sqlite.run( 142 + `INSERT INTO feature_request_comments (id, request_id, author_did, content) 143 + VALUES (?, ?, ?, ?)`, 144 + ["comment-002", "fr-001", MEMBER_DID, "I agree, dark mode would be very useful."], 145 + ); 146 + 147 + sqlite.close(); 148 + 149 + console.log(`[e2e-seed] Database seeded at ${E2E_DB_PATH}`);
+110
packages/app/e2e/tests/feature-requests.spec.ts
··· 1 + import { test, expect } from "@playwright/test"; 2 + 3 + test.describe("Feature requests list page", () => { 4 + test("renders the active requests list", async ({ page }) => { 5 + await page.goto("/infuse"); 6 + 7 + // Page title 8 + await expect(page.getByRole("heading", { name: "Infuse" })).toBeVisible(); 9 + 10 + // Tab navigation 11 + await expect(page.getByRole("link", { name: "Done" })).toBeVisible(); 12 + await expect(page.getByRole("link", { name: "Not planned" })).toBeVisible(); 13 + 14 + // Active feature requests (statuses: requested, approved, in-progress) 15 + await expect(page.getByRole("link", { name: /Add dark mode support/ })).toBeVisible(); 16 + await expect(page.getByRole("link", { name: /Export data as CSV/ })).toBeVisible(); 17 + await expect(page.getByRole("link", { name: /Mobile app/ })).toBeVisible(); 18 + 19 + // Completed/not-planned requests should NOT appear on active tab 20 + await expect(page.getByRole("link", { name: /Keyboard shortcuts/ })).not.toBeVisible(); 21 + await expect(page.getByRole("link", { name: /Windows phone support/ })).not.toBeVisible(); 22 + }); 23 + 24 + test("renders vote counts and comment counts", async ({ page }) => { 25 + await page.goto("/infuse"); 26 + 27 + // Wait for the list to render 28 + await expect(page.getByRole("link", { name: /Add dark mode support/ })).toBeVisible(); 29 + 30 + // fr-001 has 2 comments 31 + await expect(page.getByText("2 comments")).toBeVisible(); 32 + 33 + // Vote counts are rendered (△ + number) — check that the triangle markers exist 34 + const voteElements = page.getByText(/△\d/); 35 + await expect(voteElements.first()).toBeVisible(); 36 + }); 37 + }); 38 + 39 + test.describe("Feature requests tabs", () => { 40 + test("done tab shows completed requests", async ({ page }) => { 41 + await page.goto("/infuse/done"); 42 + 43 + await expect(page.getByRole("link", { name: /Keyboard shortcuts/ })).toBeVisible(); 44 + 45 + // Active requests should not appear 46 + await expect(page.getByRole("link", { name: /Add dark mode support/ })).not.toBeVisible(); 47 + }); 48 + 49 + test("not-planned tab shows rejected requests", async ({ page }) => { 50 + await page.goto("/infuse/not-planned"); 51 + 52 + await expect(page.getByRole("link", { name: /Windows phone support/ })).toBeVisible(); 53 + 54 + // Active requests should not appear 55 + await expect(page.getByRole("link", { name: /Add dark mode support/ })).not.toBeVisible(); 56 + }); 57 + }); 58 + 59 + test.describe("Feature request detail page", () => { 60 + test("renders a single feature request with comments", async ({ page }) => { 61 + await page.goto("/infuse/1"); 62 + 63 + // Title with number 64 + await expect(page.getByRole("heading", { name: /Add dark mode support/ })).toBeVisible(); 65 + 66 + // Comments 67 + await expect(page.getByText("Great idea! We should prioritize this.")).toBeVisible(); 68 + await expect(page.getByText("I agree, dark mode would be very useful.")).toBeVisible(); 69 + }); 70 + 71 + test("returns 404 for non-existent feature request", async ({ page }) => { 72 + await page.goto("/infuse/999"); 73 + await page.waitForLoadState("networkidle"); 74 + 75 + // The detail page should show no feature request content 76 + await expect(page.getByRole("heading", { name: /Add dark mode support/ })).not.toBeVisible(); 77 + }); 78 + }); 79 + 80 + test.describe("Navigation", () => { 81 + test("navigates from list to detail page", async ({ page }) => { 82 + await page.goto("/infuse"); 83 + 84 + // Click on a feature request link 85 + await page.getByRole("link", { name: /Add dark mode support/ }).click(); 86 + 87 + // Should navigate to detail page 88 + await expect(page).toHaveURL(/\/infuse\/1/); 89 + await expect(page.getByText("Great idea! We should prioritize this.")).toBeVisible(); 90 + }); 91 + 92 + test("navigates between tabs", async ({ page }) => { 93 + await page.goto("/infuse"); 94 + 95 + // Navigate to Done tab 96 + await page.getByRole("link", { name: "Done" }).click(); 97 + await expect(page).toHaveURL(/\/infuse\/done/); 98 + await expect(page.getByRole("link", { name: /Keyboard shortcuts/ })).toBeVisible(); 99 + 100 + // Navigate to Not planned tab 101 + await page.getByRole("link", { name: "Not planned" }).click(); 102 + await expect(page).toHaveURL(/\/infuse\/not-planned/); 103 + await expect(page.getByRole("link", { name: /Windows phone support/ })).toBeVisible(); 104 + 105 + // Navigate back to Requests tab 106 + await page.getByRole("link", { name: "Requests" }).click(); 107 + await expect(page).toHaveURL("/infuse"); 108 + await expect(page.getByRole("link", { name: /Add dark mode support/ })).toBeVisible(); 109 + }); 110 + });
+13
packages/app/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 7 + <title>Exosphere</title> 8 + </head> 9 + <body> 10 + <div id="app"></div> 11 + <script type="module" src="/src/client.tsx"></script> 12 + </body> 13 + </html>
+36
packages/app/package.json
··· 1 + { 2 + "name": "@exosphere/app", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "scripts": { 7 + "dev:server": "bun run --hot src/server.ts", 8 + "dev:client": "vite", 9 + "dev": "bun run dev:server & vite", 10 + "build": "vite build --outDir dist/client && vite build --ssr src/entry-server.tsx --outDir dist/server", 11 + "preview": "vite preview", 12 + "start": "NODE_ENV=production bun run src/server.ts" 13 + }, 14 + "dependencies": { 15 + "@exosphere/client": "workspace:*", 16 + "@exosphere/core": "workspace:*", 17 + "@exosphere/feature-requests": "workspace:*", 18 + "@exosphere/feeds": "workspace:*", 19 + "@exosphere/indexer": "workspace:*", 20 + "@exosphere/mcp": "workspace:*", 21 + "@preact/signals": "^2.0.0", 22 + "@vanilla-extract/css": "^1.17.0", 23 + "hono": "^4.7.0", 24 + "lucide-preact": "^0.577.0", 25 + "preact": "^10.25.0", 26 + "preact-iso": "^2.0.0", 27 + "preact-render-to-string": "^6.6.6" 28 + }, 29 + "devDependencies": { 30 + "@preact/preset-vite": "^2.9.0", 31 + "@types/bun": "latest", 32 + "@vanilla-extract/vite-plugin": "^5.1.4", 33 + "typescript": "^5.7.0", 34 + "vite": "^8.0.0" 35 + } 36 + }
+85
packages/app/playwright-report/index.html
··· 1 + 2 + 3 + <!DOCTYPE html> 4 + <html style='scrollbar-gutter: stable both-edges;'> 5 + <head> 6 + <meta charset='UTF-8'> 7 + <meta name='color-scheme' content='dark light'> 8 + <meta name='viewport' content='width=device-width, initial-scale=1.0'> 9 + <title>Playwright Test Report</title> 10 + <script type="module">var NA=Object.defineProperty;var BA=(l,u,c)=>u in l?NA(l,u,{enumerable:!0,configurable:!0,writable:!0,value:c}):l[u]=c;var yn=(l,u,c)=>BA(l,typeof u!="symbol"?u+"":u,c);(function(){const u=document.createElement("link").relList;if(u&&u.supports&&u.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))f(r);new MutationObserver(r=>{for(const o of r)if(o.type==="childList")for(const h of o.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&f(h)}).observe(document,{childList:!0,subtree:!0});function c(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function f(r){if(r.ep)return;r.ep=!0;const o=c(r);fetch(r.href,o)}})();function UA(l){return l&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l}var Mf={exports:{}},Ei={};/** 11 + * @license React 12 + * react-jsx-runtime.production.js 13 + * 14 + * Copyright (c) Meta Platforms, Inc. and affiliates. 15 + * 16 + * This source code is licensed under the MIT license found in the 17 + * LICENSE file in the root directory of this source tree. 18 + */var D1;function QA(){if(D1)return Ei;D1=1;var l=Symbol.for("react.transitional.element"),u=Symbol.for("react.fragment");function c(f,r,o){var h=null;if(o!==void 0&&(h=""+o),r.key!==void 0&&(h=""+r.key),"key"in r){o={};for(var v in r)v!=="key"&&(o[v]=r[v])}else o=r;return r=o.ref,{$$typeof:l,type:f,key:h,ref:r!==void 0?r:null,props:o}}return Ei.Fragment=u,Ei.jsx=c,Ei.jsxs=c,Ei}var M1;function zA(){return M1||(M1=1,Mf.exports=QA()),Mf.exports}var m=zA();const YA=15,xt=0,bn=1,LA=2,ye=-2,Ut=-3,j1=-4,xn=-5,Me=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],L2=1440,GA=0,XA=4,VA=9,ZA=5,qA=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],IA=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],KA=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],kA=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],JA=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],FA=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],ta=15;function Wf(){const l=this;let u,c,f,r,o,h;function v(A,E,S,O,X,B,b,p,x,R,U){let Z,F,j,D,N,K,J,k,nt,P,st,it,H,_,$;P=0,N=S;do f[A[E+P]]++,P++,N--;while(N!==0);if(f[0]==S)return b[0]=-1,p[0]=0,xt;for(k=p[0],K=1;K<=ta&&f[K]===0;K++);for(J=K,k<K&&(k=K),N=ta;N!==0&&f[N]===0;N--);for(j=N,k>N&&(k=N),p[0]=k,_=1<<K;K<N;K++,_<<=1)if((_-=f[K])<0)return Ut;if((_-=f[N])<0)return Ut;for(f[N]+=_,h[1]=K=0,P=1,H=2;--N!==0;)h[H]=K+=f[P],H++,P++;N=0,P=0;do(K=A[E+P])!==0&&(U[h[K]++]=N),P++;while(++N<S);for(S=h[j],h[0]=N=0,P=0,D=-1,it=-k,o[0]=0,st=0,$=0;J<=j;J++)for(Z=f[J];Z--!==0;){for(;J>it+k;){if(D++,it+=k,$=j-it,$=$>k?k:$,(F=1<<(K=J-it))>Z+1&&(F-=Z+1,H=J,K<$))for(;++K<$&&!((F<<=1)<=f[++H]);)F-=f[H];if($=1<<K,R[0]+$>L2)return Ut;o[D]=st=R[0],R[0]+=$,D!==0?(h[D]=N,r[0]=K,r[1]=k,K=N>>>it-k,r[2]=st-o[D-1]-K,x.set(r,(o[D-1]+K)*3)):b[0]=st}for(r[1]=J-it,P>=S?r[0]=192:U[P]<O?(r[0]=U[P]<256?0:96,r[2]=U[P++]):(r[0]=B[U[P]-O]+16+64,r[2]=X[U[P++]-O]),F=1<<J-it,K=N>>>it;K<$;K+=F)x.set(r,(st+K)*3);for(K=1<<J-1;(N&K)!==0;K>>>=1)N^=K;for(N^=K,nt=(1<<it)-1;(N&nt)!=h[D];)D--,it-=k,nt=(1<<it)-1}return _!==0&&j!=1?xn:xt}function y(A){let E;for(u||(u=[],c=[],f=new Int32Array(ta+1),r=[],o=new Int32Array(ta),h=new Int32Array(ta+1)),c.length<A&&(c=[]),E=0;E<A;E++)c[E]=0;for(E=0;E<ta+1;E++)f[E]=0;for(E=0;E<3;E++)r[E]=0;o.set(f.subarray(0,ta),0),h.set(f.subarray(0,ta+1),0)}l.inflate_trees_bits=function(A,E,S,O,X){let B;return y(19),u[0]=0,B=v(A,0,19,19,null,null,S,E,O,u,c),B==Ut?X.msg="oversubscribed dynamic bit lengths tree":(B==xn||E[0]===0)&&(X.msg="incomplete dynamic bit lengths tree",B=Ut),B},l.inflate_trees_dynamic=function(A,E,S,O,X,B,b,p,x){let R;return y(288),u[0]=0,R=v(S,0,A,257,KA,kA,B,O,p,u,c),R!=xt||O[0]===0?(R==Ut?x.msg="oversubscribed literal/length tree":R!=j1&&(x.msg="incomplete literal/length tree",R=Ut),R):(y(288),R=v(S,A,E,0,JA,FA,b,X,p,u,c),R!=xt||X[0]===0&&A>257?(R==Ut?x.msg="oversubscribed distance tree":R==xn?(x.msg="incomplete distance tree",R=Ut):R!=j1&&(x.msg="empty distance tree with lengths",R=Ut),R):xt)}}Wf.inflate_trees_fixed=function(l,u,c,f){return l[0]=VA,u[0]=ZA,c[0]=qA,f[0]=IA,xt};const Pu=0,H1=1,N1=2,B1=3,U1=4,Q1=5,z1=6,jf=7,Y1=8,$u=9;function WA(){const l=this;let u,c=0,f,r=0,o=0,h=0,v=0,y=0,A=0,E=0,S,O=0,X,B=0;function b(p,x,R,U,Z,F,j,D){let N,K,J,k,nt,P,st,it,H,_,$,ht,tt,C,L,W;st=D.next_in_index,it=D.avail_in,nt=j.bitb,P=j.bitk,H=j.write,_=H<j.read?j.read-H-1:j.end-H,$=Me[p],ht=Me[x];do{for(;P<20;)it--,nt|=(D.read_byte(st++)&255)<<P,P+=8;if(N=nt&$,K=R,J=U,W=(J+N)*3,(k=K[W])===0){nt>>=K[W+1],P-=K[W+1],j.win[H++]=K[W+2],_--;continue}do{if(nt>>=K[W+1],P-=K[W+1],(k&16)!==0){for(k&=15,tt=K[W+2]+(nt&Me[k]),nt>>=k,P-=k;P<15;)it--,nt|=(D.read_byte(st++)&255)<<P,P+=8;N=nt&ht,K=Z,J=F,W=(J+N)*3,k=K[W];do if(nt>>=K[W+1],P-=K[W+1],(k&16)!==0){for(k&=15;P<k;)it--,nt|=(D.read_byte(st++)&255)<<P,P+=8;if(C=K[W+2]+(nt&Me[k]),nt>>=k,P-=k,_-=tt,H>=C)L=H-C,H-L>0&&2>H-L?(j.win[H++]=j.win[L++],j.win[H++]=j.win[L++],tt-=2):(j.win.set(j.win.subarray(L,L+2),H),H+=2,L+=2,tt-=2);else{L=H-C;do L+=j.end;while(L<0);if(k=j.end-L,tt>k){if(tt-=k,H-L>0&&k>H-L)do j.win[H++]=j.win[L++];while(--k!==0);else j.win.set(j.win.subarray(L,L+k),H),H+=k,L+=k,k=0;L=0}}if(H-L>0&&tt>H-L)do j.win[H++]=j.win[L++];while(--tt!==0);else j.win.set(j.win.subarray(L,L+tt),H),H+=tt,L+=tt,tt=0;break}else if((k&64)===0)N+=K[W+2],N+=nt&Me[k],W=(J+N)*3,k=K[W];else return D.msg="invalid distance code",tt=D.avail_in-it,tt=P>>3<tt?P>>3:tt,it+=tt,st-=tt,P-=tt<<3,j.bitb=nt,j.bitk=P,D.avail_in=it,D.total_in+=st-D.next_in_index,D.next_in_index=st,j.write=H,Ut;while(!0);break}if((k&64)===0){if(N+=K[W+2],N+=nt&Me[k],W=(J+N)*3,(k=K[W])===0){nt>>=K[W+1],P-=K[W+1],j.win[H++]=K[W+2],_--;break}}else return(k&32)!==0?(tt=D.avail_in-it,tt=P>>3<tt?P>>3:tt,it+=tt,st-=tt,P-=tt<<3,j.bitb=nt,j.bitk=P,D.avail_in=it,D.total_in+=st-D.next_in_index,D.next_in_index=st,j.write=H,bn):(D.msg="invalid literal/length code",tt=D.avail_in-it,tt=P>>3<tt?P>>3:tt,it+=tt,st-=tt,P-=tt<<3,j.bitb=nt,j.bitk=P,D.avail_in=it,D.total_in+=st-D.next_in_index,D.next_in_index=st,j.write=H,Ut)}while(!0)}while(_>=258&&it>=10);return tt=D.avail_in-it,tt=P>>3<tt?P>>3:tt,it+=tt,st-=tt,P-=tt<<3,j.bitb=nt,j.bitk=P,D.avail_in=it,D.total_in+=st-D.next_in_index,D.next_in_index=st,j.write=H,xt}l.init=function(p,x,R,U,Z,F){u=Pu,A=p,E=x,S=R,O=U,X=Z,B=F,f=null},l.proc=function(p,x,R){let U,Z,F,j=0,D=0,N=0,K,J,k,nt;for(N=x.next_in_index,K=x.avail_in,j=p.bitb,D=p.bitk,J=p.write,k=J<p.read?p.read-J-1:p.end-J;;)switch(u){case Pu:if(k>=258&&K>=10&&(p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,R=b(A,E,S,O,X,B,p,x),N=x.next_in_index,K=x.avail_in,j=p.bitb,D=p.bitk,J=p.write,k=J<p.read?p.read-J-1:p.end-J,R!=xt)){u=R==bn?jf:$u;break}o=A,f=S,r=O,u=H1;case H1:for(U=o;D<U;){if(K!==0)R=xt;else return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);K--,j|=(x.read_byte(N++)&255)<<D,D+=8}if(Z=(r+(j&Me[U]))*3,j>>>=f[Z+1],D-=f[Z+1],F=f[Z],F===0){h=f[Z+2],u=z1;break}if((F&16)!==0){v=F&15,c=f[Z+2],u=N1;break}if((F&64)===0){o=F,r=Z/3+f[Z+2];break}if((F&32)!==0){u=jf;break}return u=$u,x.msg="invalid literal/length code",R=Ut,p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);case N1:for(U=v;D<U;){if(K!==0)R=xt;else return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);K--,j|=(x.read_byte(N++)&255)<<D,D+=8}c+=j&Me[U],j>>=U,D-=U,o=E,f=X,r=B,u=B1;case B1:for(U=o;D<U;){if(K!==0)R=xt;else return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);K--,j|=(x.read_byte(N++)&255)<<D,D+=8}if(Z=(r+(j&Me[U]))*3,j>>=f[Z+1],D-=f[Z+1],F=f[Z],(F&16)!==0){v=F&15,y=f[Z+2],u=U1;break}if((F&64)===0){o=F,r=Z/3+f[Z+2];break}return u=$u,x.msg="invalid distance code",R=Ut,p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);case U1:for(U=v;D<U;){if(K!==0)R=xt;else return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);K--,j|=(x.read_byte(N++)&255)<<D,D+=8}y+=j&Me[U],j>>=U,D-=U,u=Q1;case Q1:for(nt=J-y;nt<0;)nt+=p.end;for(;c!==0;){if(k===0&&(J==p.end&&p.read!==0&&(J=0,k=J<p.read?p.read-J-1:p.end-J),k===0&&(p.write=J,R=p.inflate_flush(x,R),J=p.write,k=J<p.read?p.read-J-1:p.end-J,J==p.end&&p.read!==0&&(J=0,k=J<p.read?p.read-J-1:p.end-J),k===0)))return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);p.win[J++]=p.win[nt++],k--,nt==p.end&&(nt=0),c--}u=Pu;break;case z1:if(k===0&&(J==p.end&&p.read!==0&&(J=0,k=J<p.read?p.read-J-1:p.end-J),k===0&&(p.write=J,R=p.inflate_flush(x,R),J=p.write,k=J<p.read?p.read-J-1:p.end-J,J==p.end&&p.read!==0&&(J=0,k=J<p.read?p.read-J-1:p.end-J),k===0)))return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);R=xt,p.win[J++]=h,k--,u=Pu;break;case jf:if(D>7&&(D-=8,K++,N--),p.write=J,R=p.inflate_flush(x,R),J=p.write,k=J<p.read?p.read-J-1:p.end-J,p.read!=p.write)return p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);u=Y1;case Y1:return R=bn,p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);case $u:return R=Ut,p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R);default:return R=ye,p.bitb=j,p.bitk=D,x.avail_in=K,x.total_in+=N-x.next_in_index,x.next_in_index=N,p.write=J,p.inflate_flush(x,R)}},l.free=function(){}}const L1=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],bl=0,Hf=1,G1=2,X1=3,V1=4,Z1=5,tc=6,ec=7,q1=8,Da=9;function _A(l,u){const c=this;let f=bl,r=0,o=0,h=0,v;const y=[0],A=[0],E=new WA;let S=0,O=new Int32Array(L2*3);const X=0,B=new Wf;c.bitk=0,c.bitb=0,c.win=new Uint8Array(u),c.end=u,c.read=0,c.write=0,c.reset=function(b,p){p&&(p[0]=X),f==tc&&E.free(b),f=bl,c.bitk=0,c.bitb=0,c.read=c.write=0},c.reset(l,null),c.inflate_flush=function(b,p){let x,R,U;return R=b.next_out_index,U=c.read,x=(U<=c.write?c.write:c.end)-U,x>b.avail_out&&(x=b.avail_out),x!==0&&p==xn&&(p=xt),b.avail_out-=x,b.total_out+=x,b.next_out.set(c.win.subarray(U,U+x),R),R+=x,U+=x,U==c.end&&(U=0,c.write==c.end&&(c.write=0),x=c.write-U,x>b.avail_out&&(x=b.avail_out),x!==0&&p==xn&&(p=xt),b.avail_out-=x,b.total_out+=x,b.next_out.set(c.win.subarray(U,U+x),R),R+=x,U+=x),b.next_out_index=R,c.read=U,p},c.proc=function(b,p){let x,R,U,Z,F,j,D,N;for(Z=b.next_in_index,F=b.avail_in,R=c.bitb,U=c.bitk,j=c.write,D=j<c.read?c.read-j-1:c.end-j;;){let K,J,k,nt,P,st,it,H;switch(f){case bl:for(;U<3;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}switch(x=R&7,S=x&1,x>>>1){case 0:R>>>=3,U-=3,x=U&7,R>>>=x,U-=x,f=Hf;break;case 1:K=[],J=[],k=[[]],nt=[[]],Wf.inflate_trees_fixed(K,J,k,nt),E.init(K[0],J[0],k[0],0,nt[0],0),R>>>=3,U-=3,f=tc;break;case 2:R>>>=3,U-=3,f=X1;break;case 3:return R>>>=3,U-=3,f=Da,b.msg="invalid block type",p=Ut,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p)}break;case Hf:for(;U<32;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}if((~R>>>16&65535)!=(R&65535))return f=Da,b.msg="invalid stored block lengths",p=Ut,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);r=R&65535,R=U=0,f=r!==0?G1:S!==0?ec:bl;break;case G1:if(F===0||D===0&&(j==c.end&&c.read!==0&&(j=0,D=j<c.read?c.read-j-1:c.end-j),D===0&&(c.write=j,p=c.inflate_flush(b,p),j=c.write,D=j<c.read?c.read-j-1:c.end-j,j==c.end&&c.read!==0&&(j=0,D=j<c.read?c.read-j-1:c.end-j),D===0)))return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);if(p=xt,x=r,x>F&&(x=F),x>D&&(x=D),c.win.set(b.read_buf(Z,x),j),Z+=x,F-=x,j+=x,D-=x,(r-=x)!==0)break;f=S!==0?ec:bl;break;case X1:for(;U<14;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}if(o=x=R&16383,(x&31)>29||(x>>5&31)>29)return f=Da,b.msg="too many length or distance symbols",p=Ut,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);if(x=258+(x&31)+(x>>5&31),!v||v.length<x)v=[];else for(N=0;N<x;N++)v[N]=0;R>>>=14,U-=14,h=0,f=V1;case V1:for(;h<4+(o>>>10);){for(;U<3;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}v[L1[h++]]=R&7,R>>>=3,U-=3}for(;h<19;)v[L1[h++]]=0;if(y[0]=7,x=B.inflate_trees_bits(v,y,A,O,b),x!=xt)return p=x,p==Ut&&(v=null,f=Da),c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);h=0,f=Z1;case Z1:for(;x=o,!(h>=258+(x&31)+(x>>5&31));){let _,$;for(x=y[0];U<x;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}if(x=O[(A[0]+(R&Me[x]))*3+1],$=O[(A[0]+(R&Me[x]))*3+2],$<16)R>>>=x,U-=x,v[h++]=$;else{for(N=$==18?7:$-14,_=$==18?11:3;U<x+N;){if(F!==0)p=xt;else return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);F--,R|=(b.read_byte(Z++)&255)<<U,U+=8}if(R>>>=x,U-=x,_+=R&Me[N],R>>>=N,U-=N,N=h,x=o,N+_>258+(x&31)+(x>>5&31)||$==16&&N<1)return v=null,f=Da,b.msg="invalid bit length repeat",p=Ut,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);$=$==16?v[N-1]:0;do v[N++]=$;while(--_!==0);h=N}}if(A[0]=-1,P=[],st=[],it=[],H=[],P[0]=9,st[0]=6,x=o,x=B.inflate_trees_dynamic(257+(x&31),1+(x>>5&31),v,P,st,it,H,O,b),x!=xt)return x==Ut&&(v=null,f=Da),p=x,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);E.init(P[0],st[0],O,it[0],O,H[0]),f=tc;case tc:if(c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,(p=E.proc(c,b,p))!=bn)return c.inflate_flush(b,p);if(p=xt,E.free(b),Z=b.next_in_index,F=b.avail_in,R=c.bitb,U=c.bitk,j=c.write,D=j<c.read?c.read-j-1:c.end-j,S===0){f=bl;break}f=ec;case ec:if(c.write=j,p=c.inflate_flush(b,p),j=c.write,D=j<c.read?c.read-j-1:c.end-j,c.read!=c.write)return c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);f=q1;case q1:return p=bn,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);case Da:return p=Ut,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p);default:return p=ye,c.bitb=R,c.bitk=U,b.avail_in=F,b.total_in+=Z-b.next_in_index,b.next_in_index=Z,c.write=j,c.inflate_flush(b,p)}}},c.free=function(b){c.reset(b,null),c.win=null,O=null},c.set_dictionary=function(b,p,x){c.win.set(b.subarray(p,p+x),0),c.read=c.write=x},c.sync_point=function(){return f==Hf?1:0}}const PA=32,$A=8,t8=0,I1=1,K1=2,k1=3,J1=4,F1=5,Nf=6,pi=7,W1=12,ea=13,e8=[0,0,255,255];function n8(){const l=this;l.mode=0,l.method=0,l.was=[0],l.need=0,l.marker=0,l.wbits=0;function u(c){return!c||!c.istate?ye:(c.total_in=c.total_out=0,c.msg=null,c.istate.mode=pi,c.istate.blocks.reset(c,null),xt)}l.inflateEnd=function(c){return l.blocks&&l.blocks.free(c),l.blocks=null,xt},l.inflateInit=function(c,f){return c.msg=null,l.blocks=null,f<8||f>15?(l.inflateEnd(c),ye):(l.wbits=f,c.istate.blocks=new _A(c,1<<f),u(c),xt)},l.inflate=function(c,f){let r,o;if(!c||!c.istate||!c.next_in)return ye;const h=c.istate;for(f=f==XA?xn:xt,r=xn;;)switch(h.mode){case t8:if(c.avail_in===0)return r;if(r=f,c.avail_in--,c.total_in++,((h.method=c.read_byte(c.next_in_index++))&15)!=$A){h.mode=ea,c.msg="unknown compression method",h.marker=5;break}if((h.method>>4)+8>h.wbits){h.mode=ea,c.msg="invalid win size",h.marker=5;break}h.mode=I1;case I1:if(c.avail_in===0)return r;if(r=f,c.avail_in--,c.total_in++,o=c.read_byte(c.next_in_index++)&255,((h.method<<8)+o)%31!==0){h.mode=ea,c.msg="incorrect header check",h.marker=5;break}if((o&PA)===0){h.mode=pi;break}h.mode=K1;case K1:if(c.avail_in===0)return r;r=f,c.avail_in--,c.total_in++,h.need=(c.read_byte(c.next_in_index++)&255)<<24&4278190080,h.mode=k1;case k1:if(c.avail_in===0)return r;r=f,c.avail_in--,c.total_in++,h.need+=(c.read_byte(c.next_in_index++)&255)<<16&16711680,h.mode=J1;case J1:if(c.avail_in===0)return r;r=f,c.avail_in--,c.total_in++,h.need+=(c.read_byte(c.next_in_index++)&255)<<8&65280,h.mode=F1;case F1:return c.avail_in===0?r:(r=f,c.avail_in--,c.total_in++,h.need+=c.read_byte(c.next_in_index++)&255,h.mode=Nf,LA);case Nf:return h.mode=ea,c.msg="need dictionary",h.marker=0,ye;case pi:if(r=h.blocks.proc(c,r),r==Ut){h.mode=ea,h.marker=0;break}if(r==xt&&(r=f),r!=bn)return r;r=f,h.blocks.reset(c,h.was),h.mode=W1;case W1:return c.avail_in=0,bn;case ea:return Ut;default:return ye}},l.inflateSetDictionary=function(c,f,r){let o=0,h=r;if(!c||!c.istate||c.istate.mode!=Nf)return ye;const v=c.istate;return h>=1<<v.wbits&&(h=(1<<v.wbits)-1,o=r-h),v.blocks.set_dictionary(f,o,h),v.mode=pi,xt},l.inflateSync=function(c){let f,r,o,h,v;if(!c||!c.istate)return ye;const y=c.istate;if(y.mode!=ea&&(y.mode=ea,y.marker=0),(f=c.avail_in)===0)return xn;for(r=c.next_in_index,o=y.marker;f!==0&&o<4;)c.read_byte(r)==e8[o]?o++:c.read_byte(r)!==0?o=0:o=4-o,r++,f--;return c.total_in+=r-c.next_in_index,c.next_in_index=r,c.avail_in=f,y.marker=o,o!=4?Ut:(h=c.total_in,v=c.total_out,u(c),c.total_in=h,c.total_out=v,y.mode=pi,xt)},l.inflateSyncPoint=function(c){return!c||!c.istate||!c.istate.blocks?ye:c.istate.blocks.sync_point()}}function G2(){}G2.prototype={inflateInit(l){const u=this;return u.istate=new n8,l||(l=YA),u.istate.inflateInit(u,l)},inflate(l){const u=this;return u.istate?u.istate.inflate(u,l):ye},inflateEnd(){const l=this;if(!l.istate)return ye;const u=l.istate.inflateEnd(l);return l.istate=null,u},inflateSync(){const l=this;return l.istate?l.istate.inflateSync(l):ye},inflateSetDictionary(l,u){const c=this;return c.istate?c.istate.inflateSetDictionary(c,l,u):ye},read_byte(l){return this.next_in[l]},read_buf(l,u){return this.next_in.subarray(l,l+u)}};function a8(l){const u=this,c=new G2,f=l&&l.chunkSize?Math.floor(l.chunkSize*2):128*1024,r=GA,o=new Uint8Array(f);let h=!1;c.inflateInit(),c.next_out=o,u.append=function(v,y){const A=[];let E,S,O=0,X=0,B=0;if(v.length!==0){c.next_in_index=0,c.next_in=v,c.avail_in=v.length;do{if(c.next_out_index=0,c.avail_out=f,c.avail_in===0&&!h&&(c.next_in_index=0,h=!0),E=c.inflate(r),h&&E===xn){if(c.avail_in!==0)throw new Error("inflating: bad input")}else if(E!==xt&&E!==bn)throw new Error("inflating: "+c.msg);if((h||E===bn)&&c.avail_in===v.length)throw new Error("inflating: bad input");c.next_out_index&&(c.next_out_index===f?A.push(new Uint8Array(o)):A.push(o.subarray(0,c.next_out_index))),B+=c.next_out_index,y&&c.next_in_index>0&&c.next_in_index!=O&&(y(c.next_in_index),O=c.next_in_index)}while(c.avail_in>0||c.avail_out===0);return A.length>1?(S=new Uint8Array(B),A.forEach(function(b){S.set(b,X),X+=b.length})):S=A[0]?new Uint8Array(A[0]):new Uint8Array,S}},u.flush=function(){c.inflateEnd()}}const ja=4294967295,la=65535,l8=8,i8=0,u8=99,c8=67324752,X2=134695760,s8=X2,_1=33639248,f8=101010256,P1=101075792,r8=117853008,pn=22,Bf=20,Uf=56,o8=12,d8=20,$1=4,h8=1,m8=39169,g8=10,A8=1,v8=21589,y8=28789,E8=25461,p8=6534,t2=1,b8=6,e2=8,n2=2048,a2=16,x8=61440,S8=16384,T8=73,l2="/",Qf=30,C8=10,O8=14,w8=18,Jt=void 0,sa="undefined",Mi="function";class i2{constructor(u){return class extends TransformStream{constructor(c,f){const r=new u(f);super({transform(o,h){h.enqueue(r.append(o))},flush(o){const h=r.flush();h&&o.enqueue(h)}})}}}}const R8=64;let V2=2;try{typeof navigator!=sa&&navigator.hardwareConcurrency&&(V2=navigator.hardwareConcurrency)}catch{}const D8={chunkSize:512*1024,maxWorkers:V2,terminateWorkerTimeout:5e3,useWebWorkers:!0,useCompressionStream:!0,workerScripts:Jt,CompressionStreamNative:typeof CompressionStream!=sa&&CompressionStream,DecompressionStreamNative:typeof DecompressionStream!=sa&&DecompressionStream},ia=Object.assign({},D8);function Z2(){return ia}function M8(l){return Math.max(l.chunkSize,R8)}function q2(l){const{baseURL:u,chunkSize:c,maxWorkers:f,terminateWorkerTimeout:r,useCompressionStream:o,useWebWorkers:h,Deflate:v,Inflate:y,CompressionStream:A,DecompressionStream:E,workerScripts:S}=l;if(na("baseURL",u),na("chunkSize",c),na("maxWorkers",f),na("terminateWorkerTimeout",r),na("useCompressionStream",o),na("useWebWorkers",h),v&&(ia.CompressionStream=new i2(v)),y&&(ia.DecompressionStream=new i2(y)),na("CompressionStream",A),na("DecompressionStream",E),S!==Jt){const{deflate:O,inflate:X}=S;if((O||X)&&(ia.workerScripts||(ia.workerScripts={})),O){if(!Array.isArray(O))throw new Error("workerScripts.deflate must be an array");ia.workerScripts.deflate=O}if(X){if(!Array.isArray(X))throw new Error("workerScripts.inflate must be an array");ia.workerScripts.inflate=X}}}function na(l,u){u!==Jt&&(ia[l]=u)}function j8(){return"application/octet-stream"}const I2=[];for(let l=0;l<256;l++){let u=l;for(let c=0;c<8;c++)u&1?u=u>>>1^3988292384:u=u>>>1;I2[l]=u}class cc{constructor(u){this.crc=u||-1}append(u){let c=this.crc|0;for(let f=0,r=u.length|0;f<r;f++)c=c>>>8^I2[(c^u[f])&255];this.crc=c}get(){return~this.crc}}class K2 extends TransformStream{constructor(){let u;const c=new cc;super({transform(f,r){c.append(f),r.enqueue(f)},flush(){const f=new Uint8Array(4);new DataView(f.buffer).setUint32(0,c.get()),u.value=f}}),u=this}}function H8(l){if(typeof TextEncoder==sa){l=unescape(encodeURIComponent(l));const u=new Uint8Array(l.length);for(let c=0;c<u.length;c++)u[c]=l.charCodeAt(c);return u}else return new TextEncoder().encode(l)}const re={concat(l,u){if(l.length===0||u.length===0)return l.concat(u);const c=l[l.length-1],f=re.getPartial(c);return f===32?l.concat(u):re._shiftRight(u,f,c|0,l.slice(0,l.length-1))},bitLength(l){const u=l.length;if(u===0)return 0;const c=l[u-1];return(u-1)*32+re.getPartial(c)},clamp(l,u){if(l.length*32<u)return l;l=l.slice(0,Math.ceil(u/32));const c=l.length;return u=u&31,c>0&&u&&(l[c-1]=re.partial(u,l[c-1]&2147483648>>u-1,1)),l},partial(l,u,c){return l===32?u:(c?u|0:u<<32-l)+l*1099511627776},getPartial(l){return Math.round(l/1099511627776)||32},_shiftRight(l,u,c,f){for(f===void 0&&(f=[]);u>=32;u-=32)f.push(c),c=0;if(u===0)return f.concat(l);for(let h=0;h<l.length;h++)f.push(c|l[h]>>>u),c=l[h]<<32-u;const r=l.length?l[l.length-1]:0,o=re.getPartial(r);return f.push(re.partial(u+o&31,u+o>32?c:f.pop(),1)),f}},sc={bytes:{fromBits(l){const c=re.bitLength(l)/8,f=new Uint8Array(c);let r;for(let o=0;o<c;o++)(o&3)===0&&(r=l[o/4]),f[o]=r>>>24,r<<=8;return f},toBits(l){const u=[];let c,f=0;for(c=0;c<l.length;c++)f=f<<8|l[c],(c&3)===3&&(u.push(f),f=0);return c&3&&u.push(re.partial(8*(c&3),f)),u}}},k2={};k2.sha1=class{constructor(l){const u=this;u.blockSize=512,u._init=[1732584193,4023233417,2562383102,271733878,3285377520],u._key=[1518500249,1859775393,2400959708,3395469782],l?(u._h=l._h.slice(0),u._buffer=l._buffer.slice(0),u._length=l._length):u.reset()}reset(){const l=this;return l._h=l._init.slice(0),l._buffer=[],l._length=0,l}update(l){const u=this;typeof l=="string"&&(l=sc.utf8String.toBits(l));const c=u._buffer=re.concat(u._buffer,l),f=u._length,r=u._length=f+re.bitLength(l);if(r>9007199254740991)throw new Error("Cannot hash more than 2^53 - 1 bits");const o=new Uint32Array(c);let h=0;for(let v=u.blockSize+f-(u.blockSize+f&u.blockSize-1);v<=r;v+=u.blockSize)u._block(o.subarray(16*h,16*(h+1))),h+=1;return c.splice(0,16*h),u}finalize(){const l=this;let u=l._buffer;const c=l._h;u=re.concat(u,[re.partial(1,1)]);for(let f=u.length+2;f&15;f++)u.push(0);for(u.push(Math.floor(l._length/4294967296)),u.push(l._length|0);u.length;)l._block(u.splice(0,16));return l.reset(),c}_f(l,u,c,f){if(l<=19)return u&c|~u&f;if(l<=39)return u^c^f;if(l<=59)return u&c|u&f|c&f;if(l<=79)return u^c^f}_S(l,u){return u<<l|u>>>32-l}_block(l){const u=this,c=u._h,f=Array(80);for(let A=0;A<16;A++)f[A]=l[A];let r=c[0],o=c[1],h=c[2],v=c[3],y=c[4];for(let A=0;A<=79;A++){A>=16&&(f[A]=u._S(1,f[A-3]^f[A-8]^f[A-14]^f[A-16]));const E=u._S(5,r)+u._f(A,o,h,v)+y+f[A]+u._key[Math.floor(A/20)]|0;y=v,v=h,h=u._S(30,o),o=r,r=E}c[0]=c[0]+r|0,c[1]=c[1]+o|0,c[2]=c[2]+h|0,c[3]=c[3]+v|0,c[4]=c[4]+y|0}};const J2={};J2.aes=class{constructor(l){const u=this;u._tables=[[[],[],[],[],[]],[[],[],[],[],[]]],u._tables[0][0][0]||u._precompute();const c=u._tables[0][4],f=u._tables[1],r=l.length;let o,h,v,y=1;if(r!==4&&r!==6&&r!==8)throw new Error("invalid aes key size");for(u._key=[h=l.slice(0),v=[]],o=r;o<4*r+28;o++){let A=h[o-1];(o%r===0||r===8&&o%r===4)&&(A=c[A>>>24]<<24^c[A>>16&255]<<16^c[A>>8&255]<<8^c[A&255],o%r===0&&(A=A<<8^A>>>24^y<<24,y=y<<1^(y>>7)*283)),h[o]=h[o-r]^A}for(let A=0;o;A++,o--){const E=h[A&3?o:o-4];o<=4||A<4?v[A]=E:v[A]=f[0][c[E>>>24]]^f[1][c[E>>16&255]]^f[2][c[E>>8&255]]^f[3][c[E&255]]}}encrypt(l){return this._crypt(l,0)}decrypt(l){return this._crypt(l,1)}_precompute(){const l=this._tables[0],u=this._tables[1],c=l[4],f=u[4],r=[],o=[];let h,v,y,A;for(let E=0;E<256;E++)o[(r[E]=E<<1^(E>>7)*283)^E]=E;for(let E=h=0;!c[E];E^=v||1,h=o[h]||1){let S=h^h<<1^h<<2^h<<3^h<<4;S=S>>8^S&255^99,c[E]=S,f[S]=E,A=r[y=r[v=r[E]]];let O=A*16843009^y*65537^v*257^E*16843008,X=r[S]*257^S*16843008;for(let B=0;B<4;B++)l[B][E]=X=X<<24^X>>>8,u[B][S]=O=O<<24^O>>>8}for(let E=0;E<5;E++)l[E]=l[E].slice(0),u[E]=u[E].slice(0)}_crypt(l,u){if(l.length!==4)throw new Error("invalid aes block size");const c=this._key[u],f=c.length/4-2,r=[0,0,0,0],o=this._tables[u],h=o[0],v=o[1],y=o[2],A=o[3],E=o[4];let S=l[0]^c[0],O=l[u?3:1]^c[1],X=l[2]^c[2],B=l[u?1:3]^c[3],b=4,p,x,R;for(let U=0;U<f;U++)p=h[S>>>24]^v[O>>16&255]^y[X>>8&255]^A[B&255]^c[b],x=h[O>>>24]^v[X>>16&255]^y[B>>8&255]^A[S&255]^c[b+1],R=h[X>>>24]^v[B>>16&255]^y[S>>8&255]^A[O&255]^c[b+2],B=h[B>>>24]^v[S>>16&255]^y[O>>8&255]^A[X&255]^c[b+3],b+=4,S=p,O=x,X=R;for(let U=0;U<4;U++)r[u?3&-U:U]=E[S>>>24]<<24^E[O>>16&255]<<16^E[X>>8&255]<<8^E[B&255]^c[b++],p=S,S=O,O=X,X=B,B=p;return r}};const N8={getRandomValues(l){const u=new Uint32Array(l.buffer),c=f=>{let r=987654321;const o=4294967295;return function(){return r=36969*(r&65535)+(r>>16)&o,f=18e3*(f&65535)+(f>>16)&o,(((r<<16)+f&o)/4294967296+.5)*(Math.random()>.5?1:-1)}};for(let f=0,r;f<l.length;f+=4){const o=c((r||Math.random())*4294967296);r=o()*987654071,u[f/4]=o()*4294967296|0}return l}},F2={};F2.ctrGladman=class{constructor(l,u){this._prf=l,this._initIv=u,this._iv=u}reset(){this._iv=this._initIv}update(l){return this.calculate(this._prf,l,this._iv)}incWord(l){if((l>>24&255)===255){let u=l>>16&255,c=l>>8&255,f=l&255;u===255?(u=0,c===255?(c=0,f===255?f=0:++f):++c):++u,l=0,l+=u<<16,l+=c<<8,l+=f}else l+=1<<24;return l}incCounter(l){(l[0]=this.incWord(l[0]))===0&&(l[1]=this.incWord(l[1]))}calculate(l,u,c){let f;if(!(f=u.length))return[];const r=re.bitLength(u);for(let o=0;o<f;o+=4){this.incCounter(c);const h=l.encrypt(c);u[o]^=h[0],u[o+1]^=h[1],u[o+2]^=h[2],u[o+3]^=h[3]}return re.clamp(u,r)}};const Ha={importKey(l){return new Ha.hmacSha1(sc.bytes.toBits(l))},pbkdf2(l,u,c,f){if(c=c||1e4,f<0||c<0)throw new Error("invalid params to pbkdf2");const r=(f>>5)+1<<2;let o,h,v,y,A;const E=new ArrayBuffer(r),S=new DataView(E);let O=0;const X=re;for(u=sc.bytes.toBits(u),A=1;O<(r||1);A++){for(o=h=l.encrypt(X.concat(u,[A])),v=1;v<c;v++)for(h=l.encrypt(h),y=0;y<h.length;y++)o[y]^=h[y];for(v=0;O<(r||1)&&v<o.length;v++)S.setInt32(O,o[v]),O+=4}return E.slice(0,f/8)}};Ha.hmacSha1=class{constructor(l){const u=this,c=u._hash=k2.sha1,f=[[],[]];u._baseHash=[new c,new c];const r=u._baseHash[0].blockSize/32;l.length>r&&(l=new c().update(l).finalize());for(let o=0;o<r;o++)f[0][o]=l[o]^909522486,f[1][o]=l[o]^1549556828;u._baseHash[0].update(f[0]),u._baseHash[1].update(f[1]),u._resultHash=new c(u._baseHash[0])}reset(){const l=this;l._resultHash=new l._hash(l._baseHash[0]),l._updated=!1}update(l){const u=this;u._updated=!0,u._resultHash.update(l)}digest(){const l=this,u=l._resultHash.finalize(),c=new l._hash(l._baseHash[1]).update(u).finalize();return l.reset(),c}encrypt(l){if(this._updated)throw new Error("encrypt on already updated hmac called!");return this.update(l),this.digest(l)}};const B8=typeof crypto!=sa&&typeof crypto.getRandomValues==Mi,rr="Invalid password",or="Invalid signature",dr="zipjs-abort-check-password";function W2(l){return B8?crypto.getRandomValues(l):N8.getRandomValues(l)}const xl=16,U8="raw",_2={name:"PBKDF2"},Q8={name:"HMAC"},z8="SHA-1",Y8=Object.assign({hash:Q8},_2),_f=Object.assign({iterations:1e3,hash:{name:z8}},_2),L8=["deriveBits"],Ci=[8,12,16],bi=[16,24,32],aa=10,G8=[0,0,0,0],oc=typeof crypto!=sa,ji=oc&&crypto.subtle,P2=oc&&typeof ji!=sa,Pe=sc.bytes,X8=J2.aes,V8=F2.ctrGladman,Z8=Ha.hmacSha1;let u2=oc&&P2&&typeof ji.importKey==Mi,c2=oc&&P2&&typeof ji.deriveBits==Mi;class q8 extends TransformStream{constructor({password:u,rawPassword:c,signed:f,encryptionStrength:r,checkPasswordOnly:o}){super({start(){Object.assign(this,{ready:new Promise(h=>this.resolveReady=h),password:eh(u,c),signed:f,strength:r-1,pending:new Uint8Array})},async transform(h,v){const y=this,{password:A,strength:E,resolveReady:S,ready:O}=y;A?(await K8(y,E,A,Xe(h,0,Ci[E]+2)),h=Xe(h,Ci[E]+2),o?v.error(new Error(dr)):S()):await O;const X=new Uint8Array(h.length-aa-(h.length-aa)%xl);v.enqueue($2(y,h,X,0,aa,!0))},async flush(h){const{signed:v,ctr:y,hmac:A,pending:E,ready:S}=this;if(A&&y){await S;const O=Xe(E,0,E.length-aa),X=Xe(E,E.length-aa);let B=new Uint8Array;if(O.length){const b=wi(Pe,O);A.update(b);const p=y.update(b);B=Oi(Pe,p)}if(v){const b=Xe(Oi(Pe,A.digest()),0,aa);for(let p=0;p<aa;p++)if(b[p]!=X[p])throw new Error(or)}h.enqueue(B)}}})}}class I8 extends TransformStream{constructor({password:u,rawPassword:c,encryptionStrength:f}){let r;super({start(){Object.assign(this,{ready:new Promise(o=>this.resolveReady=o),password:eh(u,c),strength:f-1,pending:new Uint8Array})},async transform(o,h){const v=this,{password:y,strength:A,resolveReady:E,ready:S}=v;let O=new Uint8Array;y?(O=await k8(v,A,y),E()):await S;const X=new Uint8Array(O.length+o.length-o.length%xl);X.set(O,0),h.enqueue($2(v,o,X,O.length,0))},async flush(o){const{ctr:h,hmac:v,pending:y,ready:A}=this;if(v&&h){await A;let E=new Uint8Array;if(y.length){const S=h.update(wi(Pe,y));v.update(S),E=Oi(Pe,S)}r.signature=Oi(Pe,v.digest()).slice(0,aa),o.enqueue(hr(E,r.signature))}}}),r=this}}function $2(l,u,c,f,r,o){const{ctr:h,hmac:v,pending:y}=l,A=u.length-r;y.length&&(u=hr(y,u),c=W8(c,A-A%xl));let E;for(E=0;E<=A-xl;E+=xl){const S=wi(Pe,Xe(u,E,E+xl));o&&v.update(S);const O=h.update(S);o||v.update(O),c.set(Oi(Pe,O),E+f)}return l.pending=Xe(u,E),c}async function K8(l,u,c,f){const r=await th(l,u,c,Xe(f,0,Ci[u])),o=Xe(f,Ci[u]);if(r[0]!=o[0]||r[1]!=o[1])throw new Error(rr)}async function k8(l,u,c){const f=W2(new Uint8Array(Ci[u])),r=await th(l,u,c,f);return hr(f,r)}async function th(l,u,c,f){l.password=null;const r=await J8(U8,c,Y8,!1,L8),o=await F8(Object.assign({salt:f},_f),r,8*(bi[u]*2+2)),h=new Uint8Array(o),v=wi(Pe,Xe(h,0,bi[u])),y=wi(Pe,Xe(h,bi[u],bi[u]*2)),A=Xe(h,bi[u]*2);return Object.assign(l,{keys:{key:v,authentication:y,passwordVerification:A},ctr:new V8(new X8(v),Array.from(G8)),hmac:new Z8(y)}),A}async function J8(l,u,c,f,r){if(u2)try{return await ji.importKey(l,u,c,f,r)}catch{return u2=!1,Ha.importKey(u)}else return Ha.importKey(u)}async function F8(l,u,c){if(c2)try{return await ji.deriveBits(l,u,c)}catch{return c2=!1,Ha.pbkdf2(u,l.salt,_f.iterations,c)}else return Ha.pbkdf2(u,l.salt,_f.iterations,c)}function eh(l,u){return u===Jt?H8(l):u}function hr(l,u){let c=l;return l.length+u.length&&(c=new Uint8Array(l.length+u.length),c.set(l,0),c.set(u,l.length)),c}function W8(l,u){if(u&&u>l.length){const c=l;l=new Uint8Array(u),l.set(c,0)}return l}function Xe(l,u,c){return l.subarray(u,c)}function Oi(l,u){return l.fromBits(u)}function wi(l,u){return l.toBits(u)}const Ti=12;class _8 extends TransformStream{constructor({password:u,passwordVerification:c,checkPasswordOnly:f}){super({start(){Object.assign(this,{password:u,passwordVerification:c}),nh(this,u)},transform(r,o){const h=this;if(h.password){const v=s2(h,r.subarray(0,Ti));if(h.password=null,v.at(-1)!=h.passwordVerification)throw new Error(rr);r=r.subarray(Ti)}f?o.error(new Error(dr)):o.enqueue(s2(h,r))}})}}class P8 extends TransformStream{constructor({password:u,passwordVerification:c}){super({start(){Object.assign(this,{password:u,passwordVerification:c}),nh(this,u)},transform(f,r){const o=this;let h,v;if(o.password){o.password=null;const y=W2(new Uint8Array(Ti));y[Ti-1]=o.passwordVerification,h=new Uint8Array(f.length+y.length),h.set(f2(o,y),0),v=Ti}else h=new Uint8Array(f.length),v=0;h.set(f2(o,f),v),r.enqueue(h)}})}}function s2(l,u){const c=new Uint8Array(u.length);for(let f=0;f<u.length;f++)c[f]=ah(l)^u[f],mr(l,c[f]);return c}function f2(l,u){const c=new Uint8Array(u.length);for(let f=0;f<u.length;f++)c[f]=ah(l)^u[f],mr(l,u[f]);return c}function nh(l,u){const c=[305419896,591751049,878082192];Object.assign(l,{keys:c,crcKey0:new cc(c[0]),crcKey2:new cc(c[2])});for(let f=0;f<u.length;f++)mr(l,u.charCodeAt(f))}function mr(l,u){let[c,f,r]=l.keys;l.crcKey0.append([u]),c=~l.crcKey0.get(),f=r2(Math.imul(r2(f+lh(c)),134775813)+1),l.crcKey2.append([f>>>24]),r=~l.crcKey2.get(),l.keys=[c,f,r]}function ah(l){const u=l.keys[2]|2;return lh(Math.imul(u,u^1)>>>8)}function lh(l){return l&255}function r2(l){return l&4294967295}const gr="Invalid uncompressed size",o2="deflate-raw";class $8 extends TransformStream{constructor(u,{chunkSize:c,CompressionStream:f,CompressionStreamNative:r}){super({});const{compressed:o,encrypted:h,useCompressionStream:v,zipCrypto:y,signed:A,level:E}=u,S=this;let O,X,B=super.readable;(!h||y)&&A&&(O=new K2,B=Sn(B,O)),o&&(B=uh(B,v,{level:E,chunkSize:c},r,f)),h&&(y?B=Sn(B,new P8(u)):(X=new I8(u),B=Sn(B,X))),ih(S,B,()=>{let b;h&&!y&&(b=X.signature),(!h||y)&&A&&(b=new DataView(O.value.buffer).getUint32(0)),S.signature=b})}}class t3 extends TransformStream{constructor(u,{chunkSize:c,DecompressionStream:f,DecompressionStreamNative:r}){super({});const{zipCrypto:o,encrypted:h,signed:v,signature:y,compressed:A,useCompressionStream:E}=u;let S,O,X=super.readable;h&&(o?X=Sn(X,new _8(u)):(O=new q8(u),X=Sn(X,O))),A&&(X=uh(X,E,{chunkSize:c},r,f)),(!h||o)&&v&&(S=new K2,X=Sn(X,S)),ih(this,X,()=>{if((!h||o)&&v){const B=new DataView(S.value.buffer);if(y!=B.getUint32(0,!1))throw new Error(or)}})}}function ih(l,u,c){u=Sn(u,new TransformStream({flush:c})),Object.defineProperty(l,"readable",{get(){return u}})}function uh(l,u,c,f,r){try{const o=u&&f?f:r;l=Sn(l,new o(o2,c))}catch(o){if(u)l=Sn(l,new r(o2,c));else throw o}return l}function Sn(l,u){return l.pipeThrough(u)}const e3="message",n3="start",a3="pull",d2="data",l3="ack",h2="close",i3="deflate",ch="inflate";class u3 extends TransformStream{constructor(u,c){super({});const f=this,{codecType:r}=u;let o;r.startsWith(i3)?o=$8:r.startsWith(ch)&&(o=t3),f.outputSize=0;let h=0;const v=new o(u,c),y=super.readable,A=new TransformStream({transform(S,O){S&&S.length&&(h+=S.length,O.enqueue(S))},flush(){Object.assign(f,{inputSize:h})}}),E=new TransformStream({transform(S,O){if(S&&S.length&&(O.enqueue(S),f.outputSize+=S.length,u.outputSize&&f.outputSize>u.outputSize))throw new Error(gr)},flush(){const{signature:S}=v;Object.assign(f,{signature:S,inputSize:h})}});Object.defineProperty(f,"readable",{get(){return y.pipeThrough(A).pipeThrough(v).pipeThrough(E)}})}}class c3 extends TransformStream{constructor(u){let c;super({transform:f,flush(r){c&&c.length&&r.enqueue(c)}});function f(r,o){if(c){const h=new Uint8Array(c.length+r.length);h.set(c),h.set(r,c.length),r=h,c=null}r.length>u?(o.enqueue(r.slice(0,u)),f(r.slice(u),o)):c=r}}}let sh=typeof Worker!=sa;class zf{constructor(u,{readable:c,writable:f},{options:r,config:o,streamOptions:h,useWebWorkers:v,transferStreams:y,scripts:A},E){const{signal:S}=h;return Object.assign(u,{busy:!0,readable:c.pipeThrough(new c3(o.chunkSize)).pipeThrough(new s3(h),{signal:S}),writable:f,options:Object.assign({},r),scripts:A,transferStreams:y,terminate(){return new Promise(O=>{const{worker:X,busy:B}=u;X?(B?u.resolveTerminated=O:(X.terminate(),O()),u.interface=null):O()})},onTaskFinished(){const{resolveTerminated:O}=u;O&&(u.resolveTerminated=null,u.terminated=!0,u.worker.terminate(),O()),u.busy=!1,E(u)}}),(v&&sh?f3:fh)(u,o)}}class s3 extends TransformStream{constructor({onstart:u,onprogress:c,size:f,onend:r}){let o=0;super({async start(){u&&await Yf(u,f)},async transform(h,v){o+=h.length,c&&await Yf(c,o,f),v.enqueue(h)},async flush(){r&&await Yf(r,o)}})}}async function Yf(l,...u){try{await l(...u)}catch{}}function fh(l,u){return{run:()=>r3(l,u)}}function f3(l,u){const{baseURL:c,chunkSize:f}=u;if(!l.interface){let r;try{r=h3(l.scripts[0],c,l)}catch{return sh=!1,fh(l,u)}Object.assign(l,{worker:r,interface:{run:()=>o3(l,{chunkSize:f})}})}return l.interface}async function r3({options:l,readable:u,writable:c,onTaskFinished:f},r){let o;try{o=new u3(l,r),await u.pipeThrough(o).pipeTo(c,{preventClose:!0,preventAbort:!0});const{signature:h,inputSize:v,outputSize:y}=o;return{signature:h,inputSize:v,outputSize:y}}catch(h){throw o&&(h.outputSize=o.outputSize),h}finally{f()}}async function o3(l,u){let c,f;const r=new Promise((O,X)=>{c=O,f=X});Object.assign(l,{reader:null,writer:null,resolveResult:c,rejectResult:f,result:r});const{readable:o,options:h,scripts:v}=l,{writable:y,closed:A}=d3(l.writable),E=lc({type:n3,scripts:v.slice(1),options:h,config:u,readable:o,writable:y},l);E||Object.assign(l,{reader:o.getReader(),writer:y.getWriter()});const S=await r;return E||await y.getWriter().close(),await A,S}function d3(l){let u;const c=new Promise(r=>u=r);return{writable:new WritableStream({async write(r){const o=l.getWriter();await o.ready,await o.write(r),o.releaseLock()},close(){u()},abort(r){return l.getWriter().abort(r)}}),closed:c}}let m2=!0,g2=!0;function h3(l,u,c){const f={type:"module"};let r,o;typeof l==Mi&&(l=l());try{r=new URL(l,u)}catch{r=l}if(m2)try{o=new Worker(r)}catch{m2=!1,o=new Worker(r,f)}else o=new Worker(r,f);return o.addEventListener(e3,h=>m3(h,c)),o}function lc(l,{worker:u,writer:c,onTaskFinished:f,transferStreams:r}){try{const{value:o,readable:h,writable:v}=l,y=[];if(o&&(o.byteLength<o.buffer.byteLength?l.value=o.buffer.slice(0,o.byteLength):l.value=o.buffer,y.push(l.value)),r&&g2?(h&&y.push(h),v&&y.push(v)):l.readable=l.writable=null,y.length)try{return u.postMessage(l,y),!0}catch{g2=!1,l.readable=l.writable=null,u.postMessage(l)}else u.postMessage(l)}catch(o){throw c&&c.releaseLock(),f(),o}}async function m3({data:l},u){const{type:c,value:f,messageId:r,result:o,error:h}=l,{reader:v,writer:y,resolveResult:A,rejectResult:E,onTaskFinished:S}=u;try{if(h){const{message:X,stack:B,code:b,name:p,outputSize:x}=h,R=new Error(X);Object.assign(R,{stack:B,code:b,name:p,outputSize:x}),O(R)}else{if(c==a3){const{value:X,done:B}=await v.read();lc({type:d2,value:X,done:B,messageId:r},u)}c==d2&&(await y.ready,await y.write(new Uint8Array(f)),lc({type:l3,messageId:r},u)),c==h2&&O(null,o)}}catch(X){lc({type:h2,messageId:r},u),O(X)}function O(X,B){X?E(X):A(B),y&&y.releaseLock(),S()}}let ua=[];const Lf=[];let A2=0;async function g3(l,u){const{options:c,config:f}=u,{transferStreams:r,useWebWorkers:o,useCompressionStream:h,codecType:v,compressed:y,signed:A,encrypted:E}=c,{workerScripts:S,maxWorkers:O}=f;u.transferStreams=r||r===Jt;const X=!y&&!A&&!E&&!u.transferStreams;return u.useWebWorkers=!X&&(o||o===Jt&&f.useWebWorkers),u.scripts=u.useWebWorkers&&S?S[v]:[],c.useCompressionStream=h||h===Jt&&f.useCompressionStream,(await B()).run();async function B(){const p=ua.find(x=>!x.busy);if(p)return Pf(p),new zf(p,l,u,b);if(ua.length<O){const x={indexWorker:A2};return A2++,ua.push(x),new zf(x,l,u,b)}else return new Promise(x=>Lf.push({resolve:x,stream:l,workerOptions:u}))}function b(p){if(Lf.length){const[{resolve:x,stream:R,workerOptions:U}]=Lf.splice(0,1);x(new zf(p,R,U,b))}else p.worker?(Pf(p),A3(p,u)):ua=ua.filter(x=>x!=p)}}function A3(l,u){const{config:c}=u,{terminateWorkerTimeout:f}=c;Number.isFinite(f)&&f>=0&&(l.terminated?l.terminated=!1:l.terminateTimeout=setTimeout(async()=>{ua=ua.filter(r=>r!=l);try{await l.terminate()}catch{}},f))}function Pf(l){const{terminateTimeout:u}=l;u&&(clearTimeout(u),l.terminateTimeout=null)}async function v3(){await Promise.allSettled(ua.map(l=>(Pf(l),l.terminate())))}const rh="HTTP error ",Hi="HTTP Range not supported",oh="Writer iterator completed too soon",dh="Writer not initialized",y3="text/plain",E3="Content-Length",p3="Content-Range",b3="Accept-Ranges",x3="Range",S3="Content-Type",T3="HEAD",Ar="GET",hh="bytes",C3=64*1024,vr="writable";class dc{constructor(){this.size=0}init(){this.initialized=!0}}class fa extends dc{get readable(){const u=this,{chunkSize:c=C3}=u,f=new ReadableStream({start(){this.chunkOffset=0},async pull(r){const{offset:o=0,size:h,diskNumberStart:v}=f,{chunkOffset:y}=this,A=h===Jt?c:Math.min(c,h-y),E=await Pt(u,o+y,A,v);r.enqueue(E),y+c>h||h===Jt&&!E.length&&A?r.close():this.chunkOffset+=c}});return f}}class yr extends dc{constructor(){super();const u=this,c=new WritableStream({write(f){if(!u.initialized)throw new Error(dh);return u.writeUint8Array(f)}});Object.defineProperty(u,vr,{get(){return c}})}writeUint8Array(){}}class O3 extends fa{constructor(u){super();let c=u.length;for(;u.charAt(c-1)=="=";)c--;const f=u.indexOf(",")+1;Object.assign(this,{dataURI:u,dataStart:f,size:Math.floor((c-f)*.75)})}readUint8Array(u,c){const{dataStart:f,dataURI:r}=this,o=new Uint8Array(c),h=Math.floor(u/3)*4,v=atob(r.substring(h+f,Math.ceil((u+c)/3)*4+f)),y=u-Math.floor(h/4)*3;let A=0;for(let E=y;E<y+c&&E<v.length;E++)o[E-y]=v.charCodeAt(E),A++;return A<o.length?o.subarray(0,A):o}}class w3 extends yr{constructor(u){super(),Object.assign(this,{data:"data:"+(u||"")+";base64,",pending:[]})}writeUint8Array(u){const c=this;let f=0,r=c.pending;const o=c.pending.length;for(c.pending="",f=0;f<Math.floor((o+u.length)/3)*3-o;f++)r+=String.fromCharCode(u[f]);for(;f<u.length;f++)c.pending+=String.fromCharCode(u[f]);r.length&&(r.length>2?c.data+=btoa(r):c.pending+=r)}getData(){return this.data+btoa(this.pending)}}class Er extends fa{constructor(u){super(),Object.assign(this,{blob:u,size:u.size})}async readUint8Array(u,c){const f=this,r=u+c;let h=await(u||r<f.size?f.blob.slice(u,r):f.blob).arrayBuffer();return h.byteLength>c&&(h=h.slice(u,r)),new Uint8Array(h)}}class mh extends dc{constructor(u){super();const c=this,f=new TransformStream,r=[];u&&r.push([S3,u]),Object.defineProperty(c,vr,{get(){return f.writable}}),c.blob=new Response(f.readable,{headers:r}).blob()}getData(){return this.blob}}class R3 extends Er{constructor(u){super(new Blob([u],{type:y3}))}}class D3 extends mh{constructor(u){super(u),Object.assign(this,{encoding:u,utf8:!u||u.toLowerCase()=="utf-8"})}async getData(){const{encoding:u,utf8:c}=this,f=await super.getData();if(f.text&&c)return f.text();{const r=new FileReader;return new Promise((o,h)=>{Object.assign(r,{onload:({target:v})=>o(v.result),onerror:()=>h(r.error)}),r.readAsText(f,u)})}}}class M3 extends fa{constructor(u,c){super(),gh(this,u,c)}async init(){await Ah(this,$f,v2),super.init()}readUint8Array(u,c){return vh(this,u,c,$f,v2)}}class j3 extends fa{constructor(u,c){super(),gh(this,u,c)}async init(){await Ah(this,tr,y2),super.init()}readUint8Array(u,c){return vh(this,u,c,tr,y2)}}function gh(l,u,c){const{preventHeadRequest:f,useRangeHeader:r,forceRangeRequests:o,combineSizeEocd:h}=c;c=Object.assign({},c),delete c.preventHeadRequest,delete c.useRangeHeader,delete c.forceRangeRequests,delete c.combineSizeEocd,delete c.useXHR,Object.assign(l,{url:u,options:c,preventHeadRequest:f,useRangeHeader:r,forceRangeRequests:o,combineSizeEocd:h})}async function Ah(l,u,c){const{url:f,preventHeadRequest:r,useRangeHeader:o,forceRangeRequests:h,combineSizeEocd:v}=l;if(U3(f)&&(o||h)&&(typeof r>"u"||r)){const y=await u(Ar,l,yh(l,v?-pn:void 0));if(!h&&y.headers.get(b3)!=hh)throw new Error(Hi);{v&&(l.eocdCache=new Uint8Array(await y.arrayBuffer()));let A;const E=y.headers.get(p3);if(E){const S=E.trim().split(/\s*\/\s*/);if(S.length){const O=S[1];O&&O!="*"&&(A=Number(O))}}A===Jt?await E2(l,u,c):l.size=A}}else await E2(l,u,c)}async function vh(l,u,c,f,r){const{useRangeHeader:o,forceRangeRequests:h,eocdCache:v,size:y,options:A}=l;if(o||h){if(v&&u==y-pn&&c==pn)return v;if(u>=y)return new Uint8Array;{u+c>y&&(c=y-u);const E=await f(Ar,l,yh(l,u,c));if(E.status!=206)throw new Error(Hi);return new Uint8Array(await E.arrayBuffer())}}else{const{data:E}=l;return E||await r(l,A),new Uint8Array(l.data.subarray(u,u+c))}}function yh(l,u=0,c=1){return Object.assign({},pr(l),{[x3]:hh+"="+(u<0?u:u+"-"+(u+c-1))})}function pr({options:l}){const{headers:u}=l;if(u)return Symbol.iterator in u?Object.fromEntries(u):u}async function v2(l){await Eh(l,$f)}async function y2(l){await Eh(l,tr)}async function Eh(l,u){const c=await u(Ar,l,pr(l));l.data=new Uint8Array(await c.arrayBuffer()),l.size||(l.size=l.data.length)}async function E2(l,u,c){if(l.preventHeadRequest)await c(l,l.options);else{const r=(await u(T3,l,pr(l))).headers.get(E3);r?l.size=Number(r):await c(l,l.options)}}async function $f(l,{options:u,url:c},f){const r=await fetch(c,Object.assign({},u,{method:l,headers:f}));if(r.status<400)return r;throw r.status==416?new Error(Hi):new Error(rh+(r.statusText||r.status))}function tr(l,{url:u},c){return new Promise((f,r)=>{const o=new XMLHttpRequest;if(o.addEventListener("load",()=>{if(o.status<400){const h=[];o.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach(v=>{const y=v.trim().split(/\s*:\s*/);y[0]=y[0].trim().replace(/^[a-z]|-[a-z]/g,A=>A.toUpperCase()),h.push(y)}),f({status:o.status,arrayBuffer:()=>o.response,headers:new Map(h)})}else r(o.status==416?new Error(Hi):new Error(rh+(o.statusText||o.status)))},!1),o.addEventListener("error",h=>r(h.detail?h.detail.error:new Error("Network error")),!1),o.open(l,u),c)for(const h of Object.entries(c))o.setRequestHeader(h[0],h[1]);o.responseType="arraybuffer",o.send()})}class ph extends fa{constructor(u,c={}){super(),Object.assign(this,{url:u,reader:c.useXHR?new j3(u,c):new M3(u,c)})}set size(u){}get size(){return this.reader.size}async init(){await this.reader.init(),super.init()}readUint8Array(u,c){return this.reader.readUint8Array(u,c)}}class H3 extends ph{constructor(u,c={}){c.useRangeHeader=!0,super(u,c)}}class N3 extends fa{constructor(u){super(),u=new Uint8Array(u.buffer,u.byteOffset,u.byteLength),Object.assign(this,{array:u,size:u.length})}readUint8Array(u,c){return this.array.slice(u,u+c)}}class B3 extends yr{init(u=0){Object.assign(this,{offset:0,array:new Uint8Array(u)}),super.init()}writeUint8Array(u){const c=this;if(c.offset+u.length>c.array.length){const f=c.array;c.array=new Uint8Array(f.length+u.length),c.array.set(f)}c.array.set(u,c.offset),c.offset+=u.length}getData(){return this.array}}class br extends fa{constructor(u){super(),this.readers=u}async init(){const u=this,{readers:c}=u;u.lastDiskNumber=0,u.lastDiskOffset=0,await Promise.all(c.map(async(f,r)=>{await f.init(),r!=c.length-1&&(u.lastDiskOffset+=f.size),u.size+=f.size})),super.init()}async readUint8Array(u,c,f=0){const r=this,{readers:o}=this;let h,v=f;v==-1&&(v=o.length-1);let y=u;for(;o[v]&&y>=o[v].size;)y-=o[v].size,v++;const A=o[v];if(A){const E=A.size;if(y+c<=E)h=await Pt(A,y,c);else{const S=E-y;h=new Uint8Array(c);const O=await Pt(A,y,S);h.set(O,0);const X=await r.readUint8Array(u+S,c-S,f);h.set(X,S),O.length+X.length<c&&(h=h.subarray(0,O.length+X.length))}}else h=new Uint8Array;return r.lastDiskNumber=Math.max(v,r.lastDiskNumber),h}}class fc extends dc{constructor(u,c=4294967295){super();const f=this;Object.assign(f,{diskNumber:0,diskOffset:0,size:0,maxSize:c,availableSize:c});let r,o,h;const v=new WritableStream({async write(E){const{availableSize:S}=f;if(h)E.length>=S?(await y(E.subarray(0,S)),await A(),f.diskOffset+=r.size,f.diskNumber++,h=null,await this.write(E.subarray(S))):await y(E);else{const{value:O,done:X}=await u.next();if(X&&!O)throw new Error(oh);r=O,r.size=0,r.maxSize&&(f.maxSize=r.maxSize),f.availableSize=f.maxSize,await Ri(r),o=O.writable,h=o.getWriter(),await this.write(E)}},async close(){await h.ready,await A()}});Object.defineProperty(f,vr,{get(){return v}});async function y(E){const S=E.length;S&&(await h.ready,await h.write(E),r.size+=S,f.size+=S,f.availableSize-=S)}async function A(){await h.close()}}}class bh{constructor(u){return Array.isArray(u)&&(u=new br(u)),u instanceof ReadableStream&&(u={readable:u}),u}}class xh{constructor(u){return u.writable===Jt&&typeof u.next==Mi&&(u=new fc(u)),u instanceof WritableStream&&(u={writable:u}),u.size===Jt&&(u.size=0),u instanceof fc||Object.assign(u,{diskNumber:0,diskOffset:0,availableSize:1/0,maxSize:1/0}),u}}function U3(l){const{baseURL:u}=Z2(),{protocol:c}=new URL(l,u);return c=="http:"||c=="https:"}async function Ri(l,u){if(l.init&&!l.initialized)await l.init(u);else return Promise.resolve()}function Pt(l,u,c,f){return l.readUint8Array(u,c,f)}const Q3=br,z3=fc,Sh="\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split(""),Y3=Sh.length==256;function L3(l){if(Y3){let u="";for(let c=0;c<l.length;c++)u+=Sh[l[c]];return u}else return new TextDecoder().decode(l)}function ic(l,u){return u&&u.trim().toLowerCase()=="cp437"?L3(l):new TextDecoder(u).decode(l)}const Th="filename",Ch="rawFilename",Oh="comment",wh="rawComment",Rh="uncompressedSize",Dh="compressedSize",Mh="offset",er="diskNumberStart",nr="lastModDate",ar="rawLastModDate",jh="lastAccessDate",G3="rawLastAccessDate",Hh="creationDate",X3="rawCreationDate",V3="internalFileAttribute",Z3="internalFileAttributes",q3="externalFileAttribute",I3="externalFileAttributes",K3="msDosCompatible",k3="zip64",J3="encrypted",F3="version",W3="versionMadeBy",_3="zipCrypto",P3="directory",$3="executable",t5="compressionMethod",e5="signature",n5="extraField",a5=[Th,Ch,Dh,Rh,nr,ar,Oh,wh,jh,Hh,Mh,er,er,V3,Z3,q3,I3,K3,k3,J3,F3,W3,_3,P3,$3,t5,e5,n5,"bitFlag","filenameUTF8","commentUTF8","rawExtraField","extraFieldZip64","extraFieldUnicodePath","extraFieldUnicodeComment","extraFieldAES","extraFieldNTFS","extraFieldExtendedTimestamp"];class p2{constructor(u){a5.forEach(c=>this[c]=u[c])}}const l5="filenameEncoding",i5="commentEncoding",u5="decodeText",c5="extractPrependedData",s5="extractAppendedData",f5="password",r5="rawPassword",o5="passThrough",d5="signal",h5="checkPasswordOnly",m5="checkOverlappingEntryOnly",g5="checkOverlappingEntry",A5="checkSignature",v5="useWebWorkers",y5="useCompressionStream",E5="transferStreams",p5="preventClose",uc="File format is not recognized",Nh="End of central directory not found",Bh="End of Zip64 central directory locator not found",Uh="Central directory header not found",Qh="Local file header not found",zh="Zip64 extra field not found",Yh="File contains encrypted entry",Lh="Encryption method not supported",lr="Compression method not supported",ir="Split zip file",Gh="Overlapping entry found",b2="utf-8",x2="cp437",b5=[[Rh,ja],[Dh,ja],[Mh,ja],[er,la]],x5={[la]:{getValue:Bt,bytes:4},[ja]:{getValue:Tl,bytes:8}};class Xh{constructor(u,c={}){Object.assign(this,{reader:new bh(u),options:c,config:Z2(),readRanges:[]})}async*getEntriesGenerator(u={}){const c=this;let{reader:f}=c;const{config:r}=c;if(await Ri(f),(f.size===Jt||!f.readUint8Array)&&(f=new Er(await new Response(f.readable).blob()),await Ri(f)),f.size<pn)throw new Error(uc);f.chunkSize=M8(r);const o=await M5(f,f8,f.size,pn,la*16);if(!o){const J=await Pt(f,0,4),k=Yt(J);throw Bt(k)==X2?new Error(ir):new Error(Nh)}const h=Yt(o);let v=Bt(h,12),y=Bt(h,16);const A=o.offset,E=$t(h,20),S=A+pn+E;let O=$t(h,4);const X=f.lastDiskNumber||0;let B=$t(h,6),b=$t(h,8),p=0,x=0;if(y==ja||v==ja||b==la||B==la){const J=await Pt(f,o.offset-Bf,Bf),k=Yt(J);if(Bt(k,0)==r8){y=Tl(k,8);let nt=await Pt(f,y,Uf,-1),P=Yt(nt);const st=o.offset-Bf-Uf;if(Bt(P,0)!=P1&&y!=st){const it=y;y=st,y>it&&(p=y-it),nt=await Pt(f,y,Uf,-1),P=Yt(nt)}if(Bt(P,0)!=P1)throw new Error(Bh);O==la&&(O=Bt(P,16)),B==la&&(B=Bt(P,20)),b==la&&(b=Tl(P,32)),v==ja&&(v=Tl(P,40)),y-=v}}if(y>=f.size&&(p=f.size-y-v-pn,y=f.size-v-pn),X!=O)throw new Error(ir);if(y<0)throw new Error(uc);let R=0,U=await Pt(f,y,v,B),Z=Yt(U);if(v){const J=o.offset-v;if(Bt(Z,R)!=_1&&y!=J){const k=y;y=J,y>k&&(p+=y-k),U=await Pt(f,y,v,B),Z=Yt(U)}}const F=o.offset-y-(f.lastDiskOffset||0);if(v!=F&&F>=0&&(v=F,U=await Pt(f,y,v,B),Z=Yt(U)),y<0||y>=f.size)throw new Error(uc);const j=ie(c,u,l5),D=ie(c,u,i5);for(let J=0;J<b;J++){const k=new T5(f,r,c.options);if(Bt(Z,R)!=_1)throw new Error(Uh);Vh(k,Z,R+6);const nt=!!k.bitFlag.languageEncodingFlag,P=R+46,st=P+k.filenameLength,it=st+k.extraFieldLength,H=$t(Z,R+4),_=H>>8==0,$=H>>8==3,ht=U.subarray(P,st),tt=$t(Z,R+32),C=it+tt,L=U.subarray(it,C),W=nt,et=nt,rt=Bt(Z,R+38),ot=_&&(Sl(Z,R+38)&a2)==a2||$&&(rt>>16&x8)==S8||ht.length&&ht.at(-1)==l2.charCodeAt(0),gt=$&&(rt>>16&T8)!=0,Ft=Bt(Z,R+42)+p;Object.assign(k,{versionMadeBy:H,msDosCompatible:_,compressedSize:0,uncompressedSize:0,commentLength:tt,directory:ot,offset:Ft,diskNumberStart:$t(Z,R+34),internalFileAttributes:$t(Z,R+36),externalFileAttributes:rt,rawFilename:ht,filenameUTF8:W,commentUTF8:et,rawExtraField:U.subarray(st,it),executable:gt}),k.internalFileAttribute=k.internalFileAttributes,k.externalFileAttribute=k.externalFileAttributes;const Qt=ie(c,u,u5)||ic,On=W?b2:j||x2,ra=et?b2:D||x2;let wn=Qt(ht,On);wn===Jt&&(wn=ic(ht,On));let Ba=Qt(L,ra);Ba===Jt&&(Ba=ic(L,ra)),Object.assign(k,{rawComment:L,filename:wn,comment:Ba,directory:ot||wn.endsWith(l2)}),x=Math.max(Ft,x),Zh(k,k,Z,R+6),k.zipCrypto=k.encrypted&&!k.extraFieldAES;const Ee=new p2(k);Ee.getData=(Rn,Qa)=>k.getData(Rn,Ee,c.readRanges,Qa),Ee.arrayBuffer=async Rn=>{const Qa=new TransformStream,[Bi]=await Promise.all([new Response(Qa.readable).arrayBuffer(),k.getData(Qa,Ee,c.readRanges,Rn)]);return Bi},R=C;const{onprogress:Ua}=u;if(Ua)try{await Ua(J+1,b,new p2(k))}catch{}yield Ee}const N=ie(c,u,c5),K=ie(c,u,s5);return N&&(c.prependedData=x>0?await Pt(f,0,x):new Uint8Array),c.comment=E?await Pt(f,A+pn,E):new Uint8Array,K&&(c.appendedData=S<f.size?await Pt(f,S,f.size-S):new Uint8Array),!0}async getEntries(u={}){const c=[];for await(const f of this.getEntriesGenerator(u))c.push(f);return c}async close(){}}class S5{constructor(u={}){const{readable:c,writable:f}=new TransformStream,r=new Xh(c,u).getEntriesGenerator();this.readable=new ReadableStream({async pull(o){const{done:h,value:v}=await r.next();if(h)return o.close();const y={...v,readable:(function(){const{readable:A,writable:E}=new TransformStream;if(v.getData)return v.getData(E),A})()};delete y.getData,o.enqueue(y)}}),this.writable=f}}class T5{constructor(u,c,f){Object.assign(this,{reader:u,config:c,options:f})}async getData(u,c,f,r={}){const o=this,{reader:h,offset:v,diskNumberStart:y,extraFieldAES:A,extraFieldZip64:E,compressionMethod:S,config:O,bitFlag:X,signature:B,rawLastModDate:b,uncompressedSize:p,compressedSize:x}=o,{dataDescriptor:R}=X,U=c.localDirectory={},Z=await Pt(h,v,Qf,y),F=Yt(Z);let j=ie(o,r,f5),D=ie(o,r,r5);const N=ie(o,r,o5);if(j=j&&j.length&&j,D=D&&D.length&&D,A&&A.originalCompressionMethod!=u8)throw new Error(lr);if(S!=i8&&S!=l8&&!N)throw new Error(lr);if(Bt(F,0)!=c8)throw new Error(Qh);Vh(U,F,4);const{extraFieldLength:K,filenameLength:J,lastAccessDate:k,creationDate:nt}=U;U.rawExtraField=K?await Pt(h,v+Qf+J,K,y):new Uint8Array,Zh(o,U,F,4,!0),Object.assign(c,{lastAccessDate:k,creationDate:nt});const P=o.encrypted&&U.encrypted&&!N,st=P&&!A;if(N||(c.zipCrypto=st),P){if(!st&&A.strength===Jt)throw new Error(Lh);if(!j&&!D)throw new Error(Yh)}const it=v+Qf+J+K,H=x,_=h.readable;Object.assign(_,{diskNumberStart:y,offset:it,size:H});const $=ie(o,r,d5),ht=ie(o,r,h5);let tt=ie(o,r,g5);const C=ie(o,r,m5);C&&(tt=!0);const{onstart:L,onprogress:W,onend:et}=r,rt={options:{codecType:ch,password:j,rawPassword:D,zipCrypto:st,encryptionStrength:A&&A.strength,signed:ie(o,r,A5)&&!N,passwordVerification:st&&(R?b>>>8&255:B>>>24&255),outputSize:p,signature:B,compressed:S!=0&&!N,encrypted:o.encrypted&&!N,useWebWorkers:ie(o,r,v5),useCompressionStream:ie(o,r,y5),transferStreams:ie(o,r,E5),checkPasswordOnly:ht},config:O,streamOptions:{signal:$,size:H,onstart:L,onprogress:W,onend:et}};tt&&await D5({reader:h,fileEntry:c,offset:v,diskNumberStart:y,signature:B,compressedSize:x,uncompressedSize:p,dataOffset:it,dataDescriptor:R||U.bitFlag.dataDescriptor,extraFieldZip64:E||U.extraFieldZip64,readRanges:f});let ot;try{if(!C){ht&&(u=new WritableStream),u=new xh(u),await Ri(u,N?x:p),{writable:ot}=u;const{outputSize:gt}=await g3({readable:_,writable:ot},rt);if(u.size+=gt,gt!=(N?x:p))throw new Error(gr)}}catch(gt){if(gt.outputSize!==Jt&&(u.size+=gt.outputSize),!ht||gt.message!=dr)throw gt}finally{!ie(o,r,p5)&&ot&&!ot.locked&&await ot.getWriter().close()}return ht||C?Jt:u.getData?u.getData():ot}}function Vh(l,u,c){const f=l.rawBitFlag=$t(u,c+2),r=(f&t2)==t2,o=Bt(u,c+6);Object.assign(l,{encrypted:r,version:$t(u,c),bitFlag:{level:(f&b8)>>1,dataDescriptor:(f&e2)==e2,languageEncodingFlag:(f&n2)==n2},rawLastModDate:o,lastModDate:j5(o),filenameLength:$t(u,c+22),extraFieldLength:$t(u,c+24)})}function Zh(l,u,c,f,r){const{rawExtraField:o}=u,h=u.extraField=new Map,v=Yt(new Uint8Array(o));let y=0;try{for(;y<o.length;){const x=$t(v,y),R=$t(v,y+2);h.set(x,{type:x,data:o.slice(y+4,y+4+R)}),y+=4+R}}catch{}const A=$t(c,f+4);Object.assign(u,{signature:Bt(c,f+C8),compressedSize:Bt(c,f+O8),uncompressedSize:Bt(c,f+w8)});const E=h.get(h8);E&&(C5(E,u),u.extraFieldZip64=E);const S=h.get(y8);S&&(S2(S,Th,Ch,u,l),u.extraFieldUnicodePath=S);const O=h.get(E8);O&&(S2(O,Oh,wh,u,l),u.extraFieldUnicodeComment=O);const X=h.get(m8);X?(O5(X,u,A),u.extraFieldAES=X):u.compressionMethod=A;const B=h.get(g8);B&&(w5(B,u),u.extraFieldNTFS=B);const b=h.get(v8);b&&(R5(b,u,r),u.extraFieldExtendedTimestamp=b);const p=h.get(p8);p&&(u.extraFieldUSDZ=p)}function C5(l,u){u.zip64=!0;const c=Yt(l.data),f=b5.filter(([r,o])=>u[r]==o);for(let r=0,o=0;r<f.length;r++){const[h,v]=f[r];if(u[h]==v){const y=x5[v];u[h]=l[h]=y.getValue(c,o),o+=y.bytes}else if(l[h])throw new Error(zh)}}function S2(l,u,c,f,r){const o=Yt(l.data),h=new cc;h.append(r[c]);const v=Yt(new Uint8Array(4));v.setUint32(0,h.get(),!0);const y=Bt(o,1);Object.assign(l,{version:Sl(o,0),[u]:ic(l.data.subarray(5)),valid:!r.bitFlag.languageEncodingFlag&&y==Bt(v,0)}),l.valid&&(f[u]=l[u],f[u+"UTF8"]=!0)}function O5(l,u,c){const f=Yt(l.data),r=Sl(f,4);Object.assign(l,{vendorVersion:Sl(f,0),vendorId:Sl(f,2),strength:r,originalCompressionMethod:c,compressionMethod:$t(f,5)}),u.compressionMethod=l.compressionMethod}function w5(l,u){const c=Yt(l.data);let f=4,r;try{for(;f<l.data.length&&!r;){const o=$t(c,f),h=$t(c,f+2);o==A8&&(r=l.data.slice(f+4,f+4+h)),f+=4+h}}catch{}try{if(r&&r.length==24){const o=Yt(r),h=o.getBigUint64(0,!0),v=o.getBigUint64(8,!0),y=o.getBigUint64(16,!0);Object.assign(l,{rawLastModDate:h,rawLastAccessDate:v,rawCreationDate:y});const A=Gf(h),E=Gf(v),S=Gf(y),O={lastModDate:A,lastAccessDate:E,creationDate:S};Object.assign(l,O),Object.assign(u,O)}}catch{}}function R5(l,u,c){const f=Yt(l.data),r=Sl(f,0),o=[],h=[];c?((r&1)==1&&(o.push(nr),h.push(ar)),(r&2)==2&&(o.push(jh),h.push(G3)),(r&4)==4&&(o.push(Hh),h.push(X3))):l.data.length>=5&&(o.push(nr),h.push(ar));let v=1;o.forEach((y,A)=>{if(l.data.length>=v+4){const E=Bt(f,v);u[y]=l[y]=new Date(E*1e3);const S=h[A];l[S]=E}v+=4})}async function D5({reader:l,fileEntry:u,offset:c,diskNumberStart:f,signature:r,compressedSize:o,uncompressedSize:h,dataOffset:v,dataDescriptor:y,extraFieldZip64:A,readRanges:E}){let S=0;if(f)for(let B=0;B<f;B++){const b=l.readers[B];S+=b.size}let O=0;if(y&&(A?O=d8:O=o8),O){const B=await Pt(l,v+o,O+$1,f);if(Bt(Yt(B),0)==s8){const p=Bt(Yt(B),4);let x,R;A?(x=Tl(Yt(B),8),R=Tl(Yt(B),16)):(x=Bt(Yt(B),8),R=Bt(Yt(B),12)),(u.encrypted&&!u.zipCrypto||p==r)&&x==o&&R==h&&(O+=$1)}}const X={start:S+c,end:S+v+o+O,fileEntry:u};for(const B of E)if(B.fileEntry!=u&&X.start>=B.start&&X.start<B.end){const b=new Error(Gh);throw b.overlappingEntry=B.fileEntry,b}E.push(X)}async function M5(l,u,c,f,r){const o=new Uint8Array(4),h=Yt(o);H5(h,0,u);const v=f+r;return await y(f)||await y(Math.min(v,c));async function y(A){const E=c-A,S=await Pt(l,E,A);for(let O=S.length-f;O>=0;O--)if(S[O]==o[0]&&S[O+1]==o[1]&&S[O+2]==o[2]&&S[O+3]==o[3])return{offset:E+O,buffer:S.slice(O,O+f).buffer}}}function ie(l,u,c){return u[c]===Jt?l.options[c]:u[c]}function j5(l){const u=(l&4294901760)>>16,c=l&65535;try{return new Date(1980+((u&65024)>>9),((u&480)>>5)-1,u&31,(c&63488)>>11,(c&2016)>>5,(c&31)*2,0)}catch{}}function Gf(l){return new Date(Number(l/BigInt(1e4)-BigInt(116444736e5)))}function Sl(l,u){return l.getUint8(u)}function $t(l,u){return l.getUint16(u,!0)}function Bt(l,u){return l.getUint32(u,!0)}function Tl(l,u){return Number(l.getBigUint64(u,!0))}function H5(l,u,c){l.setUint32(u,c,!0)}function Yt(l){return new DataView(l.buffer)}q2({Inflate:a8});const N5=Object.freeze(Object.defineProperty({__proto__:null,BlobReader:Er,BlobWriter:mh,Data64URIReader:O3,Data64URIWriter:w3,ERR_BAD_FORMAT:uc,ERR_CENTRAL_DIRECTORY_NOT_FOUND:Uh,ERR_ENCRYPTED:Yh,ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND:Bh,ERR_EOCDR_NOT_FOUND:Nh,ERR_EXTRAFIELD_ZIP64_NOT_FOUND:zh,ERR_HTTP_RANGE:Hi,ERR_INVALID_PASSWORD:rr,ERR_INVALID_SIGNATURE:or,ERR_INVALID_UNCOMPRESSED_SIZE:gr,ERR_ITERATOR_COMPLETED_TOO_SOON:oh,ERR_LOCAL_FILE_HEADER_NOT_FOUND:Qh,ERR_OVERLAPPING_ENTRY:Gh,ERR_SPLIT_ZIP_FILE:ir,ERR_UNSUPPORTED_COMPRESSION:lr,ERR_UNSUPPORTED_ENCRYPTION:Lh,ERR_WRITER_NOT_INITIALIZED:dh,GenericReader:bh,GenericWriter:xh,HttpRangeReader:H3,HttpReader:ph,Reader:fa,SplitDataReader:br,SplitDataWriter:fc,SplitZipReader:Q3,SplitZipWriter:z3,TextReader:R3,TextWriter:D3,Uint8ArrayReader:N3,Uint8ArrayWriter:B3,Writer:yr,ZipReader:Xh,ZipReaderStream:S5,configure:q2,getMimeType:j8,initStream:Ri,readUint8Array:Pt,terminateWorkers:v3},Symbol.toStringTag,{value:"Module"}));var Xf={exports:{}},dt={};/** 19 + * @license React 20 + * react.production.js 21 + * 22 + * Copyright (c) Meta Platforms, Inc. and affiliates. 23 + * 24 + * This source code is licensed under the MIT license found in the 25 + * LICENSE file in the root directory of this source tree. 26 + */var T2;function B5(){if(T2)return dt;T2=1;var l=Symbol.for("react.transitional.element"),u=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),r=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),h=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),y=Symbol.for("react.suspense"),A=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),S=Symbol.for("react.activity"),O=Symbol.iterator;function X(C){return C===null||typeof C!="object"?null:(C=O&&C[O]||C["@@iterator"],typeof C=="function"?C:null)}var B={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,p={};function x(C,L,W){this.props=C,this.context=L,this.refs=p,this.updater=W||B}x.prototype.isReactComponent={},x.prototype.setState=function(C,L){if(typeof C!="object"&&typeof C!="function"&&C!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,C,L,"setState")},x.prototype.forceUpdate=function(C){this.updater.enqueueForceUpdate(this,C,"forceUpdate")};function R(){}R.prototype=x.prototype;function U(C,L,W){this.props=C,this.context=L,this.refs=p,this.updater=W||B}var Z=U.prototype=new R;Z.constructor=U,b(Z,x.prototype),Z.isPureReactComponent=!0;var F=Array.isArray;function j(){}var D={H:null,A:null,T:null,S:null},N=Object.prototype.hasOwnProperty;function K(C,L,W){var et=W.ref;return{$$typeof:l,type:C,key:L,ref:et!==void 0?et:null,props:W}}function J(C,L){return K(C.type,L,C.props)}function k(C){return typeof C=="object"&&C!==null&&C.$$typeof===l}function nt(C){var L={"=":"=0",":":"=2"};return"$"+C.replace(/[=:]/g,function(W){return L[W]})}var P=/\/+/g;function st(C,L){return typeof C=="object"&&C!==null&&C.key!=null?nt(""+C.key):L.toString(36)}function it(C){switch(C.status){case"fulfilled":return C.value;case"rejected":throw C.reason;default:switch(typeof C.status=="string"?C.then(j,j):(C.status="pending",C.then(function(L){C.status==="pending"&&(C.status="fulfilled",C.value=L)},function(L){C.status==="pending"&&(C.status="rejected",C.reason=L)})),C.status){case"fulfilled":return C.value;case"rejected":throw C.reason}}throw C}function H(C,L,W,et,rt){var ot=typeof C;(ot==="undefined"||ot==="boolean")&&(C=null);var gt=!1;if(C===null)gt=!0;else switch(ot){case"bigint":case"string":case"number":gt=!0;break;case"object":switch(C.$$typeof){case l:case u:gt=!0;break;case E:return gt=C._init,H(gt(C._payload),L,W,et,rt)}}if(gt)return rt=rt(C),gt=et===""?"."+st(C,0):et,F(rt)?(W="",gt!=null&&(W=gt.replace(P,"$&/")+"/"),H(rt,L,W,"",function(On){return On})):rt!=null&&(k(rt)&&(rt=J(rt,W+(rt.key==null||C&&C.key===rt.key?"":(""+rt.key).replace(P,"$&/")+"/")+gt)),L.push(rt)),1;gt=0;var Ft=et===""?".":et+":";if(F(C))for(var Qt=0;Qt<C.length;Qt++)et=C[Qt],ot=Ft+st(et,Qt),gt+=H(et,L,W,ot,rt);else if(Qt=X(C),typeof Qt=="function")for(C=Qt.call(C),Qt=0;!(et=C.next()).done;)et=et.value,ot=Ft+st(et,Qt++),gt+=H(et,L,W,ot,rt);else if(ot==="object"){if(typeof C.then=="function")return H(it(C),L,W,et,rt);throw L=String(C),Error("Objects are not valid as a React child (found: "+(L==="[object Object]"?"object with keys {"+Object.keys(C).join(", ")+"}":L)+"). If you meant to render a collection of children, use an array instead.")}return gt}function _(C,L,W){if(C==null)return C;var et=[],rt=0;return H(C,et,"","",function(ot){return L.call(W,ot,rt++)}),et}function $(C){if(C._status===-1){var L=C._result;L=L(),L.then(function(W){(C._status===0||C._status===-1)&&(C._status=1,C._result=W)},function(W){(C._status===0||C._status===-1)&&(C._status=2,C._result=W)}),C._status===-1&&(C._status=0,C._result=L)}if(C._status===1)return C._result.default;throw C._result}var ht=typeof reportError=="function"?reportError:function(C){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var L=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof C=="object"&&C!==null&&typeof C.message=="string"?String(C.message):String(C),error:C});if(!window.dispatchEvent(L))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",C);return}console.error(C)},tt={map:_,forEach:function(C,L,W){_(C,function(){L.apply(this,arguments)},W)},count:function(C){var L=0;return _(C,function(){L++}),L},toArray:function(C){return _(C,function(L){return L})||[]},only:function(C){if(!k(C))throw Error("React.Children.only expected to receive a single React element child.");return C}};return dt.Activity=S,dt.Children=tt,dt.Component=x,dt.Fragment=c,dt.Profiler=r,dt.PureComponent=U,dt.StrictMode=f,dt.Suspense=y,dt.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=D,dt.__COMPILER_RUNTIME={__proto__:null,c:function(C){return D.H.useMemoCache(C)}},dt.cache=function(C){return function(){return C.apply(null,arguments)}},dt.cacheSignal=function(){return null},dt.cloneElement=function(C,L,W){if(C==null)throw Error("The argument must be a React element, but you passed "+C+".");var et=b({},C.props),rt=C.key;if(L!=null)for(ot in L.key!==void 0&&(rt=""+L.key),L)!N.call(L,ot)||ot==="key"||ot==="__self"||ot==="__source"||ot==="ref"&&L.ref===void 0||(et[ot]=L[ot]);var ot=arguments.length-2;if(ot===1)et.children=W;else if(1<ot){for(var gt=Array(ot),Ft=0;Ft<ot;Ft++)gt[Ft]=arguments[Ft+2];et.children=gt}return K(C.type,rt,et)},dt.createContext=function(C){return C={$$typeof:h,_currentValue:C,_currentValue2:C,_threadCount:0,Provider:null,Consumer:null},C.Provider=C,C.Consumer={$$typeof:o,_context:C},C},dt.createElement=function(C,L,W){var et,rt={},ot=null;if(L!=null)for(et in L.key!==void 0&&(ot=""+L.key),L)N.call(L,et)&&et!=="key"&&et!=="__self"&&et!=="__source"&&(rt[et]=L[et]);var gt=arguments.length-2;if(gt===1)rt.children=W;else if(1<gt){for(var Ft=Array(gt),Qt=0;Qt<gt;Qt++)Ft[Qt]=arguments[Qt+2];rt.children=Ft}if(C&&C.defaultProps)for(et in gt=C.defaultProps,gt)rt[et]===void 0&&(rt[et]=gt[et]);return K(C,ot,rt)},dt.createRef=function(){return{current:null}},dt.forwardRef=function(C){return{$$typeof:v,render:C}},dt.isValidElement=k,dt.lazy=function(C){return{$$typeof:E,_payload:{_status:-1,_result:C},_init:$}},dt.memo=function(C,L){return{$$typeof:A,type:C,compare:L===void 0?null:L}},dt.startTransition=function(C){var L=D.T,W={};D.T=W;try{var et=C(),rt=D.S;rt!==null&&rt(W,et),typeof et=="object"&&et!==null&&typeof et.then=="function"&&et.then(j,ht)}catch(ot){ht(ot)}finally{L!==null&&W.types!==null&&(L.types=W.types),D.T=L}},dt.unstable_useCacheRefresh=function(){return D.H.useCacheRefresh()},dt.use=function(C){return D.H.use(C)},dt.useActionState=function(C,L,W){return D.H.useActionState(C,L,W)},dt.useCallback=function(C,L){return D.H.useCallback(C,L)},dt.useContext=function(C){return D.H.useContext(C)},dt.useDebugValue=function(){},dt.useDeferredValue=function(C,L){return D.H.useDeferredValue(C,L)},dt.useEffect=function(C,L){return D.H.useEffect(C,L)},dt.useEffectEvent=function(C){return D.H.useEffectEvent(C)},dt.useId=function(){return D.H.useId()},dt.useImperativeHandle=function(C,L,W){return D.H.useImperativeHandle(C,L,W)},dt.useInsertionEffect=function(C,L){return D.H.useInsertionEffect(C,L)},dt.useLayoutEffect=function(C,L){return D.H.useLayoutEffect(C,L)},dt.useMemo=function(C,L){return D.H.useMemo(C,L)},dt.useOptimistic=function(C,L){return D.H.useOptimistic(C,L)},dt.useReducer=function(C,L,W){return D.H.useReducer(C,L,W)},dt.useRef=function(C){return D.H.useRef(C)},dt.useState=function(C){return D.H.useState(C)},dt.useSyncExternalStore=function(C,L,W){return D.H.useSyncExternalStore(C,L,W)},dt.useTransition=function(){return D.H.useTransition()},dt.version="19.2.1",dt}var C2;function xr(){return C2||(C2=1,Xf.exports=B5()),Xf.exports}var ct=xr();const ue=UA(ct);var Vf={exports:{}},xi={},Zf={exports:{}},qf={};/** 27 + * @license React 28 + * scheduler.production.js 29 + * 30 + * Copyright (c) Meta Platforms, Inc. and affiliates. 31 + * 32 + * This source code is licensed under the MIT license found in the 33 + * LICENSE file in the root directory of this source tree. 34 + */var O2;function U5(){return O2||(O2=1,(function(l){function u(H,_){var $=H.length;H.push(_);t:for(;0<$;){var ht=$-1>>>1,tt=H[ht];if(0<r(tt,_))H[ht]=_,H[$]=tt,$=ht;else break t}}function c(H){return H.length===0?null:H[0]}function f(H){if(H.length===0)return null;var _=H[0],$=H.pop();if($!==_){H[0]=$;t:for(var ht=0,tt=H.length,C=tt>>>1;ht<C;){var L=2*(ht+1)-1,W=H[L],et=L+1,rt=H[et];if(0>r(W,$))et<tt&&0>r(rt,W)?(H[ht]=rt,H[et]=$,ht=et):(H[ht]=W,H[L]=$,ht=L);else if(et<tt&&0>r(rt,$))H[ht]=rt,H[et]=$,ht=et;else break t}}return _}function r(H,_){var $=H.sortIndex-_.sortIndex;return $!==0?$:H.id-_.id}if(l.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;l.unstable_now=function(){return o.now()}}else{var h=Date,v=h.now();l.unstable_now=function(){return h.now()-v}}var y=[],A=[],E=1,S=null,O=3,X=!1,B=!1,b=!1,p=!1,x=typeof setTimeout=="function"?setTimeout:null,R=typeof clearTimeout=="function"?clearTimeout:null,U=typeof setImmediate<"u"?setImmediate:null;function Z(H){for(var _=c(A);_!==null;){if(_.callback===null)f(A);else if(_.startTime<=H)f(A),_.sortIndex=_.expirationTime,u(y,_);else break;_=c(A)}}function F(H){if(b=!1,Z(H),!B)if(c(y)!==null)B=!0,j||(j=!0,nt());else{var _=c(A);_!==null&&it(F,_.startTime-H)}}var j=!1,D=-1,N=5,K=-1;function J(){return p?!0:!(l.unstable_now()-K<N)}function k(){if(p=!1,j){var H=l.unstable_now();K=H;var _=!0;try{t:{B=!1,b&&(b=!1,R(D),D=-1),X=!0;var $=O;try{e:{for(Z(H),S=c(y);S!==null&&!(S.expirationTime>H&&J());){var ht=S.callback;if(typeof ht=="function"){S.callback=null,O=S.priorityLevel;var tt=ht(S.expirationTime<=H);if(H=l.unstable_now(),typeof tt=="function"){S.callback=tt,Z(H),_=!0;break e}S===c(y)&&f(y),Z(H)}else f(y);S=c(y)}if(S!==null)_=!0;else{var C=c(A);C!==null&&it(F,C.startTime-H),_=!1}}break t}finally{S=null,O=$,X=!1}_=void 0}}finally{_?nt():j=!1}}}var nt;if(typeof U=="function")nt=function(){U(k)};else if(typeof MessageChannel<"u"){var P=new MessageChannel,st=P.port2;P.port1.onmessage=k,nt=function(){st.postMessage(null)}}else nt=function(){x(k,0)};function it(H,_){D=x(function(){H(l.unstable_now())},_)}l.unstable_IdlePriority=5,l.unstable_ImmediatePriority=1,l.unstable_LowPriority=4,l.unstable_NormalPriority=3,l.unstable_Profiling=null,l.unstable_UserBlockingPriority=2,l.unstable_cancelCallback=function(H){H.callback=null},l.unstable_forceFrameRate=function(H){0>H||125<H?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):N=0<H?Math.floor(1e3/H):5},l.unstable_getCurrentPriorityLevel=function(){return O},l.unstable_next=function(H){switch(O){case 1:case 2:case 3:var _=3;break;default:_=O}var $=O;O=_;try{return H()}finally{O=$}},l.unstable_requestPaint=function(){p=!0},l.unstable_runWithPriority=function(H,_){switch(H){case 1:case 2:case 3:case 4:case 5:break;default:H=3}var $=O;O=H;try{return _()}finally{O=$}},l.unstable_scheduleCallback=function(H,_,$){var ht=l.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?ht+$:ht):$=ht,H){case 1:var tt=-1;break;case 2:tt=250;break;case 5:tt=1073741823;break;case 4:tt=1e4;break;default:tt=5e3}return tt=$+tt,H={id:E++,callback:_,priorityLevel:H,startTime:$,expirationTime:tt,sortIndex:-1},$>ht?(H.sortIndex=$,u(A,H),c(y)===null&&H===c(A)&&(b?(R(D),D=-1):b=!0,it(F,$-ht))):(H.sortIndex=tt,u(y,H),B||X||(B=!0,j||(j=!0,nt()))),H},l.unstable_shouldYield=J,l.unstable_wrapCallback=function(H){var _=O;return function(){var $=O;O=_;try{return H.apply(this,arguments)}finally{O=$}}}})(qf)),qf}var w2;function Q5(){return w2||(w2=1,Zf.exports=U5()),Zf.exports}var If={exports:{}},ce={};/** 35 + * @license React 36 + * react-dom.production.js 37 + * 38 + * Copyright (c) Meta Platforms, Inc. and affiliates. 39 + * 40 + * This source code is licensed under the MIT license found in the 41 + * LICENSE file in the root directory of this source tree. 42 + */var R2;function z5(){if(R2)return ce;R2=1;var l=xr();function u(y){var A="https://react.dev/errors/"+y;if(1<arguments.length){A+="?args[]="+encodeURIComponent(arguments[1]);for(var E=2;E<arguments.length;E++)A+="&args[]="+encodeURIComponent(arguments[E])}return"Minified React error #"+y+"; visit "+A+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function c(){}var f={d:{f:c,r:function(){throw Error(u(522))},D:c,C:c,L:c,m:c,X:c,S:c,M:c},p:0,findDOMNode:null},r=Symbol.for("react.portal");function o(y,A,E){var S=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:r,key:S==null?null:""+S,children:y,containerInfo:A,implementation:E}}var h=l.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function v(y,A){if(y==="font")return"";if(typeof A=="string")return A==="use-credentials"?A:""}return ce.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=f,ce.createPortal=function(y,A){var E=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!A||A.nodeType!==1&&A.nodeType!==9&&A.nodeType!==11)throw Error(u(299));return o(y,A,null,E)},ce.flushSync=function(y){var A=h.T,E=f.p;try{if(h.T=null,f.p=2,y)return y()}finally{h.T=A,f.p=E,f.d.f()}},ce.preconnect=function(y,A){typeof y=="string"&&(A?(A=A.crossOrigin,A=typeof A=="string"?A==="use-credentials"?A:"":void 0):A=null,f.d.C(y,A))},ce.prefetchDNS=function(y){typeof y=="string"&&f.d.D(y)},ce.preinit=function(y,A){if(typeof y=="string"&&A&&typeof A.as=="string"){var E=A.as,S=v(E,A.crossOrigin),O=typeof A.integrity=="string"?A.integrity:void 0,X=typeof A.fetchPriority=="string"?A.fetchPriority:void 0;E==="style"?f.d.S(y,typeof A.precedence=="string"?A.precedence:void 0,{crossOrigin:S,integrity:O,fetchPriority:X}):E==="script"&&f.d.X(y,{crossOrigin:S,integrity:O,fetchPriority:X,nonce:typeof A.nonce=="string"?A.nonce:void 0})}},ce.preinitModule=function(y,A){if(typeof y=="string")if(typeof A=="object"&&A!==null){if(A.as==null||A.as==="script"){var E=v(A.as,A.crossOrigin);f.d.M(y,{crossOrigin:E,integrity:typeof A.integrity=="string"?A.integrity:void 0,nonce:typeof A.nonce=="string"?A.nonce:void 0})}}else A==null&&f.d.M(y)},ce.preload=function(y,A){if(typeof y=="string"&&typeof A=="object"&&A!==null&&typeof A.as=="string"){var E=A.as,S=v(E,A.crossOrigin);f.d.L(y,E,{crossOrigin:S,integrity:typeof A.integrity=="string"?A.integrity:void 0,nonce:typeof A.nonce=="string"?A.nonce:void 0,type:typeof A.type=="string"?A.type:void 0,fetchPriority:typeof A.fetchPriority=="string"?A.fetchPriority:void 0,referrerPolicy:typeof A.referrerPolicy=="string"?A.referrerPolicy:void 0,imageSrcSet:typeof A.imageSrcSet=="string"?A.imageSrcSet:void 0,imageSizes:typeof A.imageSizes=="string"?A.imageSizes:void 0,media:typeof A.media=="string"?A.media:void 0})}},ce.preloadModule=function(y,A){if(typeof y=="string")if(A){var E=v(A.as,A.crossOrigin);f.d.m(y,{as:typeof A.as=="string"&&A.as!=="script"?A.as:void 0,crossOrigin:E,integrity:typeof A.integrity=="string"?A.integrity:void 0})}else f.d.m(y)},ce.requestFormReset=function(y){f.d.r(y)},ce.unstable_batchedUpdates=function(y,A){return y(A)},ce.useFormState=function(y,A,E){return h.H.useFormState(y,A,E)},ce.useFormStatus=function(){return h.H.useHostTransitionStatus()},ce.version="19.2.1",ce}var D2;function Y5(){if(D2)return If.exports;D2=1;function l(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(u){console.error(u)}}return l(),If.exports=z5(),If.exports}/** 43 + * @license React 44 + * react-dom-client.production.js 45 + * 46 + * Copyright (c) Meta Platforms, Inc. and affiliates. 47 + * 48 + * This source code is licensed under the MIT license found in the 49 + * LICENSE file in the root directory of this source tree. 50 + */var M2;function L5(){if(M2)return xi;M2=1;var l=Q5(),u=xr(),c=Y5();function f(t){var e="https://react.dev/errors/"+t;if(1<arguments.length){e+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)e+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+t+"; visit "+e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function r(t){return!(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)}function o(t){var e=t,n=t;if(t.alternate)for(;e.return;)e=e.return;else{t=e;do e=t,(e.flags&4098)!==0&&(n=e.return),t=e.return;while(t)}return e.tag===3?n:null}function h(t){if(t.tag===13){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function v(t){if(t.tag===31){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function y(t){if(o(t)!==t)throw Error(f(188))}function A(t){var e=t.alternate;if(!e){if(e=o(t),e===null)throw Error(f(188));return e!==t?null:t}for(var n=t,a=e;;){var i=n.return;if(i===null)break;var s=i.alternate;if(s===null){if(a=i.return,a!==null){n=a;continue}break}if(i.child===s.child){for(s=i.child;s;){if(s===n)return y(i),t;if(s===a)return y(i),e;s=s.sibling}throw Error(f(188))}if(n.return!==a.return)n=i,a=s;else{for(var d=!1,g=i.child;g;){if(g===n){d=!0,n=i,a=s;break}if(g===a){d=!0,a=i,n=s;break}g=g.sibling}if(!d){for(g=s.child;g;){if(g===n){d=!0,n=s,a=i;break}if(g===a){d=!0,a=s,n=i;break}g=g.sibling}if(!d)throw Error(f(189))}}if(n.alternate!==a)throw Error(f(190))}if(n.tag!==3)throw Error(f(188));return n.stateNode.current===n?t:e}function E(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t;for(t=t.child;t!==null;){if(e=E(t),e!==null)return e;t=t.sibling}return null}var S=Object.assign,O=Symbol.for("react.element"),X=Symbol.for("react.transitional.element"),B=Symbol.for("react.portal"),b=Symbol.for("react.fragment"),p=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),R=Symbol.for("react.consumer"),U=Symbol.for("react.context"),Z=Symbol.for("react.forward_ref"),F=Symbol.for("react.suspense"),j=Symbol.for("react.suspense_list"),D=Symbol.for("react.memo"),N=Symbol.for("react.lazy"),K=Symbol.for("react.activity"),J=Symbol.for("react.memo_cache_sentinel"),k=Symbol.iterator;function nt(t){return t===null||typeof t!="object"?null:(t=k&&t[k]||t["@@iterator"],typeof t=="function"?t:null)}var P=Symbol.for("react.client.reference");function st(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===P?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case b:return"Fragment";case x:return"Profiler";case p:return"StrictMode";case F:return"Suspense";case j:return"SuspenseList";case K:return"Activity"}if(typeof t=="object")switch(t.$$typeof){case B:return"Portal";case U:return t.displayName||"Context";case R:return(t._context.displayName||"Context")+".Consumer";case Z:var e=t.render;return t=t.displayName,t||(t=e.displayName||e.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case D:return e=t.displayName||null,e!==null?e:st(t.type)||"Memo";case N:e=t._payload,t=t._init;try{return st(t(e))}catch{}}return null}var it=Array.isArray,H=u.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,_=c.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,$={pending:!1,data:null,method:null,action:null},ht=[],tt=-1;function C(t){return{current:t}}function L(t){0>tt||(t.current=ht[tt],ht[tt]=null,tt--)}function W(t,e){tt++,ht[tt]=t.current,t.current=e}var et=C(null),rt=C(null),ot=C(null),gt=C(null);function Ft(t,e){switch(W(ot,e),W(rt,t),W(et,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Pd(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Pd(e),t=$d(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}L(et),W(et,t)}function Qt(){L(et),L(rt),L(ot)}function On(t){t.memoizedState!==null&&W(gt,t);var e=et.current,n=$d(e,t.type);e!==n&&(W(rt,t),W(et,n))}function ra(t){rt.current===t&&(L(et),L(rt)),gt.current===t&&(L(gt),gi._currentValue=$)}var wn,Ba;function Ee(t){if(wn===void 0)try{throw Error()}catch(n){var e=n.stack.trim().match(/\n( *(at )?)/);wn=e&&e[1]||"",Ba=-1<n.stack.indexOf(` 51 + at`)?" (<anonymous>)":-1<n.stack.indexOf("@")?"@unknown:0:0":""}return` 52 + `+wn+t+Ba}var Ua=!1;function Rn(t,e){if(!t||Ua)return"";Ua=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(e){var I=function(){throw Error()};if(Object.defineProperty(I.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(I,[])}catch(G){var Y=G}Reflect.construct(t,[],I)}else{try{I.call()}catch(G){Y=G}t.call(I.prototype)}}else{try{throw Error()}catch(G){Y=G}(I=t())&&typeof I.catch=="function"&&I.catch(function(){})}}catch(G){if(G&&Y&&typeof G.stack=="string")return[G.stack,Y.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var i=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");i&&i.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var s=a.DetermineComponentFrameRoot(),d=s[0],g=s[1];if(d&&g){var T=d.split(` 53 + `),z=g.split(` 54 + `);for(i=a=0;a<T.length&&!T[a].includes("DetermineComponentFrameRoot");)a++;for(;i<z.length&&!z[i].includes("DetermineComponentFrameRoot");)i++;if(a===T.length||i===z.length)for(a=T.length-1,i=z.length-1;1<=a&&0<=i&&T[a]!==z[i];)i--;for(;1<=a&&0<=i;a--,i--)if(T[a]!==z[i]){if(a!==1||i!==1)do if(a--,i--,0>i||T[a]!==z[i]){var V=` 55 + `+T[a].replace(" at new "," at ");return t.displayName&&V.includes("<anonymous>")&&(V=V.replace("<anonymous>",t.displayName)),V}while(1<=a&&0<=i);break}}}finally{Ua=!1,Error.prepareStackTrace=n}return(n=t?t.displayName||t.name:"")?Ee(n):""}function Qa(t,e){switch(t.tag){case 26:case 27:case 5:return Ee(t.type);case 16:return Ee("Lazy");case 13:return t.child!==e&&e!==null?Ee("Suspense Fallback"):Ee("Suspense");case 19:return Ee("SuspenseList");case 0:case 15:return Rn(t.type,!1);case 11:return Rn(t.type.render,!1);case 1:return Rn(t.type,!0);case 31:return Ee("Activity");default:return""}}function Bi(t){try{var e="",n=null;do e+=Qa(t,n),n=t,t=t.return;while(t);return e}catch(a){return` 56 + Error generating stack: `+a.message+` 57 + `+a.stack}}var mc=Object.prototype.hasOwnProperty,gc=l.unstable_scheduleCallback,Ac=l.unstable_cancelCallback,om=l.unstable_shouldYield,dm=l.unstable_requestPaint,pe=l.unstable_now,hm=l.unstable_getCurrentPriorityLevel,Dr=l.unstable_ImmediatePriority,Mr=l.unstable_UserBlockingPriority,Ui=l.unstable_NormalPriority,mm=l.unstable_LowPriority,jr=l.unstable_IdlePriority,gm=l.log,Am=l.unstable_setDisableYieldValue,wl=null,be=null;function Dn(t){if(typeof gm=="function"&&Am(t),be&&typeof be.setStrictMode=="function")try{be.setStrictMode(wl,t)}catch{}}var xe=Math.clz32?Math.clz32:Em,vm=Math.log,ym=Math.LN2;function Em(t){return t>>>=0,t===0?32:31-(vm(t)/ym|0)|0}var Qi=256,zi=262144,Yi=4194304;function oa(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Li(t,e,n){var a=t.pendingLanes;if(a===0)return 0;var i=0,s=t.suspendedLanes,d=t.pingedLanes;t=t.warmLanes;var g=a&134217727;return g!==0?(a=g&~s,a!==0?i=oa(a):(d&=g,d!==0?i=oa(d):n||(n=g&~t,n!==0&&(i=oa(n))))):(g=a&~s,g!==0?i=oa(g):d!==0?i=oa(d):n||(n=a&~t,n!==0&&(i=oa(n)))),i===0?0:e!==0&&e!==i&&(e&s)===0&&(s=i&-i,n=e&-e,s>=n||s===32&&(n&4194048)!==0)?e:i}function Rl(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function pm(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Hr(){var t=Yi;return Yi<<=1,(Yi&62914560)===0&&(Yi=4194304),t}function vc(t){for(var e=[],n=0;31>n;n++)e.push(t);return e}function Dl(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function bm(t,e,n,a,i,s){var d=t.pendingLanes;t.pendingLanes=n,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=n,t.entangledLanes&=n,t.errorRecoveryDisabledLanes&=n,t.shellSuspendCounter=0;var g=t.entanglements,T=t.expirationTimes,z=t.hiddenUpdates;for(n=d&~n;0<n;){var V=31-xe(n),I=1<<V;g[V]=0,T[V]=-1;var Y=z[V];if(Y!==null)for(z[V]=null,V=0;V<Y.length;V++){var G=Y[V];G!==null&&(G.lane&=-536870913)}n&=~I}a!==0&&Nr(t,a,0),s!==0&&i===0&&t.tag!==0&&(t.suspendedLanes|=s&~(d&~e))}function Nr(t,e,n){t.pendingLanes|=e,t.suspendedLanes&=~e;var a=31-xe(e);t.entangledLanes|=e,t.entanglements[a]=t.entanglements[a]|1073741824|n&261930}function Br(t,e){var n=t.entangledLanes|=e;for(t=t.entanglements;n;){var a=31-xe(n),i=1<<a;i&e|t[a]&e&&(t[a]|=e),n&=~i}}function Ur(t,e){var n=e&-e;return n=(n&42)!==0?1:yc(n),(n&(t.suspendedLanes|e))!==0?0:n}function yc(t){switch(t){case 2:t=1;break;case 8:t=4;break;case 32:t=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:t=128;break;case 268435456:t=134217728;break;default:t=0}return t}function Ec(t){return t&=-t,2<t?8<t?(t&134217727)!==0?32:268435456:8:2}function Qr(){var t=_.p;return t!==0?t:(t=window.event,t===void 0?32:x1(t.type))}function zr(t,e){var n=_.p;try{return _.p=t,e()}finally{_.p=n}}var Mn=Math.random().toString(36).slice(2),te="__reactFiber$"+Mn,oe="__reactProps$"+Mn,za="__reactContainer$"+Mn,pc="__reactEvents$"+Mn,xm="__reactListeners$"+Mn,Sm="__reactHandles$"+Mn,Yr="__reactResources$"+Mn,Ml="__reactMarker$"+Mn;function bc(t){delete t[te],delete t[oe],delete t[pc],delete t[xm],delete t[Sm]}function Ya(t){var e=t[te];if(e)return e;for(var n=t.parentNode;n;){if(e=n[za]||n[te]){if(n=e.alternate,e.child!==null||n!==null&&n.child!==null)for(t=u1(t);t!==null;){if(n=t[te])return n;t=u1(t)}return e}t=n,n=t.parentNode}return null}function La(t){if(t=t[te]||t[za]){var e=t.tag;if(e===5||e===6||e===13||e===31||e===26||e===27||e===3)return t}return null}function jl(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t.stateNode;throw Error(f(33))}function Ga(t){var e=t[Yr];return e||(e=t[Yr]={hoistableStyles:new Map,hoistableScripts:new Map}),e}function Wt(t){t[Ml]=!0}var Lr=new Set,Gr={};function da(t,e){Xa(t,e),Xa(t+"Capture",e)}function Xa(t,e){for(Gr[t]=e,t=0;t<e.length;t++)Lr.add(e[t])}var Tm=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),Xr={},Vr={};function Cm(t){return mc.call(Vr,t)?!0:mc.call(Xr,t)?!1:Tm.test(t)?Vr[t]=!0:(Xr[t]=!0,!1)}function Gi(t,e,n){if(Cm(e))if(n===null)t.removeAttribute(e);else{switch(typeof n){case"undefined":case"function":case"symbol":t.removeAttribute(e);return;case"boolean":var a=e.toLowerCase().slice(0,5);if(a!=="data-"&&a!=="aria-"){t.removeAttribute(e);return}}t.setAttribute(e,""+n)}}function Xi(t,e,n){if(n===null)t.removeAttribute(e);else{switch(typeof n){case"undefined":case"function":case"symbol":case"boolean":t.removeAttribute(e);return}t.setAttribute(e,""+n)}}function $e(t,e,n,a){if(a===null)t.removeAttribute(n);else{switch(typeof a){case"undefined":case"function":case"symbol":case"boolean":t.removeAttribute(n);return}t.setAttributeNS(e,n,""+a)}}function je(t){switch(typeof t){case"bigint":case"boolean":case"number":case"string":case"undefined":return t;case"object":return t;default:return""}}function Zr(t){var e=t.type;return(t=t.nodeName)&&t.toLowerCase()==="input"&&(e==="checkbox"||e==="radio")}function Om(t,e,n){var a=Object.getOwnPropertyDescriptor(t.constructor.prototype,e);if(!t.hasOwnProperty(e)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var i=a.get,s=a.set;return Object.defineProperty(t,e,{configurable:!0,get:function(){return i.call(this)},set:function(d){n=""+d,s.call(this,d)}}),Object.defineProperty(t,e,{enumerable:a.enumerable}),{getValue:function(){return n},setValue:function(d){n=""+d},stopTracking:function(){t._valueTracker=null,delete t[e]}}}}function xc(t){if(!t._valueTracker){var e=Zr(t)?"checked":"value";t._valueTracker=Om(t,e,""+t[e])}}function qr(t){if(!t)return!1;var e=t._valueTracker;if(!e)return!0;var n=e.getValue(),a="";return t&&(a=Zr(t)?t.checked?"true":"false":t.value),t=a,t!==n?(e.setValue(t),!0):!1}function Vi(t){if(t=t||(typeof document<"u"?document:void 0),typeof t>"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var wm=/[\n"\\]/g;function He(t){return t.replace(wm,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function Sc(t,e,n,a,i,s,d,g){t.name="",d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"?t.type=d:t.removeAttribute("type"),e!=null?d==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+je(e)):t.value!==""+je(e)&&(t.value=""+je(e)):d!=="submit"&&d!=="reset"||t.removeAttribute("value"),e!=null?Tc(t,d,je(e)):n!=null?Tc(t,d,je(n)):a!=null&&t.removeAttribute("value"),i==null&&s!=null&&(t.defaultChecked=!!s),i!=null&&(t.checked=i&&typeof i!="function"&&typeof i!="symbol"),g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"?t.name=""+je(g):t.removeAttribute("name")}function Ir(t,e,n,a,i,s,d,g){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(t.type=s),e!=null||n!=null){if(!(s!=="submit"&&s!=="reset"||e!=null)){xc(t);return}n=n!=null?""+je(n):"",e=e!=null?""+je(e):n,g||e===t.value||(t.value=e),t.defaultValue=e}a=a??i,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=g?t.checked:!!a,t.defaultChecked=!!a,d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"&&(t.name=d),xc(t)}function Tc(t,e,n){e==="number"&&Vi(t.ownerDocument)===t||t.defaultValue===""+n||(t.defaultValue=""+n)}function Va(t,e,n,a){if(t=t.options,e){e={};for(var i=0;i<n.length;i++)e["$"+n[i]]=!0;for(n=0;n<t.length;n++)i=e.hasOwnProperty("$"+t[n].value),t[n].selected!==i&&(t[n].selected=i),i&&a&&(t[n].defaultSelected=!0)}else{for(n=""+je(n),e=null,i=0;i<t.length;i++){if(t[i].value===n){t[i].selected=!0,a&&(t[i].defaultSelected=!0);return}e!==null||t[i].disabled||(e=t[i])}e!==null&&(e.selected=!0)}}function Kr(t,e,n){if(e!=null&&(e=""+je(e),e!==t.value&&(t.value=e),n==null)){t.defaultValue!==e&&(t.defaultValue=e);return}t.defaultValue=n!=null?""+je(n):""}function kr(t,e,n,a){if(e==null){if(a!=null){if(n!=null)throw Error(f(92));if(it(a)){if(1<a.length)throw Error(f(93));a=a[0]}n=a}n==null&&(n=""),e=n}n=je(e),t.defaultValue=n,a=t.textContent,a===n&&a!==""&&a!==null&&(t.value=a),xc(t)}function Za(t,e){if(e){var n=t.firstChild;if(n&&n===t.lastChild&&n.nodeType===3){n.nodeValue=e;return}}t.textContent=e}var Rm=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function Jr(t,e,n){var a=e.indexOf("--")===0;n==null||typeof n=="boolean"||n===""?a?t.setProperty(e,""):e==="float"?t.cssFloat="":t[e]="":a?t.setProperty(e,n):typeof n!="number"||n===0||Rm.has(e)?e==="float"?t.cssFloat=n:t[e]=(""+n).trim():t[e]=n+"px"}function Fr(t,e,n){if(e!=null&&typeof e!="object")throw Error(f(62));if(t=t.style,n!=null){for(var a in n)!n.hasOwnProperty(a)||e!=null&&e.hasOwnProperty(a)||(a.indexOf("--")===0?t.setProperty(a,""):a==="float"?t.cssFloat="":t[a]="");for(var i in e)a=e[i],e.hasOwnProperty(i)&&n[i]!==a&&Jr(t,i,a)}else for(var s in e)e.hasOwnProperty(s)&&Jr(t,s,e[s])}function Cc(t){if(t.indexOf("-")===-1)return!1;switch(t){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Dm=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),Mm=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function Zi(t){return Mm.test(""+t)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":t}function tn(){}var Oc=null;function wc(t){return t=t.target||t.srcElement||window,t.correspondingUseElement&&(t=t.correspondingUseElement),t.nodeType===3?t.parentNode:t}var qa=null,Ia=null;function Wr(t){var e=La(t);if(e&&(t=e.stateNode)){var n=t[oe]||null;t:switch(t=e.stateNode,e.type){case"input":if(Sc(t,n.value,n.defaultValue,n.defaultValue,n.checked,n.defaultChecked,n.type,n.name),e=n.name,n.type==="radio"&&e!=null){for(n=t;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll('input[name="'+He(""+e)+'"][type="radio"]'),e=0;e<n.length;e++){var a=n[e];if(a!==t&&a.form===t.form){var i=a[oe]||null;if(!i)throw Error(f(90));Sc(a,i.value,i.defaultValue,i.defaultValue,i.checked,i.defaultChecked,i.type,i.name)}}for(e=0;e<n.length;e++)a=n[e],a.form===t.form&&qr(a)}break t;case"textarea":Kr(t,n.value,n.defaultValue);break t;case"select":e=n.value,e!=null&&Va(t,!!n.multiple,e,!1)}}}var Rc=!1;function _r(t,e,n){if(Rc)return t(e,n);Rc=!0;try{var a=t(e);return a}finally{if(Rc=!1,(qa!==null||Ia!==null)&&(Mu(),qa&&(e=qa,t=Ia,Ia=qa=null,Wr(e),t)))for(e=0;e<t.length;e++)Wr(t[e])}}function Hl(t,e){var n=t.stateNode;if(n===null)return null;var a=n[oe]||null;if(a===null)return null;n=a[e];t:switch(e){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(a=!a.disabled)||(t=t.type,a=!(t==="button"||t==="input"||t==="select"||t==="textarea")),t=!a;break t;default:t=!1}if(t)return null;if(n&&typeof n!="function")throw Error(f(231,e,typeof n));return n}var en=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Dc=!1;if(en)try{var Nl={};Object.defineProperty(Nl,"passive",{get:function(){Dc=!0}}),window.addEventListener("test",Nl,Nl),window.removeEventListener("test",Nl,Nl)}catch{Dc=!1}var jn=null,Mc=null,qi=null;function Pr(){if(qi)return qi;var t,e=Mc,n=e.length,a,i="value"in jn?jn.value:jn.textContent,s=i.length;for(t=0;t<n&&e[t]===i[t];t++);var d=n-t;for(a=1;a<=d&&e[n-a]===i[s-a];a++);return qi=i.slice(t,1<a?1-a:void 0)}function Ii(t){var e=t.keyCode;return"charCode"in t?(t=t.charCode,t===0&&e===13&&(t=13)):t=e,t===10&&(t=13),32<=t||t===13?t:0}function Ki(){return!0}function $r(){return!1}function de(t){function e(n,a,i,s,d){this._reactName=n,this._targetInst=i,this.type=a,this.nativeEvent=s,this.target=d,this.currentTarget=null;for(var g in t)t.hasOwnProperty(g)&&(n=t[g],this[g]=n?n(s):s[g]);return this.isDefaultPrevented=(s.defaultPrevented!=null?s.defaultPrevented:s.returnValue===!1)?Ki:$r,this.isPropagationStopped=$r,this}return S(e.prototype,{preventDefault:function(){this.defaultPrevented=!0;var n=this.nativeEvent;n&&(n.preventDefault?n.preventDefault():typeof n.returnValue!="unknown"&&(n.returnValue=!1),this.isDefaultPrevented=Ki)},stopPropagation:function(){var n=this.nativeEvent;n&&(n.stopPropagation?n.stopPropagation():typeof n.cancelBubble!="unknown"&&(n.cancelBubble=!0),this.isPropagationStopped=Ki)},persist:function(){},isPersistent:Ki}),e}var ha={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(t){return t.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},ki=de(ha),Bl=S({},ha,{view:0,detail:0}),jm=de(Bl),jc,Hc,Ul,Ji=S({},Bl,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Bc,button:0,buttons:0,relatedTarget:function(t){return t.relatedTarget===void 0?t.fromElement===t.srcElement?t.toElement:t.fromElement:t.relatedTarget},movementX:function(t){return"movementX"in t?t.movementX:(t!==Ul&&(Ul&&t.type==="mousemove"?(jc=t.screenX-Ul.screenX,Hc=t.screenY-Ul.screenY):Hc=jc=0,Ul=t),jc)},movementY:function(t){return"movementY"in t?t.movementY:Hc}}),to=de(Ji),Hm=S({},Ji,{dataTransfer:0}),Nm=de(Hm),Bm=S({},Bl,{relatedTarget:0}),Nc=de(Bm),Um=S({},ha,{animationName:0,elapsedTime:0,pseudoElement:0}),Qm=de(Um),zm=S({},ha,{clipboardData:function(t){return"clipboardData"in t?t.clipboardData:window.clipboardData}}),Ym=de(zm),Lm=S({},ha,{data:0}),eo=de(Lm),Gm={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Xm={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Vm={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function Zm(t){var e=this.nativeEvent;return e.getModifierState?e.getModifierState(t):(t=Vm[t])?!!e[t]:!1}function Bc(){return Zm}var qm=S({},Bl,{key:function(t){if(t.key){var e=Gm[t.key]||t.key;if(e!=="Unidentified")return e}return t.type==="keypress"?(t=Ii(t),t===13?"Enter":String.fromCharCode(t)):t.type==="keydown"||t.type==="keyup"?Xm[t.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Bc,charCode:function(t){return t.type==="keypress"?Ii(t):0},keyCode:function(t){return t.type==="keydown"||t.type==="keyup"?t.keyCode:0},which:function(t){return t.type==="keypress"?Ii(t):t.type==="keydown"||t.type==="keyup"?t.keyCode:0}}),Im=de(qm),Km=S({},Ji,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),no=de(Km),km=S({},Bl,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Bc}),Jm=de(km),Fm=S({},ha,{propertyName:0,elapsedTime:0,pseudoElement:0}),Wm=de(Fm),_m=S({},Ji,{deltaX:function(t){return"deltaX"in t?t.deltaX:"wheelDeltaX"in t?-t.wheelDeltaX:0},deltaY:function(t){return"deltaY"in t?t.deltaY:"wheelDeltaY"in t?-t.wheelDeltaY:"wheelDelta"in t?-t.wheelDelta:0},deltaZ:0,deltaMode:0}),Pm=de(_m),$m=S({},ha,{newState:0,oldState:0}),tg=de($m),eg=[9,13,27,32],Uc=en&&"CompositionEvent"in window,Ql=null;en&&"documentMode"in document&&(Ql=document.documentMode);var ng=en&&"TextEvent"in window&&!Ql,ao=en&&(!Uc||Ql&&8<Ql&&11>=Ql),lo=" ",io=!1;function uo(t,e){switch(t){case"keyup":return eg.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function co(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ka=!1;function ag(t,e){switch(t){case"compositionend":return co(e);case"keypress":return e.which!==32?null:(io=!0,lo);case"textInput":return t=e.data,t===lo&&io?null:t;default:return null}}function lg(t,e){if(Ka)return t==="compositionend"||!Uc&&uo(t,e)?(t=Pr(),qi=Mc=jn=null,Ka=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1<e.char.length)return e.char;if(e.which)return String.fromCharCode(e.which)}return null;case"compositionend":return ao&&e.locale!=="ko"?null:e.data;default:return null}}var ig={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function so(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e==="input"?!!ig[t.type]:e==="textarea"}function fo(t,e,n,a){qa?Ia?Ia.push(a):Ia=[a]:qa=a,e=zu(e,"onChange"),0<e.length&&(n=new ki("onChange","change",null,n,a),t.push({event:n,listeners:e}))}var zl=null,Yl=null;function ug(t){Kd(t,0)}function Fi(t){var e=jl(t);if(qr(e))return t}function ro(t,e){if(t==="change")return e}var oo=!1;if(en){var Qc;if(en){var zc="oninput"in document;if(!zc){var ho=document.createElement("div");ho.setAttribute("oninput","return;"),zc=typeof ho.oninput=="function"}Qc=zc}else Qc=!1;oo=Qc&&(!document.documentMode||9<document.documentMode)}function mo(){zl&&(zl.detachEvent("onpropertychange",go),Yl=zl=null)}function go(t){if(t.propertyName==="value"&&Fi(Yl)){var e=[];fo(e,Yl,t,wc(t)),_r(ug,e)}}function cg(t,e,n){t==="focusin"?(mo(),zl=e,Yl=n,zl.attachEvent("onpropertychange",go)):t==="focusout"&&mo()}function sg(t){if(t==="selectionchange"||t==="keyup"||t==="keydown")return Fi(Yl)}function fg(t,e){if(t==="click")return Fi(e)}function rg(t,e){if(t==="input"||t==="change")return Fi(e)}function og(t,e){return t===e&&(t!==0||1/t===1/e)||t!==t&&e!==e}var Se=typeof Object.is=="function"?Object.is:og;function Ll(t,e){if(Se(t,e))return!0;if(typeof t!="object"||t===null||typeof e!="object"||e===null)return!1;var n=Object.keys(t),a=Object.keys(e);if(n.length!==a.length)return!1;for(a=0;a<n.length;a++){var i=n[a];if(!mc.call(e,i)||!Se(t[i],e[i]))return!1}return!0}function Ao(t){for(;t&&t.firstChild;)t=t.firstChild;return t}function vo(t,e){var n=Ao(t);t=0;for(var a;n;){if(n.nodeType===3){if(a=t+n.textContent.length,t<=e&&a>=e)return{node:n,offset:e-t};t=a}t:{for(;n;){if(n.nextSibling){n=n.nextSibling;break t}n=n.parentNode}n=void 0}n=Ao(n)}}function yo(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?yo(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function Eo(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=Vi(t.document);e instanceof t.HTMLIFrameElement;){try{var n=typeof e.contentWindow.location.href=="string"}catch{n=!1}if(n)t=e.contentWindow;else break;e=Vi(t.document)}return e}function Yc(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var dg=en&&"documentMode"in document&&11>=document.documentMode,ka=null,Lc=null,Gl=null,Gc=!1;function po(t,e,n){var a=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Gc||ka==null||ka!==Vi(a)||(a=ka,"selectionStart"in a&&Yc(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Gl&&Ll(Gl,a)||(Gl=a,a=zu(Lc,"onSelect"),0<a.length&&(e=new ki("onSelect","select",null,e,n),t.push({event:e,listeners:a}),e.target=ka)))}function ma(t,e){var n={};return n[t.toLowerCase()]=e.toLowerCase(),n["Webkit"+t]="webkit"+e,n["Moz"+t]="moz"+e,n}var Ja={animationend:ma("Animation","AnimationEnd"),animationiteration:ma("Animation","AnimationIteration"),animationstart:ma("Animation","AnimationStart"),transitionrun:ma("Transition","TransitionRun"),transitionstart:ma("Transition","TransitionStart"),transitioncancel:ma("Transition","TransitionCancel"),transitionend:ma("Transition","TransitionEnd")},Xc={},bo={};en&&(bo=document.createElement("div").style,"AnimationEvent"in window||(delete Ja.animationend.animation,delete Ja.animationiteration.animation,delete Ja.animationstart.animation),"TransitionEvent"in window||delete Ja.transitionend.transition);function ga(t){if(Xc[t])return Xc[t];if(!Ja[t])return t;var e=Ja[t],n;for(n in e)if(e.hasOwnProperty(n)&&n in bo)return Xc[t]=e[n];return t}var xo=ga("animationend"),So=ga("animationiteration"),To=ga("animationstart"),hg=ga("transitionrun"),mg=ga("transitionstart"),gg=ga("transitioncancel"),Co=ga("transitionend"),Oo=new Map,Vc="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");Vc.push("scrollEnd");function qe(t,e){Oo.set(t,e),da(e,[t])}var Wi=typeof reportError=="function"?reportError:function(t){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var e=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof t=="object"&&t!==null&&typeof t.message=="string"?String(t.message):String(t),error:t});if(!window.dispatchEvent(e))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",t);return}console.error(t)},Ne=[],Fa=0,Zc=0;function _i(){for(var t=Fa,e=Zc=Fa=0;e<t;){var n=Ne[e];Ne[e++]=null;var a=Ne[e];Ne[e++]=null;var i=Ne[e];Ne[e++]=null;var s=Ne[e];if(Ne[e++]=null,a!==null&&i!==null){var d=a.pending;d===null?i.next=i:(i.next=d.next,d.next=i),a.pending=i}s!==0&&wo(n,i,s)}}function Pi(t,e,n,a){Ne[Fa++]=t,Ne[Fa++]=e,Ne[Fa++]=n,Ne[Fa++]=a,Zc|=a,t.lanes|=a,t=t.alternate,t!==null&&(t.lanes|=a)}function qc(t,e,n,a){return Pi(t,e,n,a),$i(t)}function Aa(t,e){return Pi(t,null,null,e),$i(t)}function wo(t,e,n){t.lanes|=n;var a=t.alternate;a!==null&&(a.lanes|=n);for(var i=!1,s=t.return;s!==null;)s.childLanes|=n,a=s.alternate,a!==null&&(a.childLanes|=n),s.tag===22&&(t=s.stateNode,t===null||t._visibility&1||(i=!0)),t=s,s=s.return;return t.tag===3?(s=t.stateNode,i&&e!==null&&(i=31-xe(n),t=s.hiddenUpdates,a=t[i],a===null?t[i]=[e]:a.push(e),e.lane=n|536870912),s):null}function $i(t){if(50<si)throw si=0,$s=null,Error(f(185));for(var e=t.return;e!==null;)t=e,e=t.return;return t.tag===3?t.stateNode:null}var Wa={};function Ag(t,e,n,a){this.tag=t,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=e,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=a,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Te(t,e,n,a){return new Ag(t,e,n,a)}function Ic(t){return t=t.prototype,!(!t||!t.isReactComponent)}function nn(t,e){var n=t.alternate;return n===null?(n=Te(t.tag,e,t.key,t.mode),n.elementType=t.elementType,n.type=t.type,n.stateNode=t.stateNode,n.alternate=t,t.alternate=n):(n.pendingProps=e,n.type=t.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=t.flags&65011712,n.childLanes=t.childLanes,n.lanes=t.lanes,n.child=t.child,n.memoizedProps=t.memoizedProps,n.memoizedState=t.memoizedState,n.updateQueue=t.updateQueue,e=t.dependencies,n.dependencies=e===null?null:{lanes:e.lanes,firstContext:e.firstContext},n.sibling=t.sibling,n.index=t.index,n.ref=t.ref,n.refCleanup=t.refCleanup,n}function Ro(t,e){t.flags&=65011714;var n=t.alternate;return n===null?(t.childLanes=0,t.lanes=e,t.child=null,t.subtreeFlags=0,t.memoizedProps=null,t.memoizedState=null,t.updateQueue=null,t.dependencies=null,t.stateNode=null):(t.childLanes=n.childLanes,t.lanes=n.lanes,t.child=n.child,t.subtreeFlags=0,t.deletions=null,t.memoizedProps=n.memoizedProps,t.memoizedState=n.memoizedState,t.updateQueue=n.updateQueue,t.type=n.type,e=n.dependencies,t.dependencies=e===null?null:{lanes:e.lanes,firstContext:e.firstContext}),t}function tu(t,e,n,a,i,s){var d=0;if(a=t,typeof t=="function")Ic(t)&&(d=1);else if(typeof t=="string")d=bA(t,n,et.current)?26:t==="html"||t==="head"||t==="body"?27:5;else t:switch(t){case K:return t=Te(31,n,e,i),t.elementType=K,t.lanes=s,t;case b:return va(n.children,i,s,e);case p:d=8,i|=24;break;case x:return t=Te(12,n,e,i|2),t.elementType=x,t.lanes=s,t;case F:return t=Te(13,n,e,i),t.elementType=F,t.lanes=s,t;case j:return t=Te(19,n,e,i),t.elementType=j,t.lanes=s,t;default:if(typeof t=="object"&&t!==null)switch(t.$$typeof){case U:d=10;break t;case R:d=9;break t;case Z:d=11;break t;case D:d=14;break t;case N:d=16,a=null;break t}d=29,n=Error(f(130,t===null?"null":typeof t,"")),a=null}return e=Te(d,n,e,i),e.elementType=t,e.type=a,e.lanes=s,e}function va(t,e,n,a){return t=Te(7,t,a,e),t.lanes=n,t}function Kc(t,e,n){return t=Te(6,t,null,e),t.lanes=n,t}function Do(t){var e=Te(18,null,null,0);return e.stateNode=t,e}function kc(t,e,n){return e=Te(4,t.children!==null?t.children:[],t.key,e),e.lanes=n,e.stateNode={containerInfo:t.containerInfo,pendingChildren:null,implementation:t.implementation},e}var Mo=new WeakMap;function Be(t,e){if(typeof t=="object"&&t!==null){var n=Mo.get(t);return n!==void 0?n:(e={value:t,source:e,stack:Bi(e)},Mo.set(t,e),e)}return{value:t,source:e,stack:Bi(e)}}var _a=[],Pa=0,eu=null,Xl=0,Ue=[],Qe=0,Hn=null,Je=1,Fe="";function an(t,e){_a[Pa++]=Xl,_a[Pa++]=eu,eu=t,Xl=e}function jo(t,e,n){Ue[Qe++]=Je,Ue[Qe++]=Fe,Ue[Qe++]=Hn,Hn=t;var a=Je;t=Fe;var i=32-xe(a)-1;a&=~(1<<i),n+=1;var s=32-xe(e)+i;if(30<s){var d=i-i%5;s=(a&(1<<d)-1).toString(32),a>>=d,i-=d,Je=1<<32-xe(e)+i|n<<i|a,Fe=s+t}else Je=1<<s|n<<i|a,Fe=t}function Jc(t){t.return!==null&&(an(t,1),jo(t,1,0))}function Fc(t){for(;t===eu;)eu=_a[--Pa],_a[Pa]=null,Xl=_a[--Pa],_a[Pa]=null;for(;t===Hn;)Hn=Ue[--Qe],Ue[Qe]=null,Fe=Ue[--Qe],Ue[Qe]=null,Je=Ue[--Qe],Ue[Qe]=null}function Ho(t,e){Ue[Qe++]=Je,Ue[Qe++]=Fe,Ue[Qe++]=Hn,Je=e.id,Fe=e.overflow,Hn=t}var ee=null,Ht=null,bt=!1,Nn=null,ze=!1,Wc=Error(f(519));function Bn(t){var e=Error(f(418,1<arguments.length&&arguments[1]!==void 0&&arguments[1]?"text":"HTML",""));throw Vl(Be(e,t)),Wc}function No(t){var e=t.stateNode,n=t.type,a=t.memoizedProps;switch(e[te]=t,e[oe]=a,n){case"dialog":yt("cancel",e),yt("close",e);break;case"iframe":case"object":case"embed":yt("load",e);break;case"video":case"audio":for(n=0;n<ri.length;n++)yt(ri[n],e);break;case"source":yt("error",e);break;case"img":case"image":case"link":yt("error",e),yt("load",e);break;case"details":yt("toggle",e);break;case"input":yt("invalid",e),Ir(e,a.value,a.defaultValue,a.checked,a.defaultChecked,a.type,a.name,!0);break;case"select":yt("invalid",e);break;case"textarea":yt("invalid",e),kr(e,a.value,a.defaultValue,a.children)}n=a.children,typeof n!="string"&&typeof n!="number"&&typeof n!="bigint"||e.textContent===""+n||a.suppressHydrationWarning===!0||Wd(e.textContent,n)?(a.popover!=null&&(yt("beforetoggle",e),yt("toggle",e)),a.onScroll!=null&&yt("scroll",e),a.onScrollEnd!=null&&yt("scrollend",e),a.onClick!=null&&(e.onclick=tn),e=!0):e=!1,e||Bn(t,!0)}function Bo(t){for(ee=t.return;ee;)switch(ee.tag){case 5:case 31:case 13:ze=!1;return;case 27:case 3:ze=!0;return;default:ee=ee.return}}function $a(t){if(t!==ee)return!1;if(!bt)return Bo(t),bt=!0,!1;var e=t.tag,n;if((n=e!==3&&e!==27)&&((n=e===5)&&(n=t.type,n=!(n!=="form"&&n!=="button")||gf(t.type,t.memoizedProps)),n=!n),n&&Ht&&Bn(t),Bo(t),e===13){if(t=t.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(317));Ht=i1(t)}else if(e===31){if(t=t.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(317));Ht=i1(t)}else e===27?(e=Ht,Jn(t.type)?(t=pf,pf=null,Ht=t):Ht=e):Ht=ee?Le(t.stateNode.nextSibling):null;return!0}function ya(){Ht=ee=null,bt=!1}function _c(){var t=Nn;return t!==null&&(Ae===null?Ae=t:Ae.push.apply(Ae,t),Nn=null),t}function Vl(t){Nn===null?Nn=[t]:Nn.push(t)}var Pc=C(null),Ea=null,ln=null;function Un(t,e,n){W(Pc,e._currentValue),e._currentValue=n}function un(t){t._currentValue=Pc.current,L(Pc)}function $c(t,e,n){for(;t!==null;){var a=t.alternate;if((t.childLanes&e)!==e?(t.childLanes|=e,a!==null&&(a.childLanes|=e)):a!==null&&(a.childLanes&e)!==e&&(a.childLanes|=e),t===n)break;t=t.return}}function ts(t,e,n,a){var i=t.child;for(i!==null&&(i.return=t);i!==null;){var s=i.dependencies;if(s!==null){var d=i.child;s=s.firstContext;t:for(;s!==null;){var g=s;s=i;for(var T=0;T<e.length;T++)if(g.context===e[T]){s.lanes|=n,g=s.alternate,g!==null&&(g.lanes|=n),$c(s.return,n,t),a||(d=null);break t}s=g.next}}else if(i.tag===18){if(d=i.return,d===null)throw Error(f(341));d.lanes|=n,s=d.alternate,s!==null&&(s.lanes|=n),$c(d,n,t),d=null}else d=i.child;if(d!==null)d.return=i;else for(d=i;d!==null;){if(d===t){d=null;break}if(i=d.sibling,i!==null){i.return=d.return,d=i;break}d=d.return}i=d}}function tl(t,e,n,a){t=null;for(var i=e,s=!1;i!==null;){if(!s){if((i.flags&524288)!==0)s=!0;else if((i.flags&262144)!==0)break}if(i.tag===10){var d=i.alternate;if(d===null)throw Error(f(387));if(d=d.memoizedProps,d!==null){var g=i.type;Se(i.pendingProps.value,d.value)||(t!==null?t.push(g):t=[g])}}else if(i===gt.current){if(d=i.alternate,d===null)throw Error(f(387));d.memoizedState.memoizedState!==i.memoizedState.memoizedState&&(t!==null?t.push(gi):t=[gi])}i=i.return}t!==null&&ts(e,t,n,a),e.flags|=262144}function nu(t){for(t=t.firstContext;t!==null;){if(!Se(t.context._currentValue,t.memoizedValue))return!0;t=t.next}return!1}function pa(t){Ea=t,ln=null,t=t.dependencies,t!==null&&(t.firstContext=null)}function ne(t){return Uo(Ea,t)}function au(t,e){return Ea===null&&pa(t),Uo(t,e)}function Uo(t,e){var n=e._currentValue;if(e={context:e,memoizedValue:n,next:null},ln===null){if(t===null)throw Error(f(308));ln=e,t.dependencies={lanes:0,firstContext:e},t.flags|=524288}else ln=ln.next=e;return n}var vg=typeof AbortController<"u"?AbortController:function(){var t=[],e=this.signal={aborted:!1,addEventListener:function(n,a){t.push(a)}};this.abort=function(){e.aborted=!0,t.forEach(function(n){return n()})}},yg=l.unstable_scheduleCallback,Eg=l.unstable_NormalPriority,Zt={$$typeof:U,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function es(){return{controller:new vg,data:new Map,refCount:0}}function Zl(t){t.refCount--,t.refCount===0&&yg(Eg,function(){t.controller.abort()})}var ql=null,ns=0,el=0,nl=null;function pg(t,e){if(ql===null){var n=ql=[];ns=0,el=uf(),nl={status:"pending",value:void 0,then:function(a){n.push(a)}}}return ns++,e.then(Qo,Qo),e}function Qo(){if(--ns===0&&ql!==null){nl!==null&&(nl.status="fulfilled");var t=ql;ql=null,el=0,nl=null;for(var e=0;e<t.length;e++)(0,t[e])()}}function bg(t,e){var n=[],a={status:"pending",value:null,reason:null,then:function(i){n.push(i)}};return t.then(function(){a.status="fulfilled",a.value=e;for(var i=0;i<n.length;i++)(0,n[i])(e)},function(i){for(a.status="rejected",a.reason=i,i=0;i<n.length;i++)(0,n[i])(void 0)}),a}var zo=H.S;H.S=function(t,e){pd=pe(),typeof e=="object"&&e!==null&&typeof e.then=="function"&&pg(t,e),zo!==null&&zo(t,e)};var ba=C(null);function as(){var t=ba.current;return t!==null?t:jt.pooledCache}function lu(t,e){e===null?W(ba,ba.current):W(ba,e.pool)}function Yo(){var t=as();return t===null?null:{parent:Zt._currentValue,pool:t}}var al=Error(f(460)),ls=Error(f(474)),iu=Error(f(542)),uu={then:function(){}};function Lo(t){return t=t.status,t==="fulfilled"||t==="rejected"}function Go(t,e,n){switch(n=t[n],n===void 0?t.push(e):n!==e&&(e.then(tn,tn),e=n),e.status){case"fulfilled":return e.value;case"rejected":throw t=e.reason,Vo(t),t;default:if(typeof e.status=="string")e.then(tn,tn);else{if(t=jt,t!==null&&100<t.shellSuspendCounter)throw Error(f(482));t=e,t.status="pending",t.then(function(a){if(e.status==="pending"){var i=e;i.status="fulfilled",i.value=a}},function(a){if(e.status==="pending"){var i=e;i.status="rejected",i.reason=a}})}switch(e.status){case"fulfilled":return e.value;case"rejected":throw t=e.reason,Vo(t),t}throw Sa=e,al}}function xa(t){try{var e=t._init;return e(t._payload)}catch(n){throw n!==null&&typeof n=="object"&&typeof n.then=="function"?(Sa=n,al):n}}var Sa=null;function Xo(){if(Sa===null)throw Error(f(459));var t=Sa;return Sa=null,t}function Vo(t){if(t===al||t===iu)throw Error(f(483))}var ll=null,Il=0;function cu(t){var e=Il;return Il+=1,ll===null&&(ll=[]),Go(ll,t,e)}function Kl(t,e){e=e.props.ref,t.ref=e!==void 0?e:null}function su(t,e){throw e.$$typeof===O?Error(f(525)):(t=Object.prototype.toString.call(e),Error(f(31,t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)))}function Zo(t){function e(M,w){if(t){var Q=M.deletions;Q===null?(M.deletions=[w],M.flags|=16):Q.push(w)}}function n(M,w){if(!t)return null;for(;w!==null;)e(M,w),w=w.sibling;return null}function a(M){for(var w=new Map;M!==null;)M.key!==null?w.set(M.key,M):w.set(M.index,M),M=M.sibling;return w}function i(M,w){return M=nn(M,w),M.index=0,M.sibling=null,M}function s(M,w,Q){return M.index=Q,t?(Q=M.alternate,Q!==null?(Q=Q.index,Q<w?(M.flags|=67108866,w):Q):(M.flags|=67108866,w)):(M.flags|=1048576,w)}function d(M){return t&&M.alternate===null&&(M.flags|=67108866),M}function g(M,w,Q,q){return w===null||w.tag!==6?(w=Kc(Q,M.mode,q),w.return=M,w):(w=i(w,Q),w.return=M,w)}function T(M,w,Q,q){var ut=Q.type;return ut===b?V(M,w,Q.props.children,q,Q.key):w!==null&&(w.elementType===ut||typeof ut=="object"&&ut!==null&&ut.$$typeof===N&&xa(ut)===w.type)?(w=i(w,Q.props),Kl(w,Q),w.return=M,w):(w=tu(Q.type,Q.key,Q.props,null,M.mode,q),Kl(w,Q),w.return=M,w)}function z(M,w,Q,q){return w===null||w.tag!==4||w.stateNode.containerInfo!==Q.containerInfo||w.stateNode.implementation!==Q.implementation?(w=kc(Q,M.mode,q),w.return=M,w):(w=i(w,Q.children||[]),w.return=M,w)}function V(M,w,Q,q,ut){return w===null||w.tag!==7?(w=va(Q,M.mode,q,ut),w.return=M,w):(w=i(w,Q),w.return=M,w)}function I(M,w,Q){if(typeof w=="string"&&w!==""||typeof w=="number"||typeof w=="bigint")return w=Kc(""+w,M.mode,Q),w.return=M,w;if(typeof w=="object"&&w!==null){switch(w.$$typeof){case X:return Q=tu(w.type,w.key,w.props,null,M.mode,Q),Kl(Q,w),Q.return=M,Q;case B:return w=kc(w,M.mode,Q),w.return=M,w;case N:return w=xa(w),I(M,w,Q)}if(it(w)||nt(w))return w=va(w,M.mode,Q,null),w.return=M,w;if(typeof w.then=="function")return I(M,cu(w),Q);if(w.$$typeof===U)return I(M,au(M,w),Q);su(M,w)}return null}function Y(M,w,Q,q){var ut=w!==null?w.key:null;if(typeof Q=="string"&&Q!==""||typeof Q=="number"||typeof Q=="bigint")return ut!==null?null:g(M,w,""+Q,q);if(typeof Q=="object"&&Q!==null){switch(Q.$$typeof){case X:return Q.key===ut?T(M,w,Q,q):null;case B:return Q.key===ut?z(M,w,Q,q):null;case N:return Q=xa(Q),Y(M,w,Q,q)}if(it(Q)||nt(Q))return ut!==null?null:V(M,w,Q,q,null);if(typeof Q.then=="function")return Y(M,w,cu(Q),q);if(Q.$$typeof===U)return Y(M,w,au(M,Q),q);su(M,Q)}return null}function G(M,w,Q,q,ut){if(typeof q=="string"&&q!==""||typeof q=="number"||typeof q=="bigint")return M=M.get(Q)||null,g(w,M,""+q,ut);if(typeof q=="object"&&q!==null){switch(q.$$typeof){case X:return M=M.get(q.key===null?Q:q.key)||null,T(w,M,q,ut);case B:return M=M.get(q.key===null?Q:q.key)||null,z(w,M,q,ut);case N:return q=xa(q),G(M,w,Q,q,ut)}if(it(q)||nt(q))return M=M.get(Q)||null,V(w,M,q,ut,null);if(typeof q.then=="function")return G(M,w,Q,cu(q),ut);if(q.$$typeof===U)return G(M,w,Q,au(w,q),ut);su(w,q)}return null}function at(M,w,Q,q){for(var ut=null,St=null,lt=w,At=w=0,pt=null;lt!==null&&At<Q.length;At++){lt.index>At?(pt=lt,lt=null):pt=lt.sibling;var Tt=Y(M,lt,Q[At],q);if(Tt===null){lt===null&&(lt=pt);break}t&&lt&&Tt.alternate===null&&e(M,lt),w=s(Tt,w,At),St===null?ut=Tt:St.sibling=Tt,St=Tt,lt=pt}if(At===Q.length)return n(M,lt),bt&&an(M,At),ut;if(lt===null){for(;At<Q.length;At++)lt=I(M,Q[At],q),lt!==null&&(w=s(lt,w,At),St===null?ut=lt:St.sibling=lt,St=lt);return bt&&an(M,At),ut}for(lt=a(lt);At<Q.length;At++)pt=G(lt,M,At,Q[At],q),pt!==null&&(t&&pt.alternate!==null&&lt.delete(pt.key===null?At:pt.key),w=s(pt,w,At),St===null?ut=pt:St.sibling=pt,St=pt);return t&&lt.forEach(function($n){return e(M,$n)}),bt&&an(M,At),ut}function ft(M,w,Q,q){if(Q==null)throw Error(f(151));for(var ut=null,St=null,lt=w,At=w=0,pt=null,Tt=Q.next();lt!==null&&!Tt.done;At++,Tt=Q.next()){lt.index>At?(pt=lt,lt=null):pt=lt.sibling;var $n=Y(M,lt,Tt.value,q);if($n===null){lt===null&&(lt=pt);break}t&&lt&&$n.alternate===null&&e(M,lt),w=s($n,w,At),St===null?ut=$n:St.sibling=$n,St=$n,lt=pt}if(Tt.done)return n(M,lt),bt&&an(M,At),ut;if(lt===null){for(;!Tt.done;At++,Tt=Q.next())Tt=I(M,Tt.value,q),Tt!==null&&(w=s(Tt,w,At),St===null?ut=Tt:St.sibling=Tt,St=Tt);return bt&&an(M,At),ut}for(lt=a(lt);!Tt.done;At++,Tt=Q.next())Tt=G(lt,M,At,Tt.value,q),Tt!==null&&(t&&Tt.alternate!==null&&lt.delete(Tt.key===null?At:Tt.key),w=s(Tt,w,At),St===null?ut=Tt:St.sibling=Tt,St=Tt);return t&&lt.forEach(function(HA){return e(M,HA)}),bt&&an(M,At),ut}function Mt(M,w,Q,q){if(typeof Q=="object"&&Q!==null&&Q.type===b&&Q.key===null&&(Q=Q.props.children),typeof Q=="object"&&Q!==null){switch(Q.$$typeof){case X:t:{for(var ut=Q.key;w!==null;){if(w.key===ut){if(ut=Q.type,ut===b){if(w.tag===7){n(M,w.sibling),q=i(w,Q.props.children),q.return=M,M=q;break t}}else if(w.elementType===ut||typeof ut=="object"&&ut!==null&&ut.$$typeof===N&&xa(ut)===w.type){n(M,w.sibling),q=i(w,Q.props),Kl(q,Q),q.return=M,M=q;break t}n(M,w);break}else e(M,w);w=w.sibling}Q.type===b?(q=va(Q.props.children,M.mode,q,Q.key),q.return=M,M=q):(q=tu(Q.type,Q.key,Q.props,null,M.mode,q),Kl(q,Q),q.return=M,M=q)}return d(M);case B:t:{for(ut=Q.key;w!==null;){if(w.key===ut)if(w.tag===4&&w.stateNode.containerInfo===Q.containerInfo&&w.stateNode.implementation===Q.implementation){n(M,w.sibling),q=i(w,Q.children||[]),q.return=M,M=q;break t}else{n(M,w);break}else e(M,w);w=w.sibling}q=kc(Q,M.mode,q),q.return=M,M=q}return d(M);case N:return Q=xa(Q),Mt(M,w,Q,q)}if(it(Q))return at(M,w,Q,q);if(nt(Q)){if(ut=nt(Q),typeof ut!="function")throw Error(f(150));return Q=ut.call(Q),ft(M,w,Q,q)}if(typeof Q.then=="function")return Mt(M,w,cu(Q),q);if(Q.$$typeof===U)return Mt(M,w,au(M,Q),q);su(M,Q)}return typeof Q=="string"&&Q!==""||typeof Q=="number"||typeof Q=="bigint"?(Q=""+Q,w!==null&&w.tag===6?(n(M,w.sibling),q=i(w,Q),q.return=M,M=q):(n(M,w),q=Kc(Q,M.mode,q),q.return=M,M=q),d(M)):n(M,w)}return function(M,w,Q,q){try{Il=0;var ut=Mt(M,w,Q,q);return ll=null,ut}catch(lt){if(lt===al||lt===iu)throw lt;var St=Te(29,lt,null,M.mode);return St.lanes=q,St.return=M,St}finally{}}}var Ta=Zo(!0),qo=Zo(!1),Qn=!1;function is(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function us(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function zn(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Yn(t,e,n){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(Ct&2)!==0){var i=a.pending;return i===null?e.next=e:(e.next=i.next,i.next=e),a.pending=e,e=$i(t),wo(t,null,n),e}return Pi(t,a,e,n),$i(t)}function kl(t,e,n){if(e=e.updateQueue,e!==null&&(e=e.shared,(n&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,n|=a,e.lanes=n,Br(t,n)}}function cs(t,e){var n=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,n===a)){var i=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var d={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};s===null?i=s=d:s=s.next=d,n=n.next}while(n!==null);s===null?i=s=e:s=s.next=e}else i=s=e;n={baseState:a.baseState,firstBaseUpdate:i,lastBaseUpdate:s,shared:a.shared,callbacks:a.callbacks},t.updateQueue=n;return}t=n.lastBaseUpdate,t===null?n.firstBaseUpdate=e:t.next=e,n.lastBaseUpdate=e}var ss=!1;function Jl(){if(ss){var t=nl;if(t!==null)throw t}}function Fl(t,e,n,a){ss=!1;var i=t.updateQueue;Qn=!1;var s=i.firstBaseUpdate,d=i.lastBaseUpdate,g=i.shared.pending;if(g!==null){i.shared.pending=null;var T=g,z=T.next;T.next=null,d===null?s=z:d.next=z,d=T;var V=t.alternate;V!==null&&(V=V.updateQueue,g=V.lastBaseUpdate,g!==d&&(g===null?V.firstBaseUpdate=z:g.next=z,V.lastBaseUpdate=T))}if(s!==null){var I=i.baseState;d=0,V=z=T=null,g=s;do{var Y=g.lane&-536870913,G=Y!==g.lane;if(G?(Et&Y)===Y:(a&Y)===Y){Y!==0&&Y===el&&(ss=!0),V!==null&&(V=V.next={lane:0,tag:g.tag,payload:g.payload,callback:null,next:null});t:{var at=t,ft=g;Y=e;var Mt=n;switch(ft.tag){case 1:if(at=ft.payload,typeof at=="function"){I=at.call(Mt,I,Y);break t}I=at;break t;case 3:at.flags=at.flags&-65537|128;case 0:if(at=ft.payload,Y=typeof at=="function"?at.call(Mt,I,Y):at,Y==null)break t;I=S({},I,Y);break t;case 2:Qn=!0}}Y=g.callback,Y!==null&&(t.flags|=64,G&&(t.flags|=8192),G=i.callbacks,G===null?i.callbacks=[Y]:G.push(Y))}else G={lane:Y,tag:g.tag,payload:g.payload,callback:g.callback,next:null},V===null?(z=V=G,T=I):V=V.next=G,d|=Y;if(g=g.next,g===null){if(g=i.shared.pending,g===null)break;G=g,g=G.next,G.next=null,i.lastBaseUpdate=G,i.shared.pending=null}}while(!0);V===null&&(T=I),i.baseState=T,i.firstBaseUpdate=z,i.lastBaseUpdate=V,s===null&&(i.shared.lanes=0),Zn|=d,t.lanes=d,t.memoizedState=I}}function Io(t,e){if(typeof t!="function")throw Error(f(191,t));t.call(e)}function Ko(t,e){var n=t.callbacks;if(n!==null)for(t.callbacks=null,t=0;t<n.length;t++)Io(n[t],e)}var il=C(null),fu=C(0);function ko(t,e){t=gn,W(fu,t),W(il,e),gn=t|e.baseLanes}function fs(){W(fu,gn),W(il,il.current)}function rs(){gn=fu.current,L(il),L(fu)}var Ce=C(null),Ye=null;function Ln(t){var e=t.alternate;W(Xt,Xt.current&1),W(Ce,t),Ye===null&&(e===null||il.current!==null||e.memoizedState!==null)&&(Ye=t)}function os(t){W(Xt,Xt.current),W(Ce,t),Ye===null&&(Ye=t)}function Jo(t){t.tag===22?(W(Xt,Xt.current),W(Ce,t),Ye===null&&(Ye=t)):Gn()}function Gn(){W(Xt,Xt.current),W(Ce,Ce.current)}function Oe(t){L(Ce),Ye===t&&(Ye=null),L(Xt)}var Xt=C(0);function ru(t){for(var e=t;e!==null;){if(e.tag===13){var n=e.memoizedState;if(n!==null&&(n=n.dehydrated,n===null||yf(n)||Ef(n)))return e}else if(e.tag===19&&(e.memoizedProps.revealOrder==="forwards"||e.memoizedProps.revealOrder==="backwards"||e.memoizedProps.revealOrder==="unstable_legacy-backwards"||e.memoizedProps.revealOrder==="together")){if((e.flags&128)!==0)return e}else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break;for(;e.sibling===null;){if(e.return===null||e.return===t)return null;e=e.return}e.sibling.return=e.return,e=e.sibling}return null}var cn=0,mt=null,Rt=null,qt=null,ou=!1,ul=!1,Ca=!1,du=0,Wl=0,cl=null,xg=0;function Lt(){throw Error(f(321))}function ds(t,e){if(e===null)return!1;for(var n=0;n<e.length&&n<t.length;n++)if(!Se(t[n],e[n]))return!1;return!0}function hs(t,e,n,a,i,s){return cn=s,mt=e,e.memoizedState=null,e.updateQueue=null,e.lanes=0,H.H=t===null||t.memoizedState===null?j0:Rs,Ca=!1,s=n(a,i),Ca=!1,ul&&(s=Wo(e,n,a,i)),Fo(t),s}function Fo(t){H.H=$l;var e=Rt!==null&&Rt.next!==null;if(cn=0,qt=Rt=mt=null,ou=!1,Wl=0,cl=null,e)throw Error(f(300));t===null||It||(t=t.dependencies,t!==null&&nu(t)&&(It=!0))}function Wo(t,e,n,a){mt=t;var i=0;do{if(ul&&(cl=null),Wl=0,ul=!1,25<=i)throw Error(f(301));if(i+=1,qt=Rt=null,t.updateQueue!=null){var s=t.updateQueue;s.lastEffect=null,s.events=null,s.stores=null,s.memoCache!=null&&(s.memoCache.index=0)}H.H=H0,s=e(n,a)}while(ul);return s}function Sg(){var t=H.H,e=t.useState()[0];return e=typeof e.then=="function"?_l(e):e,t=t.useState()[0],(Rt!==null?Rt.memoizedState:null)!==t&&(mt.flags|=1024),e}function ms(){var t=du!==0;return du=0,t}function gs(t,e,n){e.updateQueue=t.updateQueue,e.flags&=-2053,t.lanes&=~n}function As(t){if(ou){for(t=t.memoizedState;t!==null;){var e=t.queue;e!==null&&(e.pending=null),t=t.next}ou=!1}cn=0,qt=Rt=mt=null,ul=!1,Wl=du=0,cl=null}function fe(){var t={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return qt===null?mt.memoizedState=qt=t:qt=qt.next=t,qt}function Vt(){if(Rt===null){var t=mt.alternate;t=t!==null?t.memoizedState:null}else t=Rt.next;var e=qt===null?mt.memoizedState:qt.next;if(e!==null)qt=e,Rt=t;else{if(t===null)throw mt.alternate===null?Error(f(467)):Error(f(310));Rt=t,t={memoizedState:Rt.memoizedState,baseState:Rt.baseState,baseQueue:Rt.baseQueue,queue:Rt.queue,next:null},qt===null?mt.memoizedState=qt=t:qt=qt.next=t}return qt}function hu(){return{lastEffect:null,events:null,stores:null,memoCache:null}}function _l(t){var e=Wl;return Wl+=1,cl===null&&(cl=[]),t=Go(cl,t,e),e=mt,(qt===null?e.memoizedState:qt.next)===null&&(e=e.alternate,H.H=e===null||e.memoizedState===null?j0:Rs),t}function mu(t){if(t!==null&&typeof t=="object"){if(typeof t.then=="function")return _l(t);if(t.$$typeof===U)return ne(t)}throw Error(f(438,String(t)))}function vs(t){var e=null,n=mt.updateQueue;if(n!==null&&(e=n.memoCache),e==null){var a=mt.alternate;a!==null&&(a=a.updateQueue,a!==null&&(a=a.memoCache,a!=null&&(e={data:a.data.map(function(i){return i.slice()}),index:0})))}if(e==null&&(e={data:[],index:0}),n===null&&(n=hu(),mt.updateQueue=n),n.memoCache=e,n=e.data[e.index],n===void 0)for(n=e.data[e.index]=Array(t),a=0;a<t;a++)n[a]=J;return e.index++,n}function sn(t,e){return typeof e=="function"?e(t):e}function gu(t){var e=Vt();return ys(e,Rt,t)}function ys(t,e,n){var a=t.queue;if(a===null)throw Error(f(311));a.lastRenderedReducer=n;var i=t.baseQueue,s=a.pending;if(s!==null){if(i!==null){var d=i.next;i.next=s.next,s.next=d}e.baseQueue=i=s,a.pending=null}if(s=t.baseState,i===null)t.memoizedState=s;else{e=i.next;var g=d=null,T=null,z=e,V=!1;do{var I=z.lane&-536870913;if(I!==z.lane?(Et&I)===I:(cn&I)===I){var Y=z.revertLane;if(Y===0)T!==null&&(T=T.next={lane:0,revertLane:0,gesture:null,action:z.action,hasEagerState:z.hasEagerState,eagerState:z.eagerState,next:null}),I===el&&(V=!0);else if((cn&Y)===Y){z=z.next,Y===el&&(V=!0);continue}else I={lane:0,revertLane:z.revertLane,gesture:null,action:z.action,hasEagerState:z.hasEagerState,eagerState:z.eagerState,next:null},T===null?(g=T=I,d=s):T=T.next=I,mt.lanes|=Y,Zn|=Y;I=z.action,Ca&&n(s,I),s=z.hasEagerState?z.eagerState:n(s,I)}else Y={lane:I,revertLane:z.revertLane,gesture:z.gesture,action:z.action,hasEagerState:z.hasEagerState,eagerState:z.eagerState,next:null},T===null?(g=T=Y,d=s):T=T.next=Y,mt.lanes|=I,Zn|=I;z=z.next}while(z!==null&&z!==e);if(T===null?d=s:T.next=g,!Se(s,t.memoizedState)&&(It=!0,V&&(n=nl,n!==null)))throw n;t.memoizedState=s,t.baseState=d,t.baseQueue=T,a.lastRenderedState=s}return i===null&&(a.lanes=0),[t.memoizedState,a.dispatch]}function Es(t){var e=Vt(),n=e.queue;if(n===null)throw Error(f(311));n.lastRenderedReducer=t;var a=n.dispatch,i=n.pending,s=e.memoizedState;if(i!==null){n.pending=null;var d=i=i.next;do s=t(s,d.action),d=d.next;while(d!==i);Se(s,e.memoizedState)||(It=!0),e.memoizedState=s,e.baseQueue===null&&(e.baseState=s),n.lastRenderedState=s}return[s,a]}function _o(t,e,n){var a=mt,i=Vt(),s=bt;if(s){if(n===void 0)throw Error(f(407));n=n()}else n=e();var d=!Se((Rt||i).memoizedState,n);if(d&&(i.memoizedState=n,It=!0),i=i.queue,xs(t0.bind(null,a,i,t),[t]),i.getSnapshot!==e||d||qt!==null&&qt.memoizedState.tag&1){if(a.flags|=2048,sl(9,{destroy:void 0},$o.bind(null,a,i,n,e),null),jt===null)throw Error(f(349));s||(cn&127)!==0||Po(a,e,n)}return n}function Po(t,e,n){t.flags|=16384,t={getSnapshot:e,value:n},e=mt.updateQueue,e===null?(e=hu(),mt.updateQueue=e,e.stores=[t]):(n=e.stores,n===null?e.stores=[t]:n.push(t))}function $o(t,e,n,a){e.value=n,e.getSnapshot=a,e0(e)&&n0(t)}function t0(t,e,n){return n(function(){e0(e)&&n0(t)})}function e0(t){var e=t.getSnapshot;t=t.value;try{var n=e();return!Se(t,n)}catch{return!0}}function n0(t){var e=Aa(t,2);e!==null&&ve(e,t,2)}function ps(t){var e=fe();if(typeof t=="function"){var n=t;if(t=n(),Ca){Dn(!0);try{n()}finally{Dn(!1)}}}return e.memoizedState=e.baseState=t,e.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:sn,lastRenderedState:t},e}function a0(t,e,n,a){return t.baseState=n,ys(t,Rt,typeof a=="function"?a:sn)}function Tg(t,e,n,a,i){if(yu(t))throw Error(f(485));if(t=e.action,t!==null){var s={payload:i,action:t,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(d){s.listeners.push(d)}};H.T!==null?n(!0):s.isTransition=!1,a(s),n=e.pending,n===null?(s.next=e.pending=s,l0(e,s)):(s.next=n.next,e.pending=n.next=s)}}function l0(t,e){var n=e.action,a=e.payload,i=t.state;if(e.isTransition){var s=H.T,d={};H.T=d;try{var g=n(i,a),T=H.S;T!==null&&T(d,g),i0(t,e,g)}catch(z){bs(t,e,z)}finally{s!==null&&d.types!==null&&(s.types=d.types),H.T=s}}else try{s=n(i,a),i0(t,e,s)}catch(z){bs(t,e,z)}}function i0(t,e,n){n!==null&&typeof n=="object"&&typeof n.then=="function"?n.then(function(a){u0(t,e,a)},function(a){return bs(t,e,a)}):u0(t,e,n)}function u0(t,e,n){e.status="fulfilled",e.value=n,c0(e),t.state=n,e=t.pending,e!==null&&(n=e.next,n===e?t.pending=null:(n=n.next,e.next=n,l0(t,n)))}function bs(t,e,n){var a=t.pending;if(t.pending=null,a!==null){a=a.next;do e.status="rejected",e.reason=n,c0(e),e=e.next;while(e!==a)}t.action=null}function c0(t){t=t.listeners;for(var e=0;e<t.length;e++)(0,t[e])()}function s0(t,e){return e}function f0(t,e){if(bt){var n=jt.formState;if(n!==null){t:{var a=mt;if(bt){if(Ht){e:{for(var i=Ht,s=ze;i.nodeType!==8;){if(!s){i=null;break e}if(i=Le(i.nextSibling),i===null){i=null;break e}}s=i.data,i=s==="F!"||s==="F"?i:null}if(i){Ht=Le(i.nextSibling),a=i.data==="F!";break t}}Bn(a)}a=!1}a&&(e=n[0])}}return n=fe(),n.memoizedState=n.baseState=e,a={pending:null,lanes:0,dispatch:null,lastRenderedReducer:s0,lastRenderedState:e},n.queue=a,n=R0.bind(null,mt,a),a.dispatch=n,a=ps(!1),s=ws.bind(null,mt,!1,a.queue),a=fe(),i={state:e,dispatch:null,action:t,pending:null},a.queue=i,n=Tg.bind(null,mt,i,s,n),i.dispatch=n,a.memoizedState=t,[e,n,!1]}function r0(t){var e=Vt();return o0(e,Rt,t)}function o0(t,e,n){if(e=ys(t,e,s0)[0],t=gu(sn)[0],typeof e=="object"&&e!==null&&typeof e.then=="function")try{var a=_l(e)}catch(d){throw d===al?iu:d}else a=e;e=Vt();var i=e.queue,s=i.dispatch;return n!==e.memoizedState&&(mt.flags|=2048,sl(9,{destroy:void 0},Cg.bind(null,i,n),null)),[a,s,t]}function Cg(t,e){t.action=e}function d0(t){var e=Vt(),n=Rt;if(n!==null)return o0(e,n,t);Vt(),e=e.memoizedState,n=Vt();var a=n.queue.dispatch;return n.memoizedState=t,[e,a,!1]}function sl(t,e,n,a){return t={tag:t,create:n,deps:a,inst:e,next:null},e=mt.updateQueue,e===null&&(e=hu(),mt.updateQueue=e),n=e.lastEffect,n===null?e.lastEffect=t.next=t:(a=n.next,n.next=t,t.next=a,e.lastEffect=t),t}function h0(){return Vt().memoizedState}function Au(t,e,n,a){var i=fe();mt.flags|=t,i.memoizedState=sl(1|e,{destroy:void 0},n,a===void 0?null:a)}function vu(t,e,n,a){var i=Vt();a=a===void 0?null:a;var s=i.memoizedState.inst;Rt!==null&&a!==null&&ds(a,Rt.memoizedState.deps)?i.memoizedState=sl(e,s,n,a):(mt.flags|=t,i.memoizedState=sl(1|e,s,n,a))}function m0(t,e){Au(8390656,8,t,e)}function xs(t,e){vu(2048,8,t,e)}function Og(t){mt.flags|=4;var e=mt.updateQueue;if(e===null)e=hu(),mt.updateQueue=e,e.events=[t];else{var n=e.events;n===null?e.events=[t]:n.push(t)}}function g0(t){var e=Vt().memoizedState;return Og({ref:e,nextImpl:t}),function(){if((Ct&2)!==0)throw Error(f(440));return e.impl.apply(void 0,arguments)}}function A0(t,e){return vu(4,2,t,e)}function v0(t,e){return vu(4,4,t,e)}function y0(t,e){if(typeof e=="function"){t=t();var n=e(t);return function(){typeof n=="function"?n():e(null)}}if(e!=null)return t=t(),e.current=t,function(){e.current=null}}function E0(t,e,n){n=n!=null?n.concat([t]):null,vu(4,4,y0.bind(null,e,t),n)}function Ss(){}function p0(t,e){var n=Vt();e=e===void 0?null:e;var a=n.memoizedState;return e!==null&&ds(e,a[1])?a[0]:(n.memoizedState=[t,e],t)}function b0(t,e){var n=Vt();e=e===void 0?null:e;var a=n.memoizedState;if(e!==null&&ds(e,a[1]))return a[0];if(a=t(),Ca){Dn(!0);try{t()}finally{Dn(!1)}}return n.memoizedState=[a,e],a}function Ts(t,e,n){return n===void 0||(cn&1073741824)!==0&&(Et&261930)===0?t.memoizedState=e:(t.memoizedState=n,t=xd(),mt.lanes|=t,Zn|=t,n)}function x0(t,e,n,a){return Se(n,e)?n:il.current!==null?(t=Ts(t,n,a),Se(t,e)||(It=!0),t):(cn&42)===0||(cn&1073741824)!==0&&(Et&261930)===0?(It=!0,t.memoizedState=n):(t=xd(),mt.lanes|=t,Zn|=t,e)}function S0(t,e,n,a,i){var s=_.p;_.p=s!==0&&8>s?s:8;var d=H.T,g={};H.T=g,ws(t,!1,e,n);try{var T=i(),z=H.S;if(z!==null&&z(g,T),T!==null&&typeof T=="object"&&typeof T.then=="function"){var V=bg(T,a);Pl(t,e,V,De(t))}else Pl(t,e,a,De(t))}catch(I){Pl(t,e,{then:function(){},status:"rejected",reason:I},De())}finally{_.p=s,d!==null&&g.types!==null&&(d.types=g.types),H.T=d}}function wg(){}function Cs(t,e,n,a){if(t.tag!==5)throw Error(f(476));var i=T0(t).queue;S0(t,i,e,$,n===null?wg:function(){return C0(t),n(a)})}function T0(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:$,baseState:$,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:sn,lastRenderedState:$},next:null};var n={};return e.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:sn,lastRenderedState:n},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function C0(t){var e=T0(t);e.next===null&&(e=t.alternate.memoizedState),Pl(t,e.next.queue,{},De())}function Os(){return ne(gi)}function O0(){return Vt().memoizedState}function w0(){return Vt().memoizedState}function Rg(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var n=De();t=zn(n);var a=Yn(e,t,n);a!==null&&(ve(a,e,n),kl(a,e,n)),e={cache:es()},t.payload=e;return}e=e.return}}function Dg(t,e,n){var a=De();n={lane:a,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},yu(t)?D0(e,n):(n=qc(t,e,n,a),n!==null&&(ve(n,t,a),M0(n,e,a)))}function R0(t,e,n){var a=De();Pl(t,e,n,a)}function Pl(t,e,n,a){var i={lane:a,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(yu(t))D0(e,i);else{var s=t.alternate;if(t.lanes===0&&(s===null||s.lanes===0)&&(s=e.lastRenderedReducer,s!==null))try{var d=e.lastRenderedState,g=s(d,n);if(i.hasEagerState=!0,i.eagerState=g,Se(g,d))return Pi(t,e,i,0),jt===null&&_i(),!1}catch{}finally{}if(n=qc(t,e,i,a),n!==null)return ve(n,t,a),M0(n,e,a),!0}return!1}function ws(t,e,n,a){if(a={lane:2,revertLane:uf(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},yu(t)){if(e)throw Error(f(479))}else e=qc(t,n,a,2),e!==null&&ve(e,t,2)}function yu(t){var e=t.alternate;return t===mt||e!==null&&e===mt}function D0(t,e){ul=ou=!0;var n=t.pending;n===null?e.next=e:(e.next=n.next,n.next=e),t.pending=e}function M0(t,e,n){if((n&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,n|=a,e.lanes=n,Br(t,n)}}var $l={readContext:ne,use:mu,useCallback:Lt,useContext:Lt,useEffect:Lt,useImperativeHandle:Lt,useLayoutEffect:Lt,useInsertionEffect:Lt,useMemo:Lt,useReducer:Lt,useRef:Lt,useState:Lt,useDebugValue:Lt,useDeferredValue:Lt,useTransition:Lt,useSyncExternalStore:Lt,useId:Lt,useHostTransitionStatus:Lt,useFormState:Lt,useActionState:Lt,useOptimistic:Lt,useMemoCache:Lt,useCacheRefresh:Lt};$l.useEffectEvent=Lt;var j0={readContext:ne,use:mu,useCallback:function(t,e){return fe().memoizedState=[t,e===void 0?null:e],t},useContext:ne,useEffect:m0,useImperativeHandle:function(t,e,n){n=n!=null?n.concat([t]):null,Au(4194308,4,y0.bind(null,e,t),n)},useLayoutEffect:function(t,e){return Au(4194308,4,t,e)},useInsertionEffect:function(t,e){Au(4,2,t,e)},useMemo:function(t,e){var n=fe();e=e===void 0?null:e;var a=t();if(Ca){Dn(!0);try{t()}finally{Dn(!1)}}return n.memoizedState=[a,e],a},useReducer:function(t,e,n){var a=fe();if(n!==void 0){var i=n(e);if(Ca){Dn(!0);try{n(e)}finally{Dn(!1)}}}else i=e;return a.memoizedState=a.baseState=i,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:i},a.queue=t,t=t.dispatch=Dg.bind(null,mt,t),[a.memoizedState,t]},useRef:function(t){var e=fe();return t={current:t},e.memoizedState=t},useState:function(t){t=ps(t);var e=t.queue,n=R0.bind(null,mt,e);return e.dispatch=n,[t.memoizedState,n]},useDebugValue:Ss,useDeferredValue:function(t,e){var n=fe();return Ts(n,t,e)},useTransition:function(){var t=ps(!1);return t=S0.bind(null,mt,t.queue,!0,!1),fe().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,n){var a=mt,i=fe();if(bt){if(n===void 0)throw Error(f(407));n=n()}else{if(n=e(),jt===null)throw Error(f(349));(Et&127)!==0||Po(a,e,n)}i.memoizedState=n;var s={value:n,getSnapshot:e};return i.queue=s,m0(t0.bind(null,a,s,t),[t]),a.flags|=2048,sl(9,{destroy:void 0},$o.bind(null,a,s,n,e),null),n},useId:function(){var t=fe(),e=jt.identifierPrefix;if(bt){var n=Fe,a=Je;n=(a&~(1<<32-xe(a)-1)).toString(32)+n,e="_"+e+"R_"+n,n=du++,0<n&&(e+="H"+n.toString(32)),e+="_"}else n=xg++,e="_"+e+"r_"+n.toString(32)+"_";return t.memoizedState=e},useHostTransitionStatus:Os,useFormState:f0,useActionState:f0,useOptimistic:function(t){var e=fe();e.memoizedState=e.baseState=t;var n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return e.queue=n,e=ws.bind(null,mt,!0,n),n.dispatch=e,[t,e]},useMemoCache:vs,useCacheRefresh:function(){return fe().memoizedState=Rg.bind(null,mt)},useEffectEvent:function(t){var e=fe(),n={impl:t};return e.memoizedState=n,function(){if((Ct&2)!==0)throw Error(f(440));return n.impl.apply(void 0,arguments)}}},Rs={readContext:ne,use:mu,useCallback:p0,useContext:ne,useEffect:xs,useImperativeHandle:E0,useInsertionEffect:A0,useLayoutEffect:v0,useMemo:b0,useReducer:gu,useRef:h0,useState:function(){return gu(sn)},useDebugValue:Ss,useDeferredValue:function(t,e){var n=Vt();return x0(n,Rt.memoizedState,t,e)},useTransition:function(){var t=gu(sn)[0],e=Vt().memoizedState;return[typeof t=="boolean"?t:_l(t),e]},useSyncExternalStore:_o,useId:O0,useHostTransitionStatus:Os,useFormState:r0,useActionState:r0,useOptimistic:function(t,e){var n=Vt();return a0(n,Rt,t,e)},useMemoCache:vs,useCacheRefresh:w0};Rs.useEffectEvent=g0;var H0={readContext:ne,use:mu,useCallback:p0,useContext:ne,useEffect:xs,useImperativeHandle:E0,useInsertionEffect:A0,useLayoutEffect:v0,useMemo:b0,useReducer:Es,useRef:h0,useState:function(){return Es(sn)},useDebugValue:Ss,useDeferredValue:function(t,e){var n=Vt();return Rt===null?Ts(n,t,e):x0(n,Rt.memoizedState,t,e)},useTransition:function(){var t=Es(sn)[0],e=Vt().memoizedState;return[typeof t=="boolean"?t:_l(t),e]},useSyncExternalStore:_o,useId:O0,useHostTransitionStatus:Os,useFormState:d0,useActionState:d0,useOptimistic:function(t,e){var n=Vt();return Rt!==null?a0(n,Rt,t,e):(n.baseState=t,[t,n.queue.dispatch])},useMemoCache:vs,useCacheRefresh:w0};H0.useEffectEvent=g0;function Ds(t,e,n,a){e=t.memoizedState,n=n(a,e),n=n==null?e:S({},e,n),t.memoizedState=n,t.lanes===0&&(t.updateQueue.baseState=n)}var Ms={enqueueSetState:function(t,e,n){t=t._reactInternals;var a=De(),i=zn(a);i.payload=e,n!=null&&(i.callback=n),e=Yn(t,i,a),e!==null&&(ve(e,t,a),kl(e,t,a))},enqueueReplaceState:function(t,e,n){t=t._reactInternals;var a=De(),i=zn(a);i.tag=1,i.payload=e,n!=null&&(i.callback=n),e=Yn(t,i,a),e!==null&&(ve(e,t,a),kl(e,t,a))},enqueueForceUpdate:function(t,e){t=t._reactInternals;var n=De(),a=zn(n);a.tag=2,e!=null&&(a.callback=e),e=Yn(t,a,n),e!==null&&(ve(e,t,n),kl(e,t,n))}};function N0(t,e,n,a,i,s,d){return t=t.stateNode,typeof t.shouldComponentUpdate=="function"?t.shouldComponentUpdate(a,s,d):e.prototype&&e.prototype.isPureReactComponent?!Ll(n,a)||!Ll(i,s):!0}function B0(t,e,n,a){t=e.state,typeof e.componentWillReceiveProps=="function"&&e.componentWillReceiveProps(n,a),typeof e.UNSAFE_componentWillReceiveProps=="function"&&e.UNSAFE_componentWillReceiveProps(n,a),e.state!==t&&Ms.enqueueReplaceState(e,e.state,null)}function Oa(t,e){var n=e;if("ref"in e){n={};for(var a in e)a!=="ref"&&(n[a]=e[a])}if(t=t.defaultProps){n===e&&(n=S({},n));for(var i in t)n[i]===void 0&&(n[i]=t[i])}return n}function U0(t){Wi(t)}function Q0(t){console.error(t)}function z0(t){Wi(t)}function Eu(t,e){try{var n=t.onUncaughtError;n(e.value,{componentStack:e.stack})}catch(a){setTimeout(function(){throw a})}}function Y0(t,e,n){try{var a=t.onCaughtError;a(n.value,{componentStack:n.stack,errorBoundary:e.tag===1?e.stateNode:null})}catch(i){setTimeout(function(){throw i})}}function js(t,e,n){return n=zn(n),n.tag=3,n.payload={element:null},n.callback=function(){Eu(t,e)},n}function L0(t){return t=zn(t),t.tag=3,t}function G0(t,e,n,a){var i=n.type.getDerivedStateFromError;if(typeof i=="function"){var s=a.value;t.payload=function(){return i(s)},t.callback=function(){Y0(e,n,a)}}var d=n.stateNode;d!==null&&typeof d.componentDidCatch=="function"&&(t.callback=function(){Y0(e,n,a),typeof i!="function"&&(qn===null?qn=new Set([this]):qn.add(this));var g=a.stack;this.componentDidCatch(a.value,{componentStack:g!==null?g:""})})}function Mg(t,e,n,a,i){if(n.flags|=32768,a!==null&&typeof a=="object"&&typeof a.then=="function"){if(e=n.alternate,e!==null&&tl(e,n,i,!0),n=Ce.current,n!==null){switch(n.tag){case 31:case 13:return Ye===null?ju():n.alternate===null&&Gt===0&&(Gt=3),n.flags&=-257,n.flags|=65536,n.lanes=i,a===uu?n.flags|=16384:(e=n.updateQueue,e===null?n.updateQueue=new Set([a]):e.add(a),nf(t,a,i)),!1;case 22:return n.flags|=65536,a===uu?n.flags|=16384:(e=n.updateQueue,e===null?(e={transitions:null,markerInstances:null,retryQueue:new Set([a])},n.updateQueue=e):(n=e.retryQueue,n===null?e.retryQueue=new Set([a]):n.add(a)),nf(t,a,i)),!1}throw Error(f(435,n.tag))}return nf(t,a,i),ju(),!1}if(bt)return e=Ce.current,e!==null?((e.flags&65536)===0&&(e.flags|=256),e.flags|=65536,e.lanes=i,a!==Wc&&(t=Error(f(422),{cause:a}),Vl(Be(t,n)))):(a!==Wc&&(e=Error(f(423),{cause:a}),Vl(Be(e,n))),t=t.current.alternate,t.flags|=65536,i&=-i,t.lanes|=i,a=Be(a,n),i=js(t.stateNode,a,i),cs(t,i),Gt!==4&&(Gt=2)),!1;var s=Error(f(520),{cause:a});if(s=Be(s,n),ci===null?ci=[s]:ci.push(s),Gt!==4&&(Gt=2),e===null)return!0;a=Be(a,n),n=e;do{switch(n.tag){case 3:return n.flags|=65536,t=i&-i,n.lanes|=t,t=js(n.stateNode,a,t),cs(n,t),!1;case 1:if(e=n.type,s=n.stateNode,(n.flags&128)===0&&(typeof e.getDerivedStateFromError=="function"||s!==null&&typeof s.componentDidCatch=="function"&&(qn===null||!qn.has(s))))return n.flags|=65536,i&=-i,n.lanes|=i,i=L0(i),G0(i,t,n,a),cs(n,i),!1}n=n.return}while(n!==null);return!1}var Hs=Error(f(461)),It=!1;function ae(t,e,n,a){e.child=t===null?qo(e,null,n,a):Ta(e,t.child,n,a)}function X0(t,e,n,a,i){n=n.render;var s=e.ref;if("ref"in a){var d={};for(var g in a)g!=="ref"&&(d[g]=a[g])}else d=a;return pa(e),a=hs(t,e,n,d,s,i),g=ms(),t!==null&&!It?(gs(t,e,i),fn(t,e,i)):(bt&&g&&Jc(e),e.flags|=1,ae(t,e,a,i),e.child)}function V0(t,e,n,a,i){if(t===null){var s=n.type;return typeof s=="function"&&!Ic(s)&&s.defaultProps===void 0&&n.compare===null?(e.tag=15,e.type=s,Z0(t,e,s,a,i)):(t=tu(n.type,null,a,e,e.mode,i),t.ref=e.ref,t.return=e,e.child=t)}if(s=t.child,!Gs(t,i)){var d=s.memoizedProps;if(n=n.compare,n=n!==null?n:Ll,n(d,a)&&t.ref===e.ref)return fn(t,e,i)}return e.flags|=1,t=nn(s,a),t.ref=e.ref,t.return=e,e.child=t}function Z0(t,e,n,a,i){if(t!==null){var s=t.memoizedProps;if(Ll(s,a)&&t.ref===e.ref)if(It=!1,e.pendingProps=a=s,Gs(t,i))(t.flags&131072)!==0&&(It=!0);else return e.lanes=t.lanes,fn(t,e,i)}return Ns(t,e,n,a,i)}function q0(t,e,n,a){var i=a.children,s=t!==null?t.memoizedState:null;if(t===null&&e.stateNode===null&&(e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),a.mode==="hidden"){if((e.flags&128)!==0){if(s=s!==null?s.baseLanes|n:n,t!==null){for(a=e.child=t.child,i=0;a!==null;)i=i|a.lanes|a.childLanes,a=a.sibling;a=i&~s}else a=0,e.child=null;return I0(t,e,s,n,a)}if((n&536870912)!==0)e.memoizedState={baseLanes:0,cachePool:null},t!==null&&lu(e,s!==null?s.cachePool:null),s!==null?ko(e,s):fs(),Jo(e);else return a=e.lanes=536870912,I0(t,e,s!==null?s.baseLanes|n:n,n,a)}else s!==null?(lu(e,s.cachePool),ko(e,s),Gn(),e.memoizedState=null):(t!==null&&lu(e,null),fs(),Gn());return ae(t,e,i,n),e.child}function ti(t,e){return t!==null&&t.tag===22||e.stateNode!==null||(e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),e.sibling}function I0(t,e,n,a,i){var s=as();return s=s===null?null:{parent:Zt._currentValue,pool:s},e.memoizedState={baseLanes:n,cachePool:s},t!==null&&lu(e,null),fs(),Jo(e),t!==null&&tl(t,e,a,!0),e.childLanes=i,null}function pu(t,e){return e=xu({mode:e.mode,children:e.children},t.mode),e.ref=t.ref,t.child=e,e.return=t,e}function K0(t,e,n){return Ta(e,t.child,null,n),t=pu(e,e.pendingProps),t.flags|=2,Oe(e),e.memoizedState=null,t}function jg(t,e,n){var a=e.pendingProps,i=(e.flags&128)!==0;if(e.flags&=-129,t===null){if(bt){if(a.mode==="hidden")return t=pu(e,a),e.lanes=536870912,ti(null,t);if(os(e),(t=Ht)?(t=l1(t,ze),t=t!==null&&t.data==="&"?t:null,t!==null&&(e.memoizedState={dehydrated:t,treeContext:Hn!==null?{id:Je,overflow:Fe}:null,retryLane:536870912,hydrationErrors:null},n=Do(t),n.return=e,e.child=n,ee=e,Ht=null)):t=null,t===null)throw Bn(e);return e.lanes=536870912,null}return pu(e,a)}var s=t.memoizedState;if(s!==null){var d=s.dehydrated;if(os(e),i)if(e.flags&256)e.flags&=-257,e=K0(t,e,n);else if(e.memoizedState!==null)e.child=t.child,e.flags|=128,e=null;else throw Error(f(558));else if(It||tl(t,e,n,!1),i=(n&t.childLanes)!==0,It||i){if(a=jt,a!==null&&(d=Ur(a,n),d!==0&&d!==s.retryLane))throw s.retryLane=d,Aa(t,d),ve(a,t,d),Hs;ju(),e=K0(t,e,n)}else t=s.treeContext,Ht=Le(d.nextSibling),ee=e,bt=!0,Nn=null,ze=!1,t!==null&&Ho(e,t),e=pu(e,a),e.flags|=4096;return e}return t=nn(t.child,{mode:a.mode,children:a.children}),t.ref=e.ref,e.child=t,t.return=e,t}function bu(t,e){var n=e.ref;if(n===null)t!==null&&t.ref!==null&&(e.flags|=4194816);else{if(typeof n!="function"&&typeof n!="object")throw Error(f(284));(t===null||t.ref!==n)&&(e.flags|=4194816)}}function Ns(t,e,n,a,i){return pa(e),n=hs(t,e,n,a,void 0,i),a=ms(),t!==null&&!It?(gs(t,e,i),fn(t,e,i)):(bt&&a&&Jc(e),e.flags|=1,ae(t,e,n,i),e.child)}function k0(t,e,n,a,i,s){return pa(e),e.updateQueue=null,n=Wo(e,a,n,i),Fo(t),a=ms(),t!==null&&!It?(gs(t,e,s),fn(t,e,s)):(bt&&a&&Jc(e),e.flags|=1,ae(t,e,n,s),e.child)}function J0(t,e,n,a,i){if(pa(e),e.stateNode===null){var s=Wa,d=n.contextType;typeof d=="object"&&d!==null&&(s=ne(d)),s=new n(a,s),e.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,s.updater=Ms,e.stateNode=s,s._reactInternals=e,s=e.stateNode,s.props=a,s.state=e.memoizedState,s.refs={},is(e),d=n.contextType,s.context=typeof d=="object"&&d!==null?ne(d):Wa,s.state=e.memoizedState,d=n.getDerivedStateFromProps,typeof d=="function"&&(Ds(e,n,d,a),s.state=e.memoizedState),typeof n.getDerivedStateFromProps=="function"||typeof s.getSnapshotBeforeUpdate=="function"||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(d=s.state,typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount(),d!==s.state&&Ms.enqueueReplaceState(s,s.state,null),Fl(e,a,s,i),Jl(),s.state=e.memoizedState),typeof s.componentDidMount=="function"&&(e.flags|=4194308),a=!0}else if(t===null){s=e.stateNode;var g=e.memoizedProps,T=Oa(n,g);s.props=T;var z=s.context,V=n.contextType;d=Wa,typeof V=="object"&&V!==null&&(d=ne(V));var I=n.getDerivedStateFromProps;V=typeof I=="function"||typeof s.getSnapshotBeforeUpdate=="function",g=e.pendingProps!==g,V||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(g||z!==d)&&B0(e,s,a,d),Qn=!1;var Y=e.memoizedState;s.state=Y,Fl(e,a,s,i),Jl(),z=e.memoizedState,g||Y!==z||Qn?(typeof I=="function"&&(Ds(e,n,I,a),z=e.memoizedState),(T=Qn||N0(e,n,T,a,Y,z,d))?(V||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(e.flags|=4194308)):(typeof s.componentDidMount=="function"&&(e.flags|=4194308),e.memoizedProps=a,e.memoizedState=z),s.props=a,s.state=z,s.context=d,a=T):(typeof s.componentDidMount=="function"&&(e.flags|=4194308),a=!1)}else{s=e.stateNode,us(t,e),d=e.memoizedProps,V=Oa(n,d),s.props=V,I=e.pendingProps,Y=s.context,z=n.contextType,T=Wa,typeof z=="object"&&z!==null&&(T=ne(z)),g=n.getDerivedStateFromProps,(z=typeof g=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(d!==I||Y!==T)&&B0(e,s,a,T),Qn=!1,Y=e.memoizedState,s.state=Y,Fl(e,a,s,i),Jl();var G=e.memoizedState;d!==I||Y!==G||Qn||t!==null&&t.dependencies!==null&&nu(t.dependencies)?(typeof g=="function"&&(Ds(e,n,g,a),G=e.memoizedState),(V=Qn||N0(e,n,V,a,Y,G,T)||t!==null&&t.dependencies!==null&&nu(t.dependencies))?(z||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(a,G,T),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(a,G,T)),typeof s.componentDidUpdate=="function"&&(e.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(e.flags|=1024)):(typeof s.componentDidUpdate!="function"||d===t.memoizedProps&&Y===t.memoizedState||(e.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||d===t.memoizedProps&&Y===t.memoizedState||(e.flags|=1024),e.memoizedProps=a,e.memoizedState=G),s.props=a,s.state=G,s.context=T,a=V):(typeof s.componentDidUpdate!="function"||d===t.memoizedProps&&Y===t.memoizedState||(e.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||d===t.memoizedProps&&Y===t.memoizedState||(e.flags|=1024),a=!1)}return s=a,bu(t,e),a=(e.flags&128)!==0,s||a?(s=e.stateNode,n=a&&typeof n.getDerivedStateFromError!="function"?null:s.render(),e.flags|=1,t!==null&&a?(e.child=Ta(e,t.child,null,i),e.child=Ta(e,null,n,i)):ae(t,e,n,i),e.memoizedState=s.state,t=e.child):t=fn(t,e,i),t}function F0(t,e,n,a){return ya(),e.flags|=256,ae(t,e,n,a),e.child}var Bs={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Us(t){return{baseLanes:t,cachePool:Yo()}}function Qs(t,e,n){return t=t!==null?t.childLanes&~n:0,e&&(t|=Re),t}function W0(t,e,n){var a=e.pendingProps,i=!1,s=(e.flags&128)!==0,d;if((d=s)||(d=t!==null&&t.memoizedState===null?!1:(Xt.current&2)!==0),d&&(i=!0,e.flags&=-129),d=(e.flags&32)!==0,e.flags&=-33,t===null){if(bt){if(i?Ln(e):Gn(),(t=Ht)?(t=l1(t,ze),t=t!==null&&t.data!=="&"?t:null,t!==null&&(e.memoizedState={dehydrated:t,treeContext:Hn!==null?{id:Je,overflow:Fe}:null,retryLane:536870912,hydrationErrors:null},n=Do(t),n.return=e,e.child=n,ee=e,Ht=null)):t=null,t===null)throw Bn(e);return Ef(t)?e.lanes=32:e.lanes=536870912,null}var g=a.children;return a=a.fallback,i?(Gn(),i=e.mode,g=xu({mode:"hidden",children:g},i),a=va(a,i,n,null),g.return=e,a.return=e,g.sibling=a,e.child=g,a=e.child,a.memoizedState=Us(n),a.childLanes=Qs(t,d,n),e.memoizedState=Bs,ti(null,a)):(Ln(e),zs(e,g))}var T=t.memoizedState;if(T!==null&&(g=T.dehydrated,g!==null)){if(s)e.flags&256?(Ln(e),e.flags&=-257,e=Ys(t,e,n)):e.memoizedState!==null?(Gn(),e.child=t.child,e.flags|=128,e=null):(Gn(),g=a.fallback,i=e.mode,a=xu({mode:"visible",children:a.children},i),g=va(g,i,n,null),g.flags|=2,a.return=e,g.return=e,a.sibling=g,e.child=a,Ta(e,t.child,null,n),a=e.child,a.memoizedState=Us(n),a.childLanes=Qs(t,d,n),e.memoizedState=Bs,e=ti(null,a));else if(Ln(e),Ef(g)){if(d=g.nextSibling&&g.nextSibling.dataset,d)var z=d.dgst;d=z,a=Error(f(419)),a.stack="",a.digest=d,Vl({value:a,source:null,stack:null}),e=Ys(t,e,n)}else if(It||tl(t,e,n,!1),d=(n&t.childLanes)!==0,It||d){if(d=jt,d!==null&&(a=Ur(d,n),a!==0&&a!==T.retryLane))throw T.retryLane=a,Aa(t,a),ve(d,t,a),Hs;yf(g)||ju(),e=Ys(t,e,n)}else yf(g)?(e.flags|=192,e.child=t.child,e=null):(t=T.treeContext,Ht=Le(g.nextSibling),ee=e,bt=!0,Nn=null,ze=!1,t!==null&&Ho(e,t),e=zs(e,a.children),e.flags|=4096);return e}return i?(Gn(),g=a.fallback,i=e.mode,T=t.child,z=T.sibling,a=nn(T,{mode:"hidden",children:a.children}),a.subtreeFlags=T.subtreeFlags&65011712,z!==null?g=nn(z,g):(g=va(g,i,n,null),g.flags|=2),g.return=e,a.return=e,a.sibling=g,e.child=a,ti(null,a),a=e.child,g=t.child.memoizedState,g===null?g=Us(n):(i=g.cachePool,i!==null?(T=Zt._currentValue,i=i.parent!==T?{parent:T,pool:T}:i):i=Yo(),g={baseLanes:g.baseLanes|n,cachePool:i}),a.memoizedState=g,a.childLanes=Qs(t,d,n),e.memoizedState=Bs,ti(t.child,a)):(Ln(e),n=t.child,t=n.sibling,n=nn(n,{mode:"visible",children:a.children}),n.return=e,n.sibling=null,t!==null&&(d=e.deletions,d===null?(e.deletions=[t],e.flags|=16):d.push(t)),e.child=n,e.memoizedState=null,n)}function zs(t,e){return e=xu({mode:"visible",children:e},t.mode),e.return=t,t.child=e}function xu(t,e){return t=Te(22,t,null,e),t.lanes=0,t}function Ys(t,e,n){return Ta(e,t.child,null,n),t=zs(e,e.pendingProps.children),t.flags|=2,e.memoizedState=null,t}function _0(t,e,n){t.lanes|=e;var a=t.alternate;a!==null&&(a.lanes|=e),$c(t.return,e,n)}function Ls(t,e,n,a,i,s){var d=t.memoizedState;d===null?t.memoizedState={isBackwards:e,rendering:null,renderingStartTime:0,last:a,tail:n,tailMode:i,treeForkCount:s}:(d.isBackwards=e,d.rendering=null,d.renderingStartTime=0,d.last=a,d.tail=n,d.tailMode=i,d.treeForkCount=s)}function P0(t,e,n){var a=e.pendingProps,i=a.revealOrder,s=a.tail;a=a.children;var d=Xt.current,g=(d&2)!==0;if(g?(d=d&1|2,e.flags|=128):d&=1,W(Xt,d),ae(t,e,a,n),a=bt?Xl:0,!g&&t!==null&&(t.flags&128)!==0)t:for(t=e.child;t!==null;){if(t.tag===13)t.memoizedState!==null&&_0(t,n,e);else if(t.tag===19)_0(t,n,e);else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break t;for(;t.sibling===null;){if(t.return===null||t.return===e)break t;t=t.return}t.sibling.return=t.return,t=t.sibling}switch(i){case"forwards":for(n=e.child,i=null;n!==null;)t=n.alternate,t!==null&&ru(t)===null&&(i=n),n=n.sibling;n=i,n===null?(i=e.child,e.child=null):(i=n.sibling,n.sibling=null),Ls(e,!1,i,n,s,a);break;case"backwards":case"unstable_legacy-backwards":for(n=null,i=e.child,e.child=null;i!==null;){if(t=i.alternate,t!==null&&ru(t)===null){e.child=i;break}t=i.sibling,i.sibling=n,n=i,i=t}Ls(e,!0,n,null,s,a);break;case"together":Ls(e,!1,null,null,void 0,a);break;default:e.memoizedState=null}return e.child}function fn(t,e,n){if(t!==null&&(e.dependencies=t.dependencies),Zn|=e.lanes,(n&e.childLanes)===0)if(t!==null){if(tl(t,e,n,!1),(n&e.childLanes)===0)return null}else return null;if(t!==null&&e.child!==t.child)throw Error(f(153));if(e.child!==null){for(t=e.child,n=nn(t,t.pendingProps),e.child=n,n.return=e;t.sibling!==null;)t=t.sibling,n=n.sibling=nn(t,t.pendingProps),n.return=e;n.sibling=null}return e.child}function Gs(t,e){return(t.lanes&e)!==0?!0:(t=t.dependencies,!!(t!==null&&nu(t)))}function Hg(t,e,n){switch(e.tag){case 3:Ft(e,e.stateNode.containerInfo),Un(e,Zt,t.memoizedState.cache),ya();break;case 27:case 5:On(e);break;case 4:Ft(e,e.stateNode.containerInfo);break;case 10:Un(e,e.type,e.memoizedProps.value);break;case 31:if(e.memoizedState!==null)return e.flags|=128,os(e),null;break;case 13:var a=e.memoizedState;if(a!==null)return a.dehydrated!==null?(Ln(e),e.flags|=128,null):(n&e.child.childLanes)!==0?W0(t,e,n):(Ln(e),t=fn(t,e,n),t!==null?t.sibling:null);Ln(e);break;case 19:var i=(t.flags&128)!==0;if(a=(n&e.childLanes)!==0,a||(tl(t,e,n,!1),a=(n&e.childLanes)!==0),i){if(a)return P0(t,e,n);e.flags|=128}if(i=e.memoizedState,i!==null&&(i.rendering=null,i.tail=null,i.lastEffect=null),W(Xt,Xt.current),a)break;return null;case 22:return e.lanes=0,q0(t,e,n,e.pendingProps);case 24:Un(e,Zt,t.memoizedState.cache)}return fn(t,e,n)}function $0(t,e,n){if(t!==null)if(t.memoizedProps!==e.pendingProps)It=!0;else{if(!Gs(t,n)&&(e.flags&128)===0)return It=!1,Hg(t,e,n);It=(t.flags&131072)!==0}else It=!1,bt&&(e.flags&1048576)!==0&&jo(e,Xl,e.index);switch(e.lanes=0,e.tag){case 16:t:{var a=e.pendingProps;if(t=xa(e.elementType),e.type=t,typeof t=="function")Ic(t)?(a=Oa(t,a),e.tag=1,e=J0(null,e,t,a,n)):(e.tag=0,e=Ns(null,e,t,a,n));else{if(t!=null){var i=t.$$typeof;if(i===Z){e.tag=11,e=X0(null,e,t,a,n);break t}else if(i===D){e.tag=14,e=V0(null,e,t,a,n);break t}}throw e=st(t)||t,Error(f(306,e,""))}}return e;case 0:return Ns(t,e,e.type,e.pendingProps,n);case 1:return a=e.type,i=Oa(a,e.pendingProps),J0(t,e,a,i,n);case 3:t:{if(Ft(e,e.stateNode.containerInfo),t===null)throw Error(f(387));a=e.pendingProps;var s=e.memoizedState;i=s.element,us(t,e),Fl(e,a,null,n);var d=e.memoizedState;if(a=d.cache,Un(e,Zt,a),a!==s.cache&&ts(e,[Zt],n,!0),Jl(),a=d.element,s.isDehydrated)if(s={element:a,isDehydrated:!1,cache:d.cache},e.updateQueue.baseState=s,e.memoizedState=s,e.flags&256){e=F0(t,e,a,n);break t}else if(a!==i){i=Be(Error(f(424)),e),Vl(i),e=F0(t,e,a,n);break t}else{switch(t=e.stateNode.containerInfo,t.nodeType){case 9:t=t.body;break;default:t=t.nodeName==="HTML"?t.ownerDocument.body:t}for(Ht=Le(t.firstChild),ee=e,bt=!0,Nn=null,ze=!0,n=qo(e,null,a,n),e.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling}else{if(ya(),a===i){e=fn(t,e,n);break t}ae(t,e,a,n)}e=e.child}return e;case 26:return bu(t,e),t===null?(n=r1(e.type,null,e.pendingProps,null))?e.memoizedState=n:bt||(n=e.type,t=e.pendingProps,a=Yu(ot.current).createElement(n),a[te]=e,a[oe]=t,le(a,n,t),Wt(a),e.stateNode=a):e.memoizedState=r1(e.type,t.memoizedProps,e.pendingProps,t.memoizedState),null;case 27:return On(e),t===null&&bt&&(a=e.stateNode=c1(e.type,e.pendingProps,ot.current),ee=e,ze=!0,i=Ht,Jn(e.type)?(pf=i,Ht=Le(a.firstChild)):Ht=i),ae(t,e,e.pendingProps.children,n),bu(t,e),t===null&&(e.flags|=4194304),e.child;case 5:return t===null&&bt&&((i=a=Ht)&&(a=sA(a,e.type,e.pendingProps,ze),a!==null?(e.stateNode=a,ee=e,Ht=Le(a.firstChild),ze=!1,i=!0):i=!1),i||Bn(e)),On(e),i=e.type,s=e.pendingProps,d=t!==null?t.memoizedProps:null,a=s.children,gf(i,s)?a=null:d!==null&&gf(i,d)&&(e.flags|=32),e.memoizedState!==null&&(i=hs(t,e,Sg,null,null,n),gi._currentValue=i),bu(t,e),ae(t,e,a,n),e.child;case 6:return t===null&&bt&&((t=n=Ht)&&(n=fA(n,e.pendingProps,ze),n!==null?(e.stateNode=n,ee=e,Ht=null,t=!0):t=!1),t||Bn(e)),null;case 13:return W0(t,e,n);case 4:return Ft(e,e.stateNode.containerInfo),a=e.pendingProps,t===null?e.child=Ta(e,null,a,n):ae(t,e,a,n),e.child;case 11:return X0(t,e,e.type,e.pendingProps,n);case 7:return ae(t,e,e.pendingProps,n),e.child;case 8:return ae(t,e,e.pendingProps.children,n),e.child;case 12:return ae(t,e,e.pendingProps.children,n),e.child;case 10:return a=e.pendingProps,Un(e,e.type,a.value),ae(t,e,a.children,n),e.child;case 9:return i=e.type._context,a=e.pendingProps.children,pa(e),i=ne(i),a=a(i),e.flags|=1,ae(t,e,a,n),e.child;case 14:return V0(t,e,e.type,e.pendingProps,n);case 15:return Z0(t,e,e.type,e.pendingProps,n);case 19:return P0(t,e,n);case 31:return jg(t,e,n);case 22:return q0(t,e,n,e.pendingProps);case 24:return pa(e),a=ne(Zt),t===null?(i=as(),i===null&&(i=jt,s=es(),i.pooledCache=s,s.refCount++,s!==null&&(i.pooledCacheLanes|=n),i=s),e.memoizedState={parent:a,cache:i},is(e),Un(e,Zt,i)):((t.lanes&n)!==0&&(us(t,e),Fl(e,null,null,n),Jl()),i=t.memoizedState,s=e.memoizedState,i.parent!==a?(i={parent:a,cache:a},e.memoizedState=i,e.lanes===0&&(e.memoizedState=e.updateQueue.baseState=i),Un(e,Zt,a)):(a=s.cache,Un(e,Zt,a),a!==i.cache&&ts(e,[Zt],n,!0))),ae(t,e,e.pendingProps.children,n),e.child;case 29:throw e.pendingProps}throw Error(f(156,e.tag))}function rn(t){t.flags|=4}function Xs(t,e,n,a,i){if((e=(t.mode&32)!==0)&&(e=!1),e){if(t.flags|=16777216,(i&335544128)===i)if(t.stateNode.complete)t.flags|=8192;else if(Od())t.flags|=8192;else throw Sa=uu,ls}else t.flags&=-16777217}function td(t,e){if(e.type!=="stylesheet"||(e.state.loading&4)!==0)t.flags&=-16777217;else if(t.flags|=16777216,!g1(e))if(Od())t.flags|=8192;else throw Sa=uu,ls}function Su(t,e){e!==null&&(t.flags|=4),t.flags&16384&&(e=t.tag!==22?Hr():536870912,t.lanes|=e,dl|=e)}function ei(t,e){if(!bt)switch(t.tailMode){case"hidden":e=t.tail;for(var n=null;e!==null;)e.alternate!==null&&(n=e),e=e.sibling;n===null?t.tail=null:n.sibling=null;break;case"collapsed":n=t.tail;for(var a=null;n!==null;)n.alternate!==null&&(a=n),n=n.sibling;a===null?e||t.tail===null?t.tail=null:t.tail.sibling=null:a.sibling=null}}function Nt(t){var e=t.alternate!==null&&t.alternate.child===t.child,n=0,a=0;if(e)for(var i=t.child;i!==null;)n|=i.lanes|i.childLanes,a|=i.subtreeFlags&65011712,a|=i.flags&65011712,i.return=t,i=i.sibling;else for(i=t.child;i!==null;)n|=i.lanes|i.childLanes,a|=i.subtreeFlags,a|=i.flags,i.return=t,i=i.sibling;return t.subtreeFlags|=a,t.childLanes=n,e}function Ng(t,e,n){var a=e.pendingProps;switch(Fc(e),e.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Nt(e),null;case 1:return Nt(e),null;case 3:return n=e.stateNode,a=null,t!==null&&(a=t.memoizedState.cache),e.memoizedState.cache!==a&&(e.flags|=2048),un(Zt),Qt(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),(t===null||t.child===null)&&($a(e)?rn(e):t===null||t.memoizedState.isDehydrated&&(e.flags&256)===0||(e.flags|=1024,_c())),Nt(e),null;case 26:var i=e.type,s=e.memoizedState;return t===null?(rn(e),s!==null?(Nt(e),td(e,s)):(Nt(e),Xs(e,i,null,a,n))):s?s!==t.memoizedState?(rn(e),Nt(e),td(e,s)):(Nt(e),e.flags&=-16777217):(t=t.memoizedProps,t!==a&&rn(e),Nt(e),Xs(e,i,t,a,n)),null;case 27:if(ra(e),n=ot.current,i=e.type,t!==null&&e.stateNode!=null)t.memoizedProps!==a&&rn(e);else{if(!a){if(e.stateNode===null)throw Error(f(166));return Nt(e),null}t=et.current,$a(e)?No(e):(t=c1(i,a,n),e.stateNode=t,rn(e))}return Nt(e),null;case 5:if(ra(e),i=e.type,t!==null&&e.stateNode!=null)t.memoizedProps!==a&&rn(e);else{if(!a){if(e.stateNode===null)throw Error(f(166));return Nt(e),null}if(s=et.current,$a(e))No(e);else{var d=Yu(ot.current);switch(s){case 1:s=d.createElementNS("http://www.w3.org/2000/svg",i);break;case 2:s=d.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;default:switch(i){case"svg":s=d.createElementNS("http://www.w3.org/2000/svg",i);break;case"math":s=d.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;case"script":s=d.createElement("div"),s.innerHTML="<script><\/script>",s=s.removeChild(s.firstChild);break;case"select":s=typeof a.is=="string"?d.createElement("select",{is:a.is}):d.createElement("select"),a.multiple?s.multiple=!0:a.size&&(s.size=a.size);break;default:s=typeof a.is=="string"?d.createElement(i,{is:a.is}):d.createElement(i)}}s[te]=e,s[oe]=a;t:for(d=e.child;d!==null;){if(d.tag===5||d.tag===6)s.appendChild(d.stateNode);else if(d.tag!==4&&d.tag!==27&&d.child!==null){d.child.return=d,d=d.child;continue}if(d===e)break t;for(;d.sibling===null;){if(d.return===null||d.return===e)break t;d=d.return}d.sibling.return=d.return,d=d.sibling}e.stateNode=s;t:switch(le(s,i,a),i){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&rn(e)}}return Nt(e),Xs(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,n),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&rn(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(f(166));if(t=ot.current,$a(e)){if(t=e.stateNode,n=e.memoizedProps,a=null,i=ee,i!==null)switch(i.tag){case 27:case 5:a=i.memoizedProps}t[te]=e,t=!!(t.nodeValue===n||a!==null&&a.suppressHydrationWarning===!0||Wd(t.nodeValue,n)),t||Bn(e,!0)}else t=Yu(t).createTextNode(a),t[te]=e,e.stateNode=t}return Nt(e),null;case 31:if(n=e.memoizedState,t===null||t.memoizedState!==null){if(a=$a(e),n!==null){if(t===null){if(!a)throw Error(f(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(557));t[te]=e}else ya(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Nt(e),t=!1}else n=_c(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=n),t=!0;if(!t)return e.flags&256?(Oe(e),e):(Oe(e),null);if((e.flags&128)!==0)throw Error(f(558))}return Nt(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(i=$a(e),a!==null&&a.dehydrated!==null){if(t===null){if(!i)throw Error(f(318));if(i=e.memoizedState,i=i!==null?i.dehydrated:null,!i)throw Error(f(317));i[te]=e}else ya(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Nt(e),i=!1}else i=_c(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=i),i=!0;if(!i)return e.flags&256?(Oe(e),e):(Oe(e),null)}return Oe(e),(e.flags&128)!==0?(e.lanes=n,e):(n=a!==null,t=t!==null&&t.memoizedState!==null,n&&(a=e.child,i=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(i=a.alternate.memoizedState.cachePool.pool),s=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(s=a.memoizedState.cachePool.pool),s!==i&&(a.flags|=2048)),n!==t&&n&&(e.child.flags|=8192),Su(e,e.updateQueue),Nt(e),null);case 4:return Qt(),t===null&&rf(e.stateNode.containerInfo),Nt(e),null;case 10:return un(e.type),Nt(e),null;case 19:if(L(Xt),a=e.memoizedState,a===null)return Nt(e),null;if(i=(e.flags&128)!==0,s=a.rendering,s===null)if(i)ei(a,!1);else{if(Gt!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(s=ru(t),s!==null){for(e.flags|=128,ei(a,!1),t=s.updateQueue,e.updateQueue=t,Su(e,t),e.subtreeFlags=0,t=n,n=e.child;n!==null;)Ro(n,t),n=n.sibling;return W(Xt,Xt.current&1|2),bt&&an(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&pe()>Ru&&(e.flags|=128,i=!0,ei(a,!1),e.lanes=4194304)}else{if(!i)if(t=ru(s),t!==null){if(e.flags|=128,i=!0,t=t.updateQueue,e.updateQueue=t,Su(e,t),ei(a,!0),a.tail===null&&a.tailMode==="hidden"&&!s.alternate&&!bt)return Nt(e),null}else 2*pe()-a.renderingStartTime>Ru&&n!==536870912&&(e.flags|=128,i=!0,ei(a,!1),e.lanes=4194304);a.isBackwards?(s.sibling=e.child,e.child=s):(t=a.last,t!==null?t.sibling=s:e.child=s,a.last=s)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=pe(),t.sibling=null,n=Xt.current,W(Xt,i?n&1|2:n&1),bt&&an(e,a.treeForkCount),t):(Nt(e),null);case 22:case 23:return Oe(e),rs(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(n&536870912)!==0&&(e.flags&128)===0&&(Nt(e),e.subtreeFlags&6&&(e.flags|=8192)):Nt(e),n=e.updateQueue,n!==null&&Su(e,n.retryQueue),n=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(n=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==n&&(e.flags|=2048),t!==null&&L(ba),null;case 24:return n=null,t!==null&&(n=t.memoizedState.cache),e.memoizedState.cache!==n&&(e.flags|=2048),un(Zt),Nt(e),null;case 25:return null;case 30:return null}throw Error(f(156,e.tag))}function Bg(t,e){switch(Fc(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return un(Zt),Qt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return ra(e),null;case 31:if(e.memoizedState!==null){if(Oe(e),e.alternate===null)throw Error(f(340));ya()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(Oe(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(f(340));ya()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return L(Xt),null;case 4:return Qt(),null;case 10:return un(e.type),null;case 22:case 23:return Oe(e),rs(),t!==null&&L(ba),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return un(Zt),null;case 25:return null;default:return null}}function ed(t,e){switch(Fc(e),e.tag){case 3:un(Zt),Qt();break;case 26:case 27:case 5:ra(e);break;case 4:Qt();break;case 31:e.memoizedState!==null&&Oe(e);break;case 13:Oe(e);break;case 19:L(Xt);break;case 10:un(e.type);break;case 22:case 23:Oe(e),rs(),t!==null&&L(ba);break;case 24:un(Zt)}}function ni(t,e){try{var n=e.updateQueue,a=n!==null?n.lastEffect:null;if(a!==null){var i=a.next;n=i;do{if((n.tag&t)===t){a=void 0;var s=n.create,d=n.inst;a=s(),d.destroy=a}n=n.next}while(n!==i)}}catch(g){wt(e,e.return,g)}}function Xn(t,e,n){try{var a=e.updateQueue,i=a!==null?a.lastEffect:null;if(i!==null){var s=i.next;a=s;do{if((a.tag&t)===t){var d=a.inst,g=d.destroy;if(g!==void 0){d.destroy=void 0,i=e;var T=n,z=g;try{z()}catch(V){wt(i,T,V)}}}a=a.next}while(a!==s)}}catch(V){wt(e,e.return,V)}}function nd(t){var e=t.updateQueue;if(e!==null){var n=t.stateNode;try{Ko(e,n)}catch(a){wt(t,t.return,a)}}}function ad(t,e,n){n.props=Oa(t.type,t.memoizedProps),n.state=t.memoizedState;try{n.componentWillUnmount()}catch(a){wt(t,e,a)}}function ai(t,e){try{var n=t.ref;if(n!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof n=="function"?t.refCleanup=n(a):n.current=a}}catch(i){wt(t,e,i)}}function We(t,e){var n=t.ref,a=t.refCleanup;if(n!==null)if(typeof a=="function")try{a()}catch(i){wt(t,e,i)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof n=="function")try{n(null)}catch(i){wt(t,e,i)}else n.current=null}function ld(t){var e=t.type,n=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":n.autoFocus&&a.focus();break t;case"img":n.src?a.src=n.src:n.srcSet&&(a.srcset=n.srcSet)}}catch(i){wt(t,t.return,i)}}function Vs(t,e,n){try{var a=t.stateNode;nA(a,t.type,n,e),a[oe]=e}catch(i){wt(t,t.return,i)}}function id(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&Jn(t.type)||t.tag===4}function Zs(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||id(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&Jn(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function qs(t,e,n){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(n.nodeType===9?n.body:n.nodeName==="HTML"?n.ownerDocument.body:n).insertBefore(t,e):(e=n.nodeType===9?n.body:n.nodeName==="HTML"?n.ownerDocument.body:n,e.appendChild(t),n=n._reactRootContainer,n!=null||e.onclick!==null||(e.onclick=tn));else if(a!==4&&(a===27&&Jn(t.type)&&(n=t.stateNode,e=null),t=t.child,t!==null))for(qs(t,e,n),t=t.sibling;t!==null;)qs(t,e,n),t=t.sibling}function Tu(t,e,n){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?n.insertBefore(t,e):n.appendChild(t);else if(a!==4&&(a===27&&Jn(t.type)&&(n=t.stateNode),t=t.child,t!==null))for(Tu(t,e,n),t=t.sibling;t!==null;)Tu(t,e,n),t=t.sibling}function ud(t){var e=t.stateNode,n=t.memoizedProps;try{for(var a=t.type,i=e.attributes;i.length;)e.removeAttributeNode(i[0]);le(e,a,n),e[te]=t,e[oe]=n}catch(s){wt(t,t.return,s)}}var on=!1,Kt=!1,Is=!1,cd=typeof WeakSet=="function"?WeakSet:Set,_t=null;function Ug(t,e){if(t=t.containerInfo,hf=Iu,t=Eo(t),Yc(t)){if("selectionStart"in t)var n={start:t.selectionStart,end:t.selectionEnd};else t:{n=(n=t.ownerDocument)&&n.defaultView||window;var a=n.getSelection&&n.getSelection();if(a&&a.rangeCount!==0){n=a.anchorNode;var i=a.anchorOffset,s=a.focusNode;a=a.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break t}var d=0,g=-1,T=-1,z=0,V=0,I=t,Y=null;e:for(;;){for(var G;I!==n||i!==0&&I.nodeType!==3||(g=d+i),I!==s||a!==0&&I.nodeType!==3||(T=d+a),I.nodeType===3&&(d+=I.nodeValue.length),(G=I.firstChild)!==null;)Y=I,I=G;for(;;){if(I===t)break e;if(Y===n&&++z===i&&(g=d),Y===s&&++V===a&&(T=d),(G=I.nextSibling)!==null)break;I=Y,Y=I.parentNode}I=G}n=g===-1||T===-1?null:{start:g,end:T}}else n=null}n=n||{start:0,end:0}}else n=null;for(mf={focusedElem:t,selectionRange:n},Iu=!1,_t=e;_t!==null;)if(e=_t,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,_t=t;else for(;_t!==null;){switch(e=_t,s=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(n=0;n<t.length;n++)i=t[n],i.ref.impl=i.nextImpl;break;case 11:case 15:break;case 1:if((t&1024)!==0&&s!==null){t=void 0,n=e,i=s.memoizedProps,s=s.memoizedState,a=n.stateNode;try{var at=Oa(n.type,i);t=a.getSnapshotBeforeUpdate(at,s),a.__reactInternalSnapshotBeforeUpdate=t}catch(ft){wt(n,n.return,ft)}}break;case 3:if((t&1024)!==0){if(t=e.stateNode.containerInfo,n=t.nodeType,n===9)vf(t);else if(n===1)switch(t.nodeName){case"HEAD":case"HTML":case"BODY":vf(t);break;default:t.textContent=""}}break;case 5:case 26:case 27:case 6:case 4:case 17:break;default:if((t&1024)!==0)throw Error(f(163))}if(t=e.sibling,t!==null){t.return=e.return,_t=t;break}_t=e.return}}function sd(t,e,n){var a=n.flags;switch(n.tag){case 0:case 11:case 15:hn(t,n),a&4&&ni(5,n);break;case 1:if(hn(t,n),a&4)if(t=n.stateNode,e===null)try{t.componentDidMount()}catch(d){wt(n,n.return,d)}else{var i=Oa(n.type,e.memoizedProps);e=e.memoizedState;try{t.componentDidUpdate(i,e,t.__reactInternalSnapshotBeforeUpdate)}catch(d){wt(n,n.return,d)}}a&64&&nd(n),a&512&&ai(n,n.return);break;case 3:if(hn(t,n),a&64&&(t=n.updateQueue,t!==null)){if(e=null,n.child!==null)switch(n.child.tag){case 27:case 5:e=n.child.stateNode;break;case 1:e=n.child.stateNode}try{Ko(t,e)}catch(d){wt(n,n.return,d)}}break;case 27:e===null&&a&4&&ud(n);case 26:case 5:hn(t,n),e===null&&a&4&&ld(n),a&512&&ai(n,n.return);break;case 12:hn(t,n);break;case 31:hn(t,n),a&4&&od(t,n);break;case 13:hn(t,n),a&4&&dd(t,n),a&64&&(t=n.memoizedState,t!==null&&(t=t.dehydrated,t!==null&&(n=qg.bind(null,n),rA(t,n))));break;case 22:if(a=n.memoizedState!==null||on,!a){e=e!==null&&e.memoizedState!==null||Kt,i=on;var s=Kt;on=a,(Kt=e)&&!s?mn(t,n,(n.subtreeFlags&8772)!==0):hn(t,n),on=i,Kt=s}break;case 30:break;default:hn(t,n)}}function fd(t){var e=t.alternate;e!==null&&(t.alternate=null,fd(e)),t.child=null,t.deletions=null,t.sibling=null,t.tag===5&&(e=t.stateNode,e!==null&&bc(e)),t.stateNode=null,t.return=null,t.dependencies=null,t.memoizedProps=null,t.memoizedState=null,t.pendingProps=null,t.stateNode=null,t.updateQueue=null}var zt=null,he=!1;function dn(t,e,n){for(n=n.child;n!==null;)rd(t,e,n),n=n.sibling}function rd(t,e,n){if(be&&typeof be.onCommitFiberUnmount=="function")try{be.onCommitFiberUnmount(wl,n)}catch{}switch(n.tag){case 26:Kt||We(n,e),dn(t,e,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode,n.parentNode.removeChild(n));break;case 27:Kt||We(n,e);var a=zt,i=he;Jn(n.type)&&(zt=n.stateNode,he=!1),dn(t,e,n),di(n.stateNode),zt=a,he=i;break;case 5:Kt||We(n,e);case 6:if(a=zt,i=he,zt=null,dn(t,e,n),zt=a,he=i,zt!==null)if(he)try{(zt.nodeType===9?zt.body:zt.nodeName==="HTML"?zt.ownerDocument.body:zt).removeChild(n.stateNode)}catch(s){wt(n,e,s)}else try{zt.removeChild(n.stateNode)}catch(s){wt(n,e,s)}break;case 18:zt!==null&&(he?(t=zt,n1(t.nodeType===9?t.body:t.nodeName==="HTML"?t.ownerDocument.body:t,n.stateNode),pl(t)):n1(zt,n.stateNode));break;case 4:a=zt,i=he,zt=n.stateNode.containerInfo,he=!0,dn(t,e,n),zt=a,he=i;break;case 0:case 11:case 14:case 15:Xn(2,n,e),Kt||Xn(4,n,e),dn(t,e,n);break;case 1:Kt||(We(n,e),a=n.stateNode,typeof a.componentWillUnmount=="function"&&ad(n,e,a)),dn(t,e,n);break;case 21:dn(t,e,n);break;case 22:Kt=(a=Kt)||n.memoizedState!==null,dn(t,e,n),Kt=a;break;default:dn(t,e,n)}}function od(t,e){if(e.memoizedState===null&&(t=e.alternate,t!==null&&(t=t.memoizedState,t!==null))){t=t.dehydrated;try{pl(t)}catch(n){wt(e,e.return,n)}}}function dd(t,e){if(e.memoizedState===null&&(t=e.alternate,t!==null&&(t=t.memoizedState,t!==null&&(t=t.dehydrated,t!==null))))try{pl(t)}catch(n){wt(e,e.return,n)}}function Qg(t){switch(t.tag){case 31:case 13:case 19:var e=t.stateNode;return e===null&&(e=t.stateNode=new cd),e;case 22:return t=t.stateNode,e=t._retryCache,e===null&&(e=t._retryCache=new cd),e;default:throw Error(f(435,t.tag))}}function Cu(t,e){var n=Qg(t);e.forEach(function(a){if(!n.has(a)){n.add(a);var i=Ig.bind(null,t,a);a.then(i,i)}})}function me(t,e){var n=e.deletions;if(n!==null)for(var a=0;a<n.length;a++){var i=n[a],s=t,d=e,g=d;t:for(;g!==null;){switch(g.tag){case 27:if(Jn(g.type)){zt=g.stateNode,he=!1;break t}break;case 5:zt=g.stateNode,he=!1;break t;case 3:case 4:zt=g.stateNode.containerInfo,he=!0;break t}g=g.return}if(zt===null)throw Error(f(160));rd(s,d,i),zt=null,he=!1,s=i.alternate,s!==null&&(s.return=null),i.return=null}if(e.subtreeFlags&13886)for(e=e.child;e!==null;)hd(e,t),e=e.sibling}var Ie=null;function hd(t,e){var n=t.alternate,a=t.flags;switch(t.tag){case 0:case 11:case 14:case 15:me(e,t),ge(t),a&4&&(Xn(3,t,t.return),ni(3,t),Xn(5,t,t.return));break;case 1:me(e,t),ge(t),a&512&&(Kt||n===null||We(n,n.return)),a&64&&on&&(t=t.updateQueue,t!==null&&(a=t.callbacks,a!==null&&(n=t.shared.hiddenCallbacks,t.shared.hiddenCallbacks=n===null?a:n.concat(a))));break;case 26:var i=Ie;if(me(e,t),ge(t),a&512&&(Kt||n===null||We(n,n.return)),a&4){var s=n!==null?n.memoizedState:null;if(a=t.memoizedState,n===null)if(a===null)if(t.stateNode===null){t:{a=t.type,n=t.memoizedProps,i=i.ownerDocument||i;e:switch(a){case"title":s=i.getElementsByTagName("title")[0],(!s||s[Ml]||s[te]||s.namespaceURI==="http://www.w3.org/2000/svg"||s.hasAttribute("itemprop"))&&(s=i.createElement(a),i.head.insertBefore(s,i.querySelector("head > title"))),le(s,a,n),s[te]=t,Wt(s),a=s;break t;case"link":var d=h1("link","href",i).get(a+(n.href||""));if(d){for(var g=0;g<d.length;g++)if(s=d[g],s.getAttribute("href")===(n.href==null||n.href===""?null:n.href)&&s.getAttribute("rel")===(n.rel==null?null:n.rel)&&s.getAttribute("title")===(n.title==null?null:n.title)&&s.getAttribute("crossorigin")===(n.crossOrigin==null?null:n.crossOrigin)){d.splice(g,1);break e}}s=i.createElement(a),le(s,a,n),i.head.appendChild(s);break;case"meta":if(d=h1("meta","content",i).get(a+(n.content||""))){for(g=0;g<d.length;g++)if(s=d[g],s.getAttribute("content")===(n.content==null?null:""+n.content)&&s.getAttribute("name")===(n.name==null?null:n.name)&&s.getAttribute("property")===(n.property==null?null:n.property)&&s.getAttribute("http-equiv")===(n.httpEquiv==null?null:n.httpEquiv)&&s.getAttribute("charset")===(n.charSet==null?null:n.charSet)){d.splice(g,1);break e}}s=i.createElement(a),le(s,a,n),i.head.appendChild(s);break;default:throw Error(f(468,a))}s[te]=t,Wt(s),a=s}t.stateNode=a}else m1(i,t.type,t.stateNode);else t.stateNode=d1(i,a,t.memoizedProps);else s!==a?(s===null?n.stateNode!==null&&(n=n.stateNode,n.parentNode.removeChild(n)):s.count--,a===null?m1(i,t.type,t.stateNode):d1(i,a,t.memoizedProps)):a===null&&t.stateNode!==null&&Vs(t,t.memoizedProps,n.memoizedProps)}break;case 27:me(e,t),ge(t),a&512&&(Kt||n===null||We(n,n.return)),n!==null&&a&4&&Vs(t,t.memoizedProps,n.memoizedProps);break;case 5:if(me(e,t),ge(t),a&512&&(Kt||n===null||We(n,n.return)),t.flags&32){i=t.stateNode;try{Za(i,"")}catch(at){wt(t,t.return,at)}}a&4&&t.stateNode!=null&&(i=t.memoizedProps,Vs(t,i,n!==null?n.memoizedProps:i)),a&1024&&(Is=!0);break;case 6:if(me(e,t),ge(t),a&4){if(t.stateNode===null)throw Error(f(162));a=t.memoizedProps,n=t.stateNode;try{n.nodeValue=a}catch(at){wt(t,t.return,at)}}break;case 3:if(Xu=null,i=Ie,Ie=Lu(e.containerInfo),me(e,t),Ie=i,ge(t),a&4&&n!==null&&n.memoizedState.isDehydrated)try{pl(e.containerInfo)}catch(at){wt(t,t.return,at)}Is&&(Is=!1,md(t));break;case 4:a=Ie,Ie=Lu(t.stateNode.containerInfo),me(e,t),ge(t),Ie=a;break;case 12:me(e,t),ge(t);break;case 31:me(e,t),ge(t),a&4&&(a=t.updateQueue,a!==null&&(t.updateQueue=null,Cu(t,a)));break;case 13:me(e,t),ge(t),t.child.flags&8192&&t.memoizedState!==null!=(n!==null&&n.memoizedState!==null)&&(wu=pe()),a&4&&(a=t.updateQueue,a!==null&&(t.updateQueue=null,Cu(t,a)));break;case 22:i=t.memoizedState!==null;var T=n!==null&&n.memoizedState!==null,z=on,V=Kt;if(on=z||i,Kt=V||T,me(e,t),Kt=V,on=z,ge(t),a&8192)t:for(e=t.stateNode,e._visibility=i?e._visibility&-2:e._visibility|1,i&&(n===null||T||on||Kt||wa(t)),n=null,e=t;;){if(e.tag===5||e.tag===26){if(n===null){T=n=e;try{if(s=T.stateNode,i)d=s.style,typeof d.setProperty=="function"?d.setProperty("display","none","important"):d.display="none";else{g=T.stateNode;var I=T.memoizedProps.style,Y=I!=null&&I.hasOwnProperty("display")?I.display:null;g.style.display=Y==null||typeof Y=="boolean"?"":(""+Y).trim()}}catch(at){wt(T,T.return,at)}}}else if(e.tag===6){if(n===null){T=e;try{T.stateNode.nodeValue=i?"":T.memoizedProps}catch(at){wt(T,T.return,at)}}}else if(e.tag===18){if(n===null){T=e;try{var G=T.stateNode;i?a1(G,!0):a1(T.stateNode,!1)}catch(at){wt(T,T.return,at)}}}else if((e.tag!==22&&e.tag!==23||e.memoizedState===null||e===t)&&e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break t;for(;e.sibling===null;){if(e.return===null||e.return===t)break t;n===e&&(n=null),e=e.return}n===e&&(n=null),e.sibling.return=e.return,e=e.sibling}a&4&&(a=t.updateQueue,a!==null&&(n=a.retryQueue,n!==null&&(a.retryQueue=null,Cu(t,n))));break;case 19:me(e,t),ge(t),a&4&&(a=t.updateQueue,a!==null&&(t.updateQueue=null,Cu(t,a)));break;case 30:break;case 21:break;default:me(e,t),ge(t)}}function ge(t){var e=t.flags;if(e&2){try{for(var n,a=t.return;a!==null;){if(id(a)){n=a;break}a=a.return}if(n==null)throw Error(f(160));switch(n.tag){case 27:var i=n.stateNode,s=Zs(t);Tu(t,s,i);break;case 5:var d=n.stateNode;n.flags&32&&(Za(d,""),n.flags&=-33);var g=Zs(t);Tu(t,g,d);break;case 3:case 4:var T=n.stateNode.containerInfo,z=Zs(t);qs(t,z,T);break;default:throw Error(f(161))}}catch(V){wt(t,t.return,V)}t.flags&=-3}e&4096&&(t.flags&=-4097)}function md(t){if(t.subtreeFlags&1024)for(t=t.child;t!==null;){var e=t;md(e),e.tag===5&&e.flags&1024&&e.stateNode.reset(),t=t.sibling}}function hn(t,e){if(e.subtreeFlags&8772)for(e=e.child;e!==null;)sd(t,e.alternate,e),e=e.sibling}function wa(t){for(t=t.child;t!==null;){var e=t;switch(e.tag){case 0:case 11:case 14:case 15:Xn(4,e,e.return),wa(e);break;case 1:We(e,e.return);var n=e.stateNode;typeof n.componentWillUnmount=="function"&&ad(e,e.return,n),wa(e);break;case 27:di(e.stateNode);case 26:case 5:We(e,e.return),wa(e);break;case 22:e.memoizedState===null&&wa(e);break;case 30:wa(e);break;default:wa(e)}t=t.sibling}}function mn(t,e,n){for(n=n&&(e.subtreeFlags&8772)!==0,e=e.child;e!==null;){var a=e.alternate,i=t,s=e,d=s.flags;switch(s.tag){case 0:case 11:case 15:mn(i,s,n),ni(4,s);break;case 1:if(mn(i,s,n),a=s,i=a.stateNode,typeof i.componentDidMount=="function")try{i.componentDidMount()}catch(z){wt(a,a.return,z)}if(a=s,i=a.updateQueue,i!==null){var g=a.stateNode;try{var T=i.shared.hiddenCallbacks;if(T!==null)for(i.shared.hiddenCallbacks=null,i=0;i<T.length;i++)Io(T[i],g)}catch(z){wt(a,a.return,z)}}n&&d&64&&nd(s),ai(s,s.return);break;case 27:ud(s);case 26:case 5:mn(i,s,n),n&&a===null&&d&4&&ld(s),ai(s,s.return);break;case 12:mn(i,s,n);break;case 31:mn(i,s,n),n&&d&4&&od(i,s);break;case 13:mn(i,s,n),n&&d&4&&dd(i,s);break;case 22:s.memoizedState===null&&mn(i,s,n),ai(s,s.return);break;case 30:break;default:mn(i,s,n)}e=e.sibling}}function Ks(t,e){var n=null;t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(n=t.memoizedState.cachePool.pool),t=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(t=e.memoizedState.cachePool.pool),t!==n&&(t!=null&&t.refCount++,n!=null&&Zl(n))}function ks(t,e){t=null,e.alternate!==null&&(t=e.alternate.memoizedState.cache),e=e.memoizedState.cache,e!==t&&(e.refCount++,t!=null&&Zl(t))}function Ke(t,e,n,a){if(e.subtreeFlags&10256)for(e=e.child;e!==null;)gd(t,e,n,a),e=e.sibling}function gd(t,e,n,a){var i=e.flags;switch(e.tag){case 0:case 11:case 15:Ke(t,e,n,a),i&2048&&ni(9,e);break;case 1:Ke(t,e,n,a);break;case 3:Ke(t,e,n,a),i&2048&&(t=null,e.alternate!==null&&(t=e.alternate.memoizedState.cache),e=e.memoizedState.cache,e!==t&&(e.refCount++,t!=null&&Zl(t)));break;case 12:if(i&2048){Ke(t,e,n,a),t=e.stateNode;try{var s=e.memoizedProps,d=s.id,g=s.onPostCommit;typeof g=="function"&&g(d,e.alternate===null?"mount":"update",t.passiveEffectDuration,-0)}catch(T){wt(e,e.return,T)}}else Ke(t,e,n,a);break;case 31:Ke(t,e,n,a);break;case 13:Ke(t,e,n,a);break;case 23:break;case 22:s=e.stateNode,d=e.alternate,e.memoizedState!==null?s._visibility&2?Ke(t,e,n,a):li(t,e):s._visibility&2?Ke(t,e,n,a):(s._visibility|=2,fl(t,e,n,a,(e.subtreeFlags&10256)!==0||!1)),i&2048&&Ks(d,e);break;case 24:Ke(t,e,n,a),i&2048&&ks(e.alternate,e);break;default:Ke(t,e,n,a)}}function fl(t,e,n,a,i){for(i=i&&((e.subtreeFlags&10256)!==0||!1),e=e.child;e!==null;){var s=t,d=e,g=n,T=a,z=d.flags;switch(d.tag){case 0:case 11:case 15:fl(s,d,g,T,i),ni(8,d);break;case 23:break;case 22:var V=d.stateNode;d.memoizedState!==null?V._visibility&2?fl(s,d,g,T,i):li(s,d):(V._visibility|=2,fl(s,d,g,T,i)),i&&z&2048&&Ks(d.alternate,d);break;case 24:fl(s,d,g,T,i),i&&z&2048&&ks(d.alternate,d);break;default:fl(s,d,g,T,i)}e=e.sibling}}function li(t,e){if(e.subtreeFlags&10256)for(e=e.child;e!==null;){var n=t,a=e,i=a.flags;switch(a.tag){case 22:li(n,a),i&2048&&Ks(a.alternate,a);break;case 24:li(n,a),i&2048&&ks(a.alternate,a);break;default:li(n,a)}e=e.sibling}}var ii=8192;function rl(t,e,n){if(t.subtreeFlags&ii)for(t=t.child;t!==null;)Ad(t,e,n),t=t.sibling}function Ad(t,e,n){switch(t.tag){case 26:rl(t,e,n),t.flags&ii&&t.memoizedState!==null&&xA(n,Ie,t.memoizedState,t.memoizedProps);break;case 5:rl(t,e,n);break;case 3:case 4:var a=Ie;Ie=Lu(t.stateNode.containerInfo),rl(t,e,n),Ie=a;break;case 22:t.memoizedState===null&&(a=t.alternate,a!==null&&a.memoizedState!==null?(a=ii,ii=16777216,rl(t,e,n),ii=a):rl(t,e,n));break;default:rl(t,e,n)}}function vd(t){var e=t.alternate;if(e!==null&&(t=e.child,t!==null)){e.child=null;do e=t.sibling,t.sibling=null,t=e;while(t!==null)}}function ui(t){var e=t.deletions;if((t.flags&16)!==0){if(e!==null)for(var n=0;n<e.length;n++){var a=e[n];_t=a,Ed(a,t)}vd(t)}if(t.subtreeFlags&10256)for(t=t.child;t!==null;)yd(t),t=t.sibling}function yd(t){switch(t.tag){case 0:case 11:case 15:ui(t),t.flags&2048&&Xn(9,t,t.return);break;case 3:ui(t);break;case 12:ui(t);break;case 22:var e=t.stateNode;t.memoizedState!==null&&e._visibility&2&&(t.return===null||t.return.tag!==13)?(e._visibility&=-3,Ou(t)):ui(t);break;default:ui(t)}}function Ou(t){var e=t.deletions;if((t.flags&16)!==0){if(e!==null)for(var n=0;n<e.length;n++){var a=e[n];_t=a,Ed(a,t)}vd(t)}for(t=t.child;t!==null;){switch(e=t,e.tag){case 0:case 11:case 15:Xn(8,e,e.return),Ou(e);break;case 22:n=e.stateNode,n._visibility&2&&(n._visibility&=-3,Ou(e));break;default:Ou(e)}t=t.sibling}}function Ed(t,e){for(;_t!==null;){var n=_t;switch(n.tag){case 0:case 11:case 15:Xn(8,n,e);break;case 23:case 22:if(n.memoizedState!==null&&n.memoizedState.cachePool!==null){var a=n.memoizedState.cachePool.pool;a!=null&&a.refCount++}break;case 24:Zl(n.memoizedState.cache)}if(a=n.child,a!==null)a.return=n,_t=a;else t:for(n=t;_t!==null;){a=_t;var i=a.sibling,s=a.return;if(fd(a),a===n){_t=null;break t}if(i!==null){i.return=s,_t=i;break t}_t=s}}}var zg={getCacheForType:function(t){var e=ne(Zt),n=e.data.get(t);return n===void 0&&(n=t(),e.data.set(t,n)),n},cacheSignal:function(){return ne(Zt).controller.signal}},Yg=typeof WeakMap=="function"?WeakMap:Map,Ct=0,jt=null,vt=null,Et=0,Ot=0,we=null,Vn=!1,ol=!1,Js=!1,gn=0,Gt=0,Zn=0,Ra=0,Fs=0,Re=0,dl=0,ci=null,Ae=null,Ws=!1,wu=0,pd=0,Ru=1/0,Du=null,qn=null,kt=0,In=null,hl=null,An=0,_s=0,Ps=null,bd=null,si=0,$s=null;function De(){return(Ct&2)!==0&&Et!==0?Et&-Et:H.T!==null?uf():Qr()}function xd(){if(Re===0)if((Et&536870912)===0||bt){var t=zi;zi<<=1,(zi&3932160)===0&&(zi=262144),Re=t}else Re=536870912;return t=Ce.current,t!==null&&(t.flags|=32),Re}function ve(t,e,n){(t===jt&&(Ot===2||Ot===9)||t.cancelPendingCommit!==null)&&(ml(t,0),Kn(t,Et,Re,!1)),Dl(t,n),((Ct&2)===0||t!==jt)&&(t===jt&&((Ct&2)===0&&(Ra|=n),Gt===4&&Kn(t,Et,Re,!1)),_e(t))}function Sd(t,e,n){if((Ct&6)!==0)throw Error(f(327));var a=!n&&(e&127)===0&&(e&t.expiredLanes)===0||Rl(t,e),i=a?Xg(t,e):ef(t,e,!0),s=a;do{if(i===0){ol&&!a&&Kn(t,e,0,!1);break}else{if(n=t.current.alternate,s&&!Lg(n)){i=ef(t,e,!1),s=!1;continue}if(i===2){if(s=e,t.errorRecoveryDisabledLanes&s)var d=0;else d=t.pendingLanes&-536870913,d=d!==0?d:d&536870912?536870912:0;if(d!==0){e=d;t:{var g=t;i=ci;var T=g.current.memoizedState.isDehydrated;if(T&&(ml(g,d).flags|=256),d=ef(g,d,!1),d!==2){if(Js&&!T){g.errorRecoveryDisabledLanes|=s,Ra|=s,i=4;break t}s=Ae,Ae=i,s!==null&&(Ae===null?Ae=s:Ae.push.apply(Ae,s))}i=d}if(s=!1,i!==2)continue}}if(i===1){ml(t,0),Kn(t,e,0,!0);break}t:{switch(a=t,s=i,s){case 0:case 1:throw Error(f(345));case 4:if((e&4194048)!==e)break;case 6:Kn(a,e,Re,!Vn);break t;case 2:Ae=null;break;case 3:case 5:break;default:throw Error(f(329))}if((e&62914560)===e&&(i=wu+300-pe(),10<i)){if(Kn(a,e,Re,!Vn),Li(a,0,!0)!==0)break t;An=e,a.timeoutHandle=t1(Td.bind(null,a,n,Ae,Du,Ws,e,Re,Ra,dl,Vn,s,"Throttled",-0,0),i);break t}Td(a,n,Ae,Du,Ws,e,Re,Ra,dl,Vn,s,null,-0,0)}}break}while(!0);_e(t)}function Td(t,e,n,a,i,s,d,g,T,z,V,I,Y,G){if(t.timeoutHandle=-1,I=e.subtreeFlags,I&8192||(I&16785408)===16785408){I={stylesheets:null,count:0,imgCount:0,imgBytes:0,suspenseyImages:[],waitingForImages:!0,waitingForViewTransition:!1,unsuspend:tn},Ad(e,s,I);var at=(s&62914560)===s?wu-pe():(s&4194048)===s?pd-pe():0;if(at=SA(I,at),at!==null){An=s,t.cancelPendingCommit=at(Hd.bind(null,t,e,s,n,a,i,d,g,T,V,I,null,Y,G)),Kn(t,s,d,!z);return}}Hd(t,e,s,n,a,i,d,g,T)}function Lg(t){for(var e=t;;){var n=e.tag;if((n===0||n===11||n===15)&&e.flags&16384&&(n=e.updateQueue,n!==null&&(n=n.stores,n!==null)))for(var a=0;a<n.length;a++){var i=n[a],s=i.getSnapshot;i=i.value;try{if(!Se(s(),i))return!1}catch{return!1}}if(n=e.child,e.subtreeFlags&16384&&n!==null)n.return=e,e=n;else{if(e===t)break;for(;e.sibling===null;){if(e.return===null||e.return===t)return!0;e=e.return}e.sibling.return=e.return,e=e.sibling}}return!0}function Kn(t,e,n,a){e&=~Fs,e&=~Ra,t.suspendedLanes|=e,t.pingedLanes&=~e,a&&(t.warmLanes|=e),a=t.expirationTimes;for(var i=e;0<i;){var s=31-xe(i),d=1<<s;a[s]=-1,i&=~d}n!==0&&Nr(t,n,e)}function Mu(){return(Ct&6)===0?(fi(0),!1):!0}function tf(){if(vt!==null){if(Ot===0)var t=vt.return;else t=vt,ln=Ea=null,As(t),ll=null,Il=0,t=vt;for(;t!==null;)ed(t.alternate,t),t=t.return;vt=null}}function ml(t,e){var n=t.timeoutHandle;n!==-1&&(t.timeoutHandle=-1,iA(n)),n=t.cancelPendingCommit,n!==null&&(t.cancelPendingCommit=null,n()),An=0,tf(),jt=t,vt=n=nn(t.current,null),Et=e,Ot=0,we=null,Vn=!1,ol=Rl(t,e),Js=!1,dl=Re=Fs=Ra=Zn=Gt=0,Ae=ci=null,Ws=!1,(e&8)!==0&&(e|=e&32);var a=t.entangledLanes;if(a!==0)for(t=t.entanglements,a&=e;0<a;){var i=31-xe(a),s=1<<i;e|=t[i],a&=~s}return gn=e,_i(),n}function Cd(t,e){mt=null,H.H=$l,e===al||e===iu?(e=Xo(),Ot=3):e===ls?(e=Xo(),Ot=4):Ot=e===Hs?8:e!==null&&typeof e=="object"&&typeof e.then=="function"?6:1,we=e,vt===null&&(Gt=1,Eu(t,Be(e,t.current)))}function Od(){var t=Ce.current;return t===null?!0:(Et&4194048)===Et?Ye===null:(Et&62914560)===Et||(Et&536870912)!==0?t===Ye:!1}function wd(){var t=H.H;return H.H=$l,t===null?$l:t}function Rd(){var t=H.A;return H.A=zg,t}function ju(){Gt=4,Vn||(Et&4194048)!==Et&&Ce.current!==null||(ol=!0),(Zn&134217727)===0&&(Ra&134217727)===0||jt===null||Kn(jt,Et,Re,!1)}function ef(t,e,n){var a=Ct;Ct|=2;var i=wd(),s=Rd();(jt!==t||Et!==e)&&(Du=null,ml(t,e)),e=!1;var d=Gt;t:do try{if(Ot!==0&&vt!==null){var g=vt,T=we;switch(Ot){case 8:tf(),d=6;break t;case 3:case 2:case 9:case 6:Ce.current===null&&(e=!0);var z=Ot;if(Ot=0,we=null,gl(t,g,T,z),n&&ol){d=0;break t}break;default:z=Ot,Ot=0,we=null,gl(t,g,T,z)}}Gg(),d=Gt;break}catch(V){Cd(t,V)}while(!0);return e&&t.shellSuspendCounter++,ln=Ea=null,Ct=a,H.H=i,H.A=s,vt===null&&(jt=null,Et=0,_i()),d}function Gg(){for(;vt!==null;)Dd(vt)}function Xg(t,e){var n=Ct;Ct|=2;var a=wd(),i=Rd();jt!==t||Et!==e?(Du=null,Ru=pe()+500,ml(t,e)):ol=Rl(t,e);t:do try{if(Ot!==0&&vt!==null){e=vt;var s=we;e:switch(Ot){case 1:Ot=0,we=null,gl(t,e,s,1);break;case 2:case 9:if(Lo(s)){Ot=0,we=null,Md(e);break}e=function(){Ot!==2&&Ot!==9||jt!==t||(Ot=7),_e(t)},s.then(e,e);break t;case 3:Ot=7;break t;case 4:Ot=5;break t;case 7:Lo(s)?(Ot=0,we=null,Md(e)):(Ot=0,we=null,gl(t,e,s,7));break;case 5:var d=null;switch(vt.tag){case 26:d=vt.memoizedState;case 5:case 27:var g=vt;if(d?g1(d):g.stateNode.complete){Ot=0,we=null;var T=g.sibling;if(T!==null)vt=T;else{var z=g.return;z!==null?(vt=z,Hu(z)):vt=null}break e}}Ot=0,we=null,gl(t,e,s,5);break;case 6:Ot=0,we=null,gl(t,e,s,6);break;case 8:tf(),Gt=6;break t;default:throw Error(f(462))}}Vg();break}catch(V){Cd(t,V)}while(!0);return ln=Ea=null,H.H=a,H.A=i,Ct=n,vt!==null?0:(jt=null,Et=0,_i(),Gt)}function Vg(){for(;vt!==null&&!om();)Dd(vt)}function Dd(t){var e=$0(t.alternate,t,gn);t.memoizedProps=t.pendingProps,e===null?Hu(t):vt=e}function Md(t){var e=t,n=e.alternate;switch(e.tag){case 15:case 0:e=k0(n,e,e.pendingProps,e.type,void 0,Et);break;case 11:e=k0(n,e,e.pendingProps,e.type.render,e.ref,Et);break;case 5:As(e);default:ed(n,e),e=vt=Ro(e,gn),e=$0(n,e,gn)}t.memoizedProps=t.pendingProps,e===null?Hu(t):vt=e}function gl(t,e,n,a){ln=Ea=null,As(e),ll=null,Il=0;var i=e.return;try{if(Mg(t,i,e,n,Et)){Gt=1,Eu(t,Be(n,t.current)),vt=null;return}}catch(s){if(i!==null)throw vt=i,s;Gt=1,Eu(t,Be(n,t.current)),vt=null;return}e.flags&32768?(bt||a===1?t=!0:ol||(Et&536870912)!==0?t=!1:(Vn=t=!0,(a===2||a===9||a===3||a===6)&&(a=Ce.current,a!==null&&a.tag===13&&(a.flags|=16384))),jd(e,t)):Hu(e)}function Hu(t){var e=t;do{if((e.flags&32768)!==0){jd(e,Vn);return}t=e.return;var n=Ng(e.alternate,e,gn);if(n!==null){vt=n;return}if(e=e.sibling,e!==null){vt=e;return}vt=e=t}while(e!==null);Gt===0&&(Gt=5)}function jd(t,e){do{var n=Bg(t.alternate,t);if(n!==null){n.flags&=32767,vt=n;return}if(n=t.return,n!==null&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!e&&(t=t.sibling,t!==null)){vt=t;return}vt=t=n}while(t!==null);Gt=6,vt=null}function Hd(t,e,n,a,i,s,d,g,T){t.cancelPendingCommit=null;do Nu();while(kt!==0);if((Ct&6)!==0)throw Error(f(327));if(e!==null){if(e===t.current)throw Error(f(177));if(s=e.lanes|e.childLanes,s|=Zc,bm(t,n,s,d,g,T),t===jt&&(vt=jt=null,Et=0),hl=e,In=t,An=n,_s=s,Ps=i,bd=a,(e.subtreeFlags&10256)!==0||(e.flags&10256)!==0?(t.callbackNode=null,t.callbackPriority=0,Kg(Ui,function(){return zd(),null})):(t.callbackNode=null,t.callbackPriority=0),a=(e.flags&13878)!==0,(e.subtreeFlags&13878)!==0||a){a=H.T,H.T=null,i=_.p,_.p=2,d=Ct,Ct|=4;try{Ug(t,e,n)}finally{Ct=d,_.p=i,H.T=a}}kt=1,Nd(),Bd(),Ud()}}function Nd(){if(kt===1){kt=0;var t=In,e=hl,n=(e.flags&13878)!==0;if((e.subtreeFlags&13878)!==0||n){n=H.T,H.T=null;var a=_.p;_.p=2;var i=Ct;Ct|=4;try{hd(e,t);var s=mf,d=Eo(t.containerInfo),g=s.focusedElem,T=s.selectionRange;if(d!==g&&g&&g.ownerDocument&&yo(g.ownerDocument.documentElement,g)){if(T!==null&&Yc(g)){var z=T.start,V=T.end;if(V===void 0&&(V=z),"selectionStart"in g)g.selectionStart=z,g.selectionEnd=Math.min(V,g.value.length);else{var I=g.ownerDocument||document,Y=I&&I.defaultView||window;if(Y.getSelection){var G=Y.getSelection(),at=g.textContent.length,ft=Math.min(T.start,at),Mt=T.end===void 0?ft:Math.min(T.end,at);!G.extend&&ft>Mt&&(d=Mt,Mt=ft,ft=d);var M=vo(g,ft),w=vo(g,Mt);if(M&&w&&(G.rangeCount!==1||G.anchorNode!==M.node||G.anchorOffset!==M.offset||G.focusNode!==w.node||G.focusOffset!==w.offset)){var Q=I.createRange();Q.setStart(M.node,M.offset),G.removeAllRanges(),ft>Mt?(G.addRange(Q),G.extend(w.node,w.offset)):(Q.setEnd(w.node,w.offset),G.addRange(Q))}}}}for(I=[],G=g;G=G.parentNode;)G.nodeType===1&&I.push({element:G,left:G.scrollLeft,top:G.scrollTop});for(typeof g.focus=="function"&&g.focus(),g=0;g<I.length;g++){var q=I[g];q.element.scrollLeft=q.left,q.element.scrollTop=q.top}}Iu=!!hf,mf=hf=null}finally{Ct=i,_.p=a,H.T=n}}t.current=e,kt=2}}function Bd(){if(kt===2){kt=0;var t=In,e=hl,n=(e.flags&8772)!==0;if((e.subtreeFlags&8772)!==0||n){n=H.T,H.T=null;var a=_.p;_.p=2;var i=Ct;Ct|=4;try{sd(t,e.alternate,e)}finally{Ct=i,_.p=a,H.T=n}}kt=3}}function Ud(){if(kt===4||kt===3){kt=0,dm();var t=In,e=hl,n=An,a=bd;(e.subtreeFlags&10256)!==0||(e.flags&10256)!==0?kt=5:(kt=0,hl=In=null,Qd(t,t.pendingLanes));var i=t.pendingLanes;if(i===0&&(qn=null),Ec(n),e=e.stateNode,be&&typeof be.onCommitFiberRoot=="function")try{be.onCommitFiberRoot(wl,e,void 0,(e.current.flags&128)===128)}catch{}if(a!==null){e=H.T,i=_.p,_.p=2,H.T=null;try{for(var s=t.onRecoverableError,d=0;d<a.length;d++){var g=a[d];s(g.value,{componentStack:g.stack})}}finally{H.T=e,_.p=i}}(An&3)!==0&&Nu(),_e(t),i=t.pendingLanes,(n&261930)!==0&&(i&42)!==0?t===$s?si++:(si=0,$s=t):si=0,fi(0)}}function Qd(t,e){(t.pooledCacheLanes&=e)===0&&(e=t.pooledCache,e!=null&&(t.pooledCache=null,Zl(e)))}function Nu(){return Nd(),Bd(),Ud(),zd()}function zd(){if(kt!==5)return!1;var t=In,e=_s;_s=0;var n=Ec(An),a=H.T,i=_.p;try{_.p=32>n?32:n,H.T=null,n=Ps,Ps=null;var s=In,d=An;if(kt=0,hl=In=null,An=0,(Ct&6)!==0)throw Error(f(331));var g=Ct;if(Ct|=4,yd(s.current),gd(s,s.current,d,n),Ct=g,fi(0,!1),be&&typeof be.onPostCommitFiberRoot=="function")try{be.onPostCommitFiberRoot(wl,s)}catch{}return!0}finally{_.p=i,H.T=a,Qd(t,e)}}function Yd(t,e,n){e=Be(n,e),e=js(t.stateNode,e,2),t=Yn(t,e,2),t!==null&&(Dl(t,2),_e(t))}function wt(t,e,n){if(t.tag===3)Yd(t,t,n);else for(;e!==null;){if(e.tag===3){Yd(e,t,n);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(qn===null||!qn.has(a))){t=Be(n,t),n=L0(2),a=Yn(e,n,2),a!==null&&(G0(n,a,e,t),Dl(a,2),_e(a));break}}e=e.return}}function nf(t,e,n){var a=t.pingCache;if(a===null){a=t.pingCache=new Yg;var i=new Set;a.set(e,i)}else i=a.get(e),i===void 0&&(i=new Set,a.set(e,i));i.has(n)||(Js=!0,i.add(n),t=Zg.bind(null,t,e,n),e.then(t,t))}function Zg(t,e,n){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&n,t.warmLanes&=~n,jt===t&&(Et&n)===n&&(Gt===4||Gt===3&&(Et&62914560)===Et&&300>pe()-wu?(Ct&2)===0&&ml(t,0):Fs|=n,dl===Et&&(dl=0)),_e(t)}function Ld(t,e){e===0&&(e=Hr()),t=Aa(t,e),t!==null&&(Dl(t,e),_e(t))}function qg(t){var e=t.memoizedState,n=0;e!==null&&(n=e.retryLane),Ld(t,n)}function Ig(t,e){var n=0;switch(t.tag){case 31:case 13:var a=t.stateNode,i=t.memoizedState;i!==null&&(n=i.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(e),Ld(t,n)}function Kg(t,e){return gc(t,e)}var Bu=null,Al=null,af=!1,Uu=!1,lf=!1,kn=0;function _e(t){t!==Al&&t.next===null&&(Al===null?Bu=Al=t:Al=Al.next=t),Uu=!0,af||(af=!0,Jg())}function fi(t,e){if(!lf&&Uu){lf=!0;do for(var n=!1,a=Bu;a!==null;){if(t!==0){var i=a.pendingLanes;if(i===0)var s=0;else{var d=a.suspendedLanes,g=a.pingedLanes;s=(1<<31-xe(42|t)+1)-1,s&=i&~(d&~g),s=s&201326741?s&201326741|1:s?s|2:0}s!==0&&(n=!0,Zd(a,s))}else s=Et,s=Li(a,a===jt?s:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(s&3)===0||Rl(a,s)||(n=!0,Zd(a,s));a=a.next}while(n);lf=!1}}function kg(){Gd()}function Gd(){Uu=af=!1;var t=0;kn!==0&&lA()&&(t=kn);for(var e=pe(),n=null,a=Bu;a!==null;){var i=a.next,s=Xd(a,e);s===0?(a.next=null,n===null?Bu=i:n.next=i,i===null&&(Al=n)):(n=a,(t!==0||(s&3)!==0)&&(Uu=!0)),a=i}kt!==0&&kt!==5||fi(t),kn!==0&&(kn=0)}function Xd(t,e){for(var n=t.suspendedLanes,a=t.pingedLanes,i=t.expirationTimes,s=t.pendingLanes&-62914561;0<s;){var d=31-xe(s),g=1<<d,T=i[d];T===-1?((g&n)===0||(g&a)!==0)&&(i[d]=pm(g,e)):T<=e&&(t.expiredLanes|=g),s&=~g}if(e=jt,n=Et,n=Li(t,t===e?n:0,t.cancelPendingCommit!==null||t.timeoutHandle!==-1),a=t.callbackNode,n===0||t===e&&(Ot===2||Ot===9)||t.cancelPendingCommit!==null)return a!==null&&a!==null&&Ac(a),t.callbackNode=null,t.callbackPriority=0;if((n&3)===0||Rl(t,n)){if(e=n&-n,e===t.callbackPriority)return e;switch(a!==null&&Ac(a),Ec(n)){case 2:case 8:n=Mr;break;case 32:n=Ui;break;case 268435456:n=jr;break;default:n=Ui}return a=Vd.bind(null,t),n=gc(n,a),t.callbackPriority=e,t.callbackNode=n,e}return a!==null&&a!==null&&Ac(a),t.callbackPriority=2,t.callbackNode=null,2}function Vd(t,e){if(kt!==0&&kt!==5)return t.callbackNode=null,t.callbackPriority=0,null;var n=t.callbackNode;if(Nu()&&t.callbackNode!==n)return null;var a=Et;return a=Li(t,t===jt?a:0,t.cancelPendingCommit!==null||t.timeoutHandle!==-1),a===0?null:(Sd(t,a,e),Xd(t,pe()),t.callbackNode!=null&&t.callbackNode===n?Vd.bind(null,t):null)}function Zd(t,e){if(Nu())return null;Sd(t,e,!0)}function Jg(){uA(function(){(Ct&6)!==0?gc(Dr,kg):Gd()})}function uf(){if(kn===0){var t=el;t===0&&(t=Qi,Qi<<=1,(Qi&261888)===0&&(Qi=256)),kn=t}return kn}function qd(t){return t==null||typeof t=="symbol"||typeof t=="boolean"?null:typeof t=="function"?t:Zi(""+t)}function Id(t,e){var n=e.ownerDocument.createElement("input");return n.name=e.name,n.value=e.value,t.id&&n.setAttribute("form",t.id),e.parentNode.insertBefore(n,e),t=new FormData(t),n.parentNode.removeChild(n),t}function Fg(t,e,n,a,i){if(e==="submit"&&n&&n.stateNode===i){var s=qd((i[oe]||null).action),d=a.submitter;d&&(e=(e=d[oe]||null)?qd(e.formAction):d.getAttribute("formAction"),e!==null&&(s=e,d=null));var g=new ki("action","action",null,a,i);t.push({event:g,listeners:[{instance:null,listener:function(){if(a.defaultPrevented){if(kn!==0){var T=d?Id(i,d):new FormData(i);Cs(n,{pending:!0,data:T,method:i.method,action:s},null,T)}}else typeof s=="function"&&(g.preventDefault(),T=d?Id(i,d):new FormData(i),Cs(n,{pending:!0,data:T,method:i.method,action:s},s,T))},currentTarget:i}]})}}for(var cf=0;cf<Vc.length;cf++){var sf=Vc[cf],Wg=sf.toLowerCase(),_g=sf[0].toUpperCase()+sf.slice(1);qe(Wg,"on"+_g)}qe(xo,"onAnimationEnd"),qe(So,"onAnimationIteration"),qe(To,"onAnimationStart"),qe("dblclick","onDoubleClick"),qe("focusin","onFocus"),qe("focusout","onBlur"),qe(hg,"onTransitionRun"),qe(mg,"onTransitionStart"),qe(gg,"onTransitionCancel"),qe(Co,"onTransitionEnd"),Xa("onMouseEnter",["mouseout","mouseover"]),Xa("onMouseLeave",["mouseout","mouseover"]),Xa("onPointerEnter",["pointerout","pointerover"]),Xa("onPointerLeave",["pointerout","pointerover"]),da("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),da("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),da("onBeforeInput",["compositionend","keypress","textInput","paste"]),da("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),da("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),da("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var ri="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Pg=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(ri));function Kd(t,e){e=(e&4)!==0;for(var n=0;n<t.length;n++){var a=t[n],i=a.event;a=a.listeners;t:{var s=void 0;if(e)for(var d=a.length-1;0<=d;d--){var g=a[d],T=g.instance,z=g.currentTarget;if(g=g.listener,T!==s&&i.isPropagationStopped())break t;s=g,i.currentTarget=z;try{s(i)}catch(V){Wi(V)}i.currentTarget=null,s=T}else for(d=0;d<a.length;d++){if(g=a[d],T=g.instance,z=g.currentTarget,g=g.listener,T!==s&&i.isPropagationStopped())break t;s=g,i.currentTarget=z;try{s(i)}catch(V){Wi(V)}i.currentTarget=null,s=T}}}}function yt(t,e){var n=e[pc];n===void 0&&(n=e[pc]=new Set);var a=t+"__bubble";n.has(a)||(kd(e,t,2,!1),n.add(a))}function ff(t,e,n){var a=0;e&&(a|=4),kd(n,t,a,e)}var Qu="_reactListening"+Math.random().toString(36).slice(2);function rf(t){if(!t[Qu]){t[Qu]=!0,Lr.forEach(function(n){n!=="selectionchange"&&(Pg.has(n)||ff(n,!1,t),ff(n,!0,t))});var e=t.nodeType===9?t:t.ownerDocument;e===null||e[Qu]||(e[Qu]=!0,ff("selectionchange",!1,e))}}function kd(t,e,n,a){switch(x1(e)){case 2:var i=OA;break;case 8:i=wA;break;default:i=Cf}n=i.bind(null,e,n,t),i=void 0,!Dc||e!=="touchstart"&&e!=="touchmove"&&e!=="wheel"||(i=!0),a?i!==void 0?t.addEventListener(e,n,{capture:!0,passive:i}):t.addEventListener(e,n,!0):i!==void 0?t.addEventListener(e,n,{passive:i}):t.addEventListener(e,n,!1)}function of(t,e,n,a,i){var s=a;if((e&1)===0&&(e&2)===0&&a!==null)t:for(;;){if(a===null)return;var d=a.tag;if(d===3||d===4){var g=a.stateNode.containerInfo;if(g===i)break;if(d===4)for(d=a.return;d!==null;){var T=d.tag;if((T===3||T===4)&&d.stateNode.containerInfo===i)return;d=d.return}for(;g!==null;){if(d=Ya(g),d===null)return;if(T=d.tag,T===5||T===6||T===26||T===27){a=s=d;continue t}g=g.parentNode}}a=a.return}_r(function(){var z=s,V=wc(n),I=[];t:{var Y=Oo.get(t);if(Y!==void 0){var G=ki,at=t;switch(t){case"keypress":if(Ii(n)===0)break t;case"keydown":case"keyup":G=Im;break;case"focusin":at="focus",G=Nc;break;case"focusout":at="blur",G=Nc;break;case"beforeblur":case"afterblur":G=Nc;break;case"click":if(n.button===2)break t;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":G=to;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":G=Nm;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":G=Jm;break;case xo:case So:case To:G=Qm;break;case Co:G=Wm;break;case"scroll":case"scrollend":G=jm;break;case"wheel":G=Pm;break;case"copy":case"cut":case"paste":G=Ym;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":G=no;break;case"toggle":case"beforetoggle":G=tg}var ft=(e&4)!==0,Mt=!ft&&(t==="scroll"||t==="scrollend"),M=ft?Y!==null?Y+"Capture":null:Y;ft=[];for(var w=z,Q;w!==null;){var q=w;if(Q=q.stateNode,q=q.tag,q!==5&&q!==26&&q!==27||Q===null||M===null||(q=Hl(w,M),q!=null&&ft.push(oi(w,q,Q))),Mt)break;w=w.return}0<ft.length&&(Y=new G(Y,at,null,n,V),I.push({event:Y,listeners:ft}))}}if((e&7)===0){t:{if(Y=t==="mouseover"||t==="pointerover",G=t==="mouseout"||t==="pointerout",Y&&n!==Oc&&(at=n.relatedTarget||n.fromElement)&&(Ya(at)||at[za]))break t;if((G||Y)&&(Y=V.window===V?V:(Y=V.ownerDocument)?Y.defaultView||Y.parentWindow:window,G?(at=n.relatedTarget||n.toElement,G=z,at=at?Ya(at):null,at!==null&&(Mt=o(at),ft=at.tag,at!==Mt||ft!==5&&ft!==27&&ft!==6)&&(at=null)):(G=null,at=z),G!==at)){if(ft=to,q="onMouseLeave",M="onMouseEnter",w="mouse",(t==="pointerout"||t==="pointerover")&&(ft=no,q="onPointerLeave",M="onPointerEnter",w="pointer"),Mt=G==null?Y:jl(G),Q=at==null?Y:jl(at),Y=new ft(q,w+"leave",G,n,V),Y.target=Mt,Y.relatedTarget=Q,q=null,Ya(V)===z&&(ft=new ft(M,w+"enter",at,n,V),ft.target=Q,ft.relatedTarget=Mt,q=ft),Mt=q,G&&at)e:{for(ft=$g,M=G,w=at,Q=0,q=M;q;q=ft(q))Q++;q=0;for(var ut=w;ut;ut=ft(ut))q++;for(;0<Q-q;)M=ft(M),Q--;for(;0<q-Q;)w=ft(w),q--;for(;Q--;){if(M===w||w!==null&&M===w.alternate){ft=M;break e}M=ft(M),w=ft(w)}ft=null}else ft=null;G!==null&&Jd(I,Y,G,ft,!1),at!==null&&Mt!==null&&Jd(I,Mt,at,ft,!0)}}t:{if(Y=z?jl(z):window,G=Y.nodeName&&Y.nodeName.toLowerCase(),G==="select"||G==="input"&&Y.type==="file")var St=ro;else if(so(Y))if(oo)St=rg;else{St=sg;var lt=cg}else G=Y.nodeName,!G||G.toLowerCase()!=="input"||Y.type!=="checkbox"&&Y.type!=="radio"?z&&Cc(z.elementType)&&(St=ro):St=fg;if(St&&(St=St(t,z))){fo(I,St,n,V);break t}lt&&lt(t,Y,z),t==="focusout"&&z&&Y.type==="number"&&z.memoizedProps.value!=null&&Tc(Y,"number",Y.value)}switch(lt=z?jl(z):window,t){case"focusin":(so(lt)||lt.contentEditable==="true")&&(ka=lt,Lc=z,Gl=null);break;case"focusout":Gl=Lc=ka=null;break;case"mousedown":Gc=!0;break;case"contextmenu":case"mouseup":case"dragend":Gc=!1,po(I,n,V);break;case"selectionchange":if(dg)break;case"keydown":case"keyup":po(I,n,V)}var At;if(Uc)t:{switch(t){case"compositionstart":var pt="onCompositionStart";break t;case"compositionend":pt="onCompositionEnd";break t;case"compositionupdate":pt="onCompositionUpdate";break t}pt=void 0}else Ka?uo(t,n)&&(pt="onCompositionEnd"):t==="keydown"&&n.keyCode===229&&(pt="onCompositionStart");pt&&(ao&&n.locale!=="ko"&&(Ka||pt!=="onCompositionStart"?pt==="onCompositionEnd"&&Ka&&(At=Pr()):(jn=V,Mc="value"in jn?jn.value:jn.textContent,Ka=!0)),lt=zu(z,pt),0<lt.length&&(pt=new eo(pt,t,null,n,V),I.push({event:pt,listeners:lt}),At?pt.data=At:(At=co(n),At!==null&&(pt.data=At)))),(At=ng?ag(t,n):lg(t,n))&&(pt=zu(z,"onBeforeInput"),0<pt.length&&(lt=new eo("onBeforeInput","beforeinput",null,n,V),I.push({event:lt,listeners:pt}),lt.data=At)),Fg(I,t,z,n,V)}Kd(I,e)})}function oi(t,e,n){return{instance:t,listener:e,currentTarget:n}}function zu(t,e){for(var n=e+"Capture",a=[];t!==null;){var i=t,s=i.stateNode;if(i=i.tag,i!==5&&i!==26&&i!==27||s===null||(i=Hl(t,n),i!=null&&a.unshift(oi(t,i,s)),i=Hl(t,e),i!=null&&a.push(oi(t,i,s))),t.tag===3)return a;t=t.return}return[]}function $g(t){if(t===null)return null;do t=t.return;while(t&&t.tag!==5&&t.tag!==27);return t||null}function Jd(t,e,n,a,i){for(var s=e._reactName,d=[];n!==null&&n!==a;){var g=n,T=g.alternate,z=g.stateNode;if(g=g.tag,T!==null&&T===a)break;g!==5&&g!==26&&g!==27||z===null||(T=z,i?(z=Hl(n,s),z!=null&&d.unshift(oi(n,z,T))):i||(z=Hl(n,s),z!=null&&d.push(oi(n,z,T)))),n=n.return}d.length!==0&&t.push({event:e,listeners:d})}var tA=/\r\n?/g,eA=/\u0000|\uFFFD/g;function Fd(t){return(typeof t=="string"?t:""+t).replace(tA,` 58 + `).replace(eA,"")}function Wd(t,e){return e=Fd(e),Fd(t)===e}function Dt(t,e,n,a,i,s){switch(n){case"children":typeof a=="string"?e==="body"||e==="textarea"&&a===""||Za(t,a):(typeof a=="number"||typeof a=="bigint")&&e!=="body"&&Za(t,""+a);break;case"className":Xi(t,"class",a);break;case"tabIndex":Xi(t,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":Xi(t,n,a);break;case"style":Fr(t,a,s);break;case"data":if(e!=="object"){Xi(t,"data",a);break}case"src":case"href":if(a===""&&(e!=="a"||n!=="href")){t.removeAttribute(n);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(n);break}a=Zi(""+a),t.setAttribute(n,a);break;case"action":case"formAction":if(typeof a=="function"){t.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof s=="function"&&(n==="formAction"?(e!=="input"&&Dt(t,e,"name",i.name,i,null),Dt(t,e,"formEncType",i.formEncType,i,null),Dt(t,e,"formMethod",i.formMethod,i,null),Dt(t,e,"formTarget",i.formTarget,i,null)):(Dt(t,e,"encType",i.encType,i,null),Dt(t,e,"method",i.method,i,null),Dt(t,e,"target",i.target,i,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(n);break}a=Zi(""+a),t.setAttribute(n,a);break;case"onClick":a!=null&&(t.onclick=tn);break;case"onScroll":a!=null&&yt("scroll",t);break;case"onScrollEnd":a!=null&&yt("scrollend",t);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(f(61));if(n=a.__html,n!=null){if(i.children!=null)throw Error(f(60));t.innerHTML=n}}break;case"multiple":t.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":t.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){t.removeAttribute("xlink:href");break}n=Zi(""+a),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,""+a):t.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,""):t.removeAttribute(n);break;case"capture":case"download":a===!0?t.setAttribute(n,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(n,a):t.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?t.setAttribute(n,a):t.removeAttribute(n);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?t.removeAttribute(n):t.setAttribute(n,a);break;case"popover":yt("beforetoggle",t),yt("toggle",t),Gi(t,"popover",a);break;case"xlinkActuate":$e(t,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":$e(t,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":$e(t,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":$e(t,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":$e(t,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":$e(t,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":$e(t,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":$e(t,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":$e(t,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":Gi(t,"is",a);break;case"innerText":case"textContent":break;default:(!(2<n.length)||n[0]!=="o"&&n[0]!=="O"||n[1]!=="n"&&n[1]!=="N")&&(n=Dm.get(n)||n,Gi(t,n,a))}}function df(t,e,n,a,i,s){switch(n){case"style":Fr(t,a,s);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(f(61));if(n=a.__html,n!=null){if(i.children!=null)throw Error(f(60));t.innerHTML=n}}break;case"children":typeof a=="string"?Za(t,a):(typeof a=="number"||typeof a=="bigint")&&Za(t,""+a);break;case"onScroll":a!=null&&yt("scroll",t);break;case"onScrollEnd":a!=null&&yt("scrollend",t);break;case"onClick":a!=null&&(t.onclick=tn);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":break;case"innerText":case"textContent":break;default:if(!Gr.hasOwnProperty(n))t:{if(n[0]==="o"&&n[1]==="n"&&(i=n.endsWith("Capture"),e=n.slice(2,i?n.length-7:void 0),s=t[oe]||null,s=s!=null?s[n]:null,typeof s=="function"&&t.removeEventListener(e,s,i),typeof a=="function")){typeof s!="function"&&s!==null&&(n in t?t[n]=null:t.hasAttribute(n)&&t.removeAttribute(n)),t.addEventListener(e,a,i);break t}n in t?t[n]=a:a===!0?t.setAttribute(n,""):Gi(t,n,a)}}}function le(t,e,n){switch(e){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":yt("error",t),yt("load",t);var a=!1,i=!1,s;for(s in n)if(n.hasOwnProperty(s)){var d=n[s];if(d!=null)switch(s){case"src":a=!0;break;case"srcSet":i=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(f(137,e));default:Dt(t,e,s,d,n,null)}}i&&Dt(t,e,"srcSet",n.srcSet,n,null),a&&Dt(t,e,"src",n.src,n,null);return;case"input":yt("invalid",t);var g=s=d=i=null,T=null,z=null;for(a in n)if(n.hasOwnProperty(a)){var V=n[a];if(V!=null)switch(a){case"name":i=V;break;case"type":d=V;break;case"checked":T=V;break;case"defaultChecked":z=V;break;case"value":s=V;break;case"defaultValue":g=V;break;case"children":case"dangerouslySetInnerHTML":if(V!=null)throw Error(f(137,e));break;default:Dt(t,e,a,V,n,null)}}Ir(t,s,g,T,z,d,i,!1);return;case"select":yt("invalid",t),a=d=s=null;for(i in n)if(n.hasOwnProperty(i)&&(g=n[i],g!=null))switch(i){case"value":s=g;break;case"defaultValue":d=g;break;case"multiple":a=g;default:Dt(t,e,i,g,n,null)}e=s,n=d,t.multiple=!!a,e!=null?Va(t,!!a,e,!1):n!=null&&Va(t,!!a,n,!0);return;case"textarea":yt("invalid",t),s=i=a=null;for(d in n)if(n.hasOwnProperty(d)&&(g=n[d],g!=null))switch(d){case"value":a=g;break;case"defaultValue":i=g;break;case"children":s=g;break;case"dangerouslySetInnerHTML":if(g!=null)throw Error(f(91));break;default:Dt(t,e,d,g,n,null)}kr(t,a,i,s);return;case"option":for(T in n)if(n.hasOwnProperty(T)&&(a=n[T],a!=null))switch(T){case"selected":t.selected=a&&typeof a!="function"&&typeof a!="symbol";break;default:Dt(t,e,T,a,n,null)}return;case"dialog":yt("beforetoggle",t),yt("toggle",t),yt("cancel",t),yt("close",t);break;case"iframe":case"object":yt("load",t);break;case"video":case"audio":for(a=0;a<ri.length;a++)yt(ri[a],t);break;case"image":yt("error",t),yt("load",t);break;case"details":yt("toggle",t);break;case"embed":case"source":case"link":yt("error",t),yt("load",t);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(z in n)if(n.hasOwnProperty(z)&&(a=n[z],a!=null))switch(z){case"children":case"dangerouslySetInnerHTML":throw Error(f(137,e));default:Dt(t,e,z,a,n,null)}return;default:if(Cc(e)){for(V in n)n.hasOwnProperty(V)&&(a=n[V],a!==void 0&&df(t,e,V,a,n,void 0));return}}for(g in n)n.hasOwnProperty(g)&&(a=n[g],a!=null&&Dt(t,e,g,a,n,null))}function nA(t,e,n,a){switch(e){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var i=null,s=null,d=null,g=null,T=null,z=null,V=null;for(G in n){var I=n[G];if(n.hasOwnProperty(G)&&I!=null)switch(G){case"checked":break;case"value":break;case"defaultValue":T=I;default:a.hasOwnProperty(G)||Dt(t,e,G,null,a,I)}}for(var Y in a){var G=a[Y];if(I=n[Y],a.hasOwnProperty(Y)&&(G!=null||I!=null))switch(Y){case"type":s=G;break;case"name":i=G;break;case"checked":z=G;break;case"defaultChecked":V=G;break;case"value":d=G;break;case"defaultValue":g=G;break;case"children":case"dangerouslySetInnerHTML":if(G!=null)throw Error(f(137,e));break;default:G!==I&&Dt(t,e,Y,G,a,I)}}Sc(t,d,g,T,z,V,s,i);return;case"select":G=d=g=Y=null;for(s in n)if(T=n[s],n.hasOwnProperty(s)&&T!=null)switch(s){case"value":break;case"multiple":G=T;default:a.hasOwnProperty(s)||Dt(t,e,s,null,a,T)}for(i in a)if(s=a[i],T=n[i],a.hasOwnProperty(i)&&(s!=null||T!=null))switch(i){case"value":Y=s;break;case"defaultValue":g=s;break;case"multiple":d=s;default:s!==T&&Dt(t,e,i,s,a,T)}e=g,n=d,a=G,Y!=null?Va(t,!!n,Y,!1):!!a!=!!n&&(e!=null?Va(t,!!n,e,!0):Va(t,!!n,n?[]:"",!1));return;case"textarea":G=Y=null;for(g in n)if(i=n[g],n.hasOwnProperty(g)&&i!=null&&!a.hasOwnProperty(g))switch(g){case"value":break;case"children":break;default:Dt(t,e,g,null,a,i)}for(d in a)if(i=a[d],s=n[d],a.hasOwnProperty(d)&&(i!=null||s!=null))switch(d){case"value":Y=i;break;case"defaultValue":G=i;break;case"children":break;case"dangerouslySetInnerHTML":if(i!=null)throw Error(f(91));break;default:i!==s&&Dt(t,e,d,i,a,s)}Kr(t,Y,G);return;case"option":for(var at in n)if(Y=n[at],n.hasOwnProperty(at)&&Y!=null&&!a.hasOwnProperty(at))switch(at){case"selected":t.selected=!1;break;default:Dt(t,e,at,null,a,Y)}for(T in a)if(Y=a[T],G=n[T],a.hasOwnProperty(T)&&Y!==G&&(Y!=null||G!=null))switch(T){case"selected":t.selected=Y&&typeof Y!="function"&&typeof Y!="symbol";break;default:Dt(t,e,T,Y,a,G)}return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var ft in n)Y=n[ft],n.hasOwnProperty(ft)&&Y!=null&&!a.hasOwnProperty(ft)&&Dt(t,e,ft,null,a,Y);for(z in a)if(Y=a[z],G=n[z],a.hasOwnProperty(z)&&Y!==G&&(Y!=null||G!=null))switch(z){case"children":case"dangerouslySetInnerHTML":if(Y!=null)throw Error(f(137,e));break;default:Dt(t,e,z,Y,a,G)}return;default:if(Cc(e)){for(var Mt in n)Y=n[Mt],n.hasOwnProperty(Mt)&&Y!==void 0&&!a.hasOwnProperty(Mt)&&df(t,e,Mt,void 0,a,Y);for(V in a)Y=a[V],G=n[V],!a.hasOwnProperty(V)||Y===G||Y===void 0&&G===void 0||df(t,e,V,Y,a,G);return}}for(var M in n)Y=n[M],n.hasOwnProperty(M)&&Y!=null&&!a.hasOwnProperty(M)&&Dt(t,e,M,null,a,Y);for(I in a)Y=a[I],G=n[I],!a.hasOwnProperty(I)||Y===G||Y==null&&G==null||Dt(t,e,I,Y,a,G)}function _d(t){switch(t){case"css":case"script":case"font":case"img":case"image":case"input":case"link":return!0;default:return!1}}function aA(){if(typeof performance.getEntriesByType=="function"){for(var t=0,e=0,n=performance.getEntriesByType("resource"),a=0;a<n.length;a++){var i=n[a],s=i.transferSize,d=i.initiatorType,g=i.duration;if(s&&g&&_d(d)){for(d=0,g=i.responseEnd,a+=1;a<n.length;a++){var T=n[a],z=T.startTime;if(z>g)break;var V=T.transferSize,I=T.initiatorType;V&&_d(I)&&(T=T.responseEnd,d+=V*(T<g?1:(g-z)/(T-z)))}if(--a,e+=8*(s+d)/(i.duration/1e3),t++,10<t)break}}if(0<t)return e/t/1e6}return navigator.connection&&(t=navigator.connection.downlink,typeof t=="number")?t:5}var hf=null,mf=null;function Yu(t){return t.nodeType===9?t:t.ownerDocument}function Pd(t){switch(t){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function $d(t,e){if(t===0)switch(e){case"svg":return 1;case"math":return 2;default:return 0}return t===1&&e==="foreignObject"?0:t}function gf(t,e){return t==="textarea"||t==="noscript"||typeof e.children=="string"||typeof e.children=="number"||typeof e.children=="bigint"||typeof e.dangerouslySetInnerHTML=="object"&&e.dangerouslySetInnerHTML!==null&&e.dangerouslySetInnerHTML.__html!=null}var Af=null;function lA(){var t=window.event;return t&&t.type==="popstate"?t===Af?!1:(Af=t,!0):(Af=null,!1)}var t1=typeof setTimeout=="function"?setTimeout:void 0,iA=typeof clearTimeout=="function"?clearTimeout:void 0,e1=typeof Promise=="function"?Promise:void 0,uA=typeof queueMicrotask=="function"?queueMicrotask:typeof e1<"u"?function(t){return e1.resolve(null).then(t).catch(cA)}:t1;function cA(t){setTimeout(function(){throw t})}function Jn(t){return t==="head"}function n1(t,e){var n=e,a=0;do{var i=n.nextSibling;if(t.removeChild(n),i&&i.nodeType===8)if(n=i.data,n==="/$"||n==="/&"){if(a===0){t.removeChild(i),pl(e);return}a--}else if(n==="$"||n==="$?"||n==="$~"||n==="$!"||n==="&")a++;else if(n==="html")di(t.ownerDocument.documentElement);else if(n==="head"){n=t.ownerDocument.head,di(n);for(var s=n.firstChild;s;){var d=s.nextSibling,g=s.nodeName;s[Ml]||g==="SCRIPT"||g==="STYLE"||g==="LINK"&&s.rel.toLowerCase()==="stylesheet"||n.removeChild(s),s=d}}else n==="body"&&di(t.ownerDocument.body);n=i}while(n);pl(e)}function a1(t,e){var n=t;t=0;do{var a=n.nextSibling;if(n.nodeType===1?e?(n._stashedDisplay=n.style.display,n.style.display="none"):(n.style.display=n._stashedDisplay||"",n.getAttribute("style")===""&&n.removeAttribute("style")):n.nodeType===3&&(e?(n._stashedText=n.nodeValue,n.nodeValue=""):n.nodeValue=n._stashedText||""),a&&a.nodeType===8)if(n=a.data,n==="/$"){if(t===0)break;t--}else n!=="$"&&n!=="$?"&&n!=="$~"&&n!=="$!"||t++;n=a}while(n)}function vf(t){var e=t.firstChild;for(e&&e.nodeType===10&&(e=e.nextSibling);e;){var n=e;switch(e=e.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":vf(n),bc(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if(n.rel.toLowerCase()==="stylesheet")continue}t.removeChild(n)}}function sA(t,e,n,a){for(;t.nodeType===1;){var i=n;if(t.nodeName.toLowerCase()!==e.toLowerCase()){if(!a&&(t.nodeName!=="INPUT"||t.type!=="hidden"))break}else if(a){if(!t[Ml])switch(e){case"meta":if(!t.hasAttribute("itemprop"))break;return t;case"link":if(s=t.getAttribute("rel"),s==="stylesheet"&&t.hasAttribute("data-precedence"))break;if(s!==i.rel||t.getAttribute("href")!==(i.href==null||i.href===""?null:i.href)||t.getAttribute("crossorigin")!==(i.crossOrigin==null?null:i.crossOrigin)||t.getAttribute("title")!==(i.title==null?null:i.title))break;return t;case"style":if(t.hasAttribute("data-precedence"))break;return t;case"script":if(s=t.getAttribute("src"),(s!==(i.src==null?null:i.src)||t.getAttribute("type")!==(i.type==null?null:i.type)||t.getAttribute("crossorigin")!==(i.crossOrigin==null?null:i.crossOrigin))&&s&&t.hasAttribute("async")&&!t.hasAttribute("itemprop"))break;return t;default:return t}}else if(e==="input"&&t.type==="hidden"){var s=i.name==null?null:""+i.name;if(i.type==="hidden"&&t.getAttribute("name")===s)return t}else return t;if(t=Le(t.nextSibling),t===null)break}return null}function fA(t,e,n){if(e==="")return null;for(;t.nodeType!==3;)if((t.nodeType!==1||t.nodeName!=="INPUT"||t.type!=="hidden")&&!n||(t=Le(t.nextSibling),t===null))return null;return t}function l1(t,e){for(;t.nodeType!==8;)if((t.nodeType!==1||t.nodeName!=="INPUT"||t.type!=="hidden")&&!e||(t=Le(t.nextSibling),t===null))return null;return t}function yf(t){return t.data==="$?"||t.data==="$~"}function Ef(t){return t.data==="$!"||t.data==="$?"&&t.ownerDocument.readyState!=="loading"}function rA(t,e){var n=t.ownerDocument;if(t.data==="$~")t._reactRetry=e;else if(t.data!=="$?"||n.readyState!=="loading")e();else{var a=function(){e(),n.removeEventListener("DOMContentLoaded",a)};n.addEventListener("DOMContentLoaded",a),t._reactRetry=a}}function Le(t){for(;t!=null;t=t.nextSibling){var e=t.nodeType;if(e===1||e===3)break;if(e===8){if(e=t.data,e==="$"||e==="$!"||e==="$?"||e==="$~"||e==="&"||e==="F!"||e==="F")break;if(e==="/$"||e==="/&")return null}}return t}var pf=null;function i1(t){t=t.nextSibling;for(var e=0;t;){if(t.nodeType===8){var n=t.data;if(n==="/$"||n==="/&"){if(e===0)return Le(t.nextSibling);e--}else n!=="$"&&n!=="$!"&&n!=="$?"&&n!=="$~"&&n!=="&"||e++}t=t.nextSibling}return null}function u1(t){t=t.previousSibling;for(var e=0;t;){if(t.nodeType===8){var n=t.data;if(n==="$"||n==="$!"||n==="$?"||n==="$~"||n==="&"){if(e===0)return t;e--}else n!=="/$"&&n!=="/&"||e++}t=t.previousSibling}return null}function c1(t,e,n){switch(e=Yu(n),t){case"html":if(t=e.documentElement,!t)throw Error(f(452));return t;case"head":if(t=e.head,!t)throw Error(f(453));return t;case"body":if(t=e.body,!t)throw Error(f(454));return t;default:throw Error(f(451))}}function di(t){for(var e=t.attributes;e.length;)t.removeAttributeNode(e[0]);bc(t)}var Ge=new Map,s1=new Set;function Lu(t){return typeof t.getRootNode=="function"?t.getRootNode():t.nodeType===9?t:t.ownerDocument}var vn=_.d;_.d={f:oA,r:dA,D:hA,C:mA,L:gA,m:AA,X:yA,S:vA,M:EA};function oA(){var t=vn.f(),e=Mu();return t||e}function dA(t){var e=La(t);e!==null&&e.tag===5&&e.type==="form"?C0(e):vn.r(t)}var vl=typeof document>"u"?null:document;function f1(t,e,n){var a=vl;if(a&&typeof e=="string"&&e){var i=He(e);i='link[rel="'+t+'"][href="'+i+'"]',typeof n=="string"&&(i+='[crossorigin="'+n+'"]'),s1.has(i)||(s1.add(i),t={rel:t,crossOrigin:n,href:e},a.querySelector(i)===null&&(e=a.createElement("link"),le(e,"link",t),Wt(e),a.head.appendChild(e)))}}function hA(t){vn.D(t),f1("dns-prefetch",t,null)}function mA(t,e){vn.C(t,e),f1("preconnect",t,e)}function gA(t,e,n){vn.L(t,e,n);var a=vl;if(a&&t&&e){var i='link[rel="preload"][as="'+He(e)+'"]';e==="image"&&n&&n.imageSrcSet?(i+='[imagesrcset="'+He(n.imageSrcSet)+'"]',typeof n.imageSizes=="string"&&(i+='[imagesizes="'+He(n.imageSizes)+'"]')):i+='[href="'+He(t)+'"]';var s=i;switch(e){case"style":s=yl(t);break;case"script":s=El(t)}Ge.has(s)||(t=S({rel:"preload",href:e==="image"&&n&&n.imageSrcSet?void 0:t,as:e},n),Ge.set(s,t),a.querySelector(i)!==null||e==="style"&&a.querySelector(hi(s))||e==="script"&&a.querySelector(mi(s))||(e=a.createElement("link"),le(e,"link",t),Wt(e),a.head.appendChild(e)))}}function AA(t,e){vn.m(t,e);var n=vl;if(n&&t){var a=e&&typeof e.as=="string"?e.as:"script",i='link[rel="modulepreload"][as="'+He(a)+'"][href="'+He(t)+'"]',s=i;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":s=El(t)}if(!Ge.has(s)&&(t=S({rel:"modulepreload",href:t},e),Ge.set(s,t),n.querySelector(i)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(n.querySelector(mi(s)))return}a=n.createElement("link"),le(a,"link",t),Wt(a),n.head.appendChild(a)}}}function vA(t,e,n){vn.S(t,e,n);var a=vl;if(a&&t){var i=Ga(a).hoistableStyles,s=yl(t);e=e||"default";var d=i.get(s);if(!d){var g={loading:0,preload:null};if(d=a.querySelector(hi(s)))g.loading=5;else{t=S({rel:"stylesheet",href:t,"data-precedence":e},n),(n=Ge.get(s))&&bf(t,n);var T=d=a.createElement("link");Wt(T),le(T,"link",t),T._p=new Promise(function(z,V){T.onload=z,T.onerror=V}),T.addEventListener("load",function(){g.loading|=1}),T.addEventListener("error",function(){g.loading|=2}),g.loading|=4,Gu(d,e,a)}d={type:"stylesheet",instance:d,count:1,state:g},i.set(s,d)}}}function yA(t,e){vn.X(t,e);var n=vl;if(n&&t){var a=Ga(n).hoistableScripts,i=El(t),s=a.get(i);s||(s=n.querySelector(mi(i)),s||(t=S({src:t,async:!0},e),(e=Ge.get(i))&&xf(t,e),s=n.createElement("script"),Wt(s),le(s,"link",t),n.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},a.set(i,s))}}function EA(t,e){vn.M(t,e);var n=vl;if(n&&t){var a=Ga(n).hoistableScripts,i=El(t),s=a.get(i);s||(s=n.querySelector(mi(i)),s||(t=S({src:t,async:!0,type:"module"},e),(e=Ge.get(i))&&xf(t,e),s=n.createElement("script"),Wt(s),le(s,"link",t),n.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},a.set(i,s))}}function r1(t,e,n,a){var i=(i=ot.current)?Lu(i):null;if(!i)throw Error(f(446));switch(t){case"meta":case"title":return null;case"style":return typeof n.precedence=="string"&&typeof n.href=="string"?(e=yl(n.href),n=Ga(i).hoistableStyles,a=n.get(e),a||(a={type:"style",instance:null,count:0,state:null},n.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(n.rel==="stylesheet"&&typeof n.href=="string"&&typeof n.precedence=="string"){t=yl(n.href);var s=Ga(i).hoistableStyles,d=s.get(t);if(d||(i=i.ownerDocument||i,d={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},s.set(t,d),(s=i.querySelector(hi(t)))&&!s._p&&(d.instance=s,d.state.loading=5),Ge.has(t)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Ge.set(t,n),s||pA(i,t,n,d.state))),e&&a===null)throw Error(f(528,""));return d}if(e&&a!==null)throw Error(f(529,""));return null;case"script":return e=n.async,n=n.src,typeof n=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=El(n),n=Ga(i).hoistableScripts,a=n.get(e),a||(a={type:"script",instance:null,count:0,state:null},n.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,t))}}function yl(t){return'href="'+He(t)+'"'}function hi(t){return'link[rel="stylesheet"]['+t+"]"}function o1(t){return S({},t,{"data-precedence":t.precedence,precedence:null})}function pA(t,e,n,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),le(e,"link",n),Wt(e),t.head.appendChild(e))}function El(t){return'[src="'+He(t)+'"]'}function mi(t){return"script[async]"+t}function d1(t,e,n){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+He(n.href)+'"]');if(a)return e.instance=a,Wt(a),a;var i=S({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),Wt(a),le(a,"style",i),Gu(a,n.precedence,t),e.instance=a;case"stylesheet":i=yl(n.href);var s=t.querySelector(hi(i));if(s)return e.state.loading|=4,e.instance=s,Wt(s),s;a=o1(n),(i=Ge.get(i))&&bf(a,i),s=(t.ownerDocument||t).createElement("link"),Wt(s);var d=s;return d._p=new Promise(function(g,T){d.onload=g,d.onerror=T}),le(s,"link",a),e.state.loading|=4,Gu(s,n.precedence,t),e.instance=s;case"script":return s=El(n.src),(i=t.querySelector(mi(s)))?(e.instance=i,Wt(i),i):(a=n,(i=Ge.get(s))&&(a=S({},n),xf(a,i)),t=t.ownerDocument||t,i=t.createElement("script"),Wt(i),le(i,"link",a),t.head.appendChild(i),e.instance=i);case"void":return null;default:throw Error(f(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,Gu(a,n.precedence,t));return e.instance}function Gu(t,e,n){for(var a=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),i=a.length?a[a.length-1]:null,s=i,d=0;d<a.length;d++){var g=a[d];if(g.dataset.precedence===e)s=g;else if(s!==i)break}s?s.parentNode.insertBefore(t,s.nextSibling):(e=n.nodeType===9?n.head:n,e.insertBefore(t,e.firstChild))}function bf(t,e){t.crossOrigin==null&&(t.crossOrigin=e.crossOrigin),t.referrerPolicy==null&&(t.referrerPolicy=e.referrerPolicy),t.title==null&&(t.title=e.title)}function xf(t,e){t.crossOrigin==null&&(t.crossOrigin=e.crossOrigin),t.referrerPolicy==null&&(t.referrerPolicy=e.referrerPolicy),t.integrity==null&&(t.integrity=e.integrity)}var Xu=null;function h1(t,e,n){if(Xu===null){var a=new Map,i=Xu=new Map;i.set(n,a)}else i=Xu,a=i.get(n),a||(a=new Map,i.set(n,a));if(a.has(t))return a;for(a.set(t,null),n=n.getElementsByTagName(t),i=0;i<n.length;i++){var s=n[i];if(!(s[Ml]||s[te]||t==="link"&&s.getAttribute("rel")==="stylesheet")&&s.namespaceURI!=="http://www.w3.org/2000/svg"){var d=s.getAttribute(e)||"";d=t+d;var g=a.get(d);g?g.push(s):a.set(d,[s])}}return a}function m1(t,e,n){t=t.ownerDocument||t,t.head.insertBefore(n,e==="title"?t.querySelector("head > title"):null)}function bA(t,e,n){if(n===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;switch(e.rel){case"stylesheet":return t=e.disabled,typeof e.precedence=="string"&&t==null;default:return!0}case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function g1(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function xA(t,e,n,a){if(n.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(n.state.loading&4)===0){if(n.instance===null){var i=yl(a.href),s=e.querySelector(hi(i));if(s){e=s._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=Vu.bind(t),e.then(t,t)),n.state.loading|=4,n.instance=s,Wt(s);return}s=e.ownerDocument||e,a=o1(a),(i=Ge.get(i))&&bf(a,i),s=s.createElement("link"),Wt(s);var d=s;d._p=new Promise(function(g,T){d.onload=g,d.onerror=T}),le(s,"link",a),n.instance=s}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(n,e),(e=n.state.preload)&&(n.state.loading&3)===0&&(t.count++,n=Vu.bind(t),e.addEventListener("load",n),e.addEventListener("error",n))}}var Sf=0;function SA(t,e){return t.stylesheets&&t.count===0&&qu(t,t.stylesheets),0<t.count||0<t.imgCount?function(n){var a=setTimeout(function(){if(t.stylesheets&&qu(t,t.stylesheets),t.unsuspend){var s=t.unsuspend;t.unsuspend=null,s()}},6e4+e);0<t.imgBytes&&Sf===0&&(Sf=62500*aA());var i=setTimeout(function(){if(t.waitingForImages=!1,t.count===0&&(t.stylesheets&&qu(t,t.stylesheets),t.unsuspend)){var s=t.unsuspend;t.unsuspend=null,s()}},(t.imgBytes>Sf?50:800)+e);return t.unsuspend=n,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(i)}}:null}function Vu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)qu(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var Zu=null;function qu(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Zu=new Map,e.forEach(TA,t),Zu=null,Vu.call(t))}function TA(t,e){if(!(e.state.loading&4)){var n=Zu.get(t);if(n)var a=n.get(null);else{n=new Map,Zu.set(t,n);for(var i=t.querySelectorAll("link[data-precedence],style[data-precedence]"),s=0;s<i.length;s++){var d=i[s];(d.nodeName==="LINK"||d.getAttribute("media")!=="not all")&&(n.set(d.dataset.precedence,d),a=d)}a&&n.set(null,a)}i=e.instance,d=i.getAttribute("data-precedence"),s=n.get(d)||a,s===a&&n.set(null,i),n.set(d,i),this.count++,a=Vu.bind(this),i.addEventListener("load",a),i.addEventListener("error",a),s?s.parentNode.insertBefore(i,s.nextSibling):(t=t.nodeType===9?t.head:t,t.insertBefore(i,t.firstChild)),e.state.loading|=4}}var gi={$$typeof:U,Provider:null,Consumer:null,_currentValue:$,_currentValue2:$,_threadCount:0};function CA(t,e,n,a,i,s,d,g,T){this.tag=1,this.containerInfo=t,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=vc(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=vc(0),this.hiddenUpdates=vc(null),this.identifierPrefix=a,this.onUncaughtError=i,this.onCaughtError=s,this.onRecoverableError=d,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=T,this.incompleteTransitions=new Map}function A1(t,e,n,a,i,s,d,g,T,z,V,I){return t=new CA(t,e,n,d,T,z,V,I,g),e=1,s===!0&&(e|=24),s=Te(3,null,null,e),t.current=s,s.stateNode=t,e=es(),e.refCount++,t.pooledCache=e,e.refCount++,s.memoizedState={element:a,isDehydrated:n,cache:e},is(s),t}function v1(t){return t?(t=Wa,t):Wa}function y1(t,e,n,a,i,s){i=v1(i),a.context===null?a.context=i:a.pendingContext=i,a=zn(e),a.payload={element:n},s=s===void 0?null:s,s!==null&&(a.callback=s),n=Yn(t,a,e),n!==null&&(ve(n,t,e),kl(n,t,e))}function E1(t,e){if(t=t.memoizedState,t!==null&&t.dehydrated!==null){var n=t.retryLane;t.retryLane=n!==0&&n<e?n:e}}function Tf(t,e){E1(t,e),(t=t.alternate)&&E1(t,e)}function p1(t){if(t.tag===13||t.tag===31){var e=Aa(t,67108864);e!==null&&ve(e,t,67108864),Tf(t,67108864)}}function b1(t){if(t.tag===13||t.tag===31){var e=De();e=yc(e);var n=Aa(t,e);n!==null&&ve(n,t,e),Tf(t,e)}}var Iu=!0;function OA(t,e,n,a){var i=H.T;H.T=null;var s=_.p;try{_.p=2,Cf(t,e,n,a)}finally{_.p=s,H.T=i}}function wA(t,e,n,a){var i=H.T;H.T=null;var s=_.p;try{_.p=8,Cf(t,e,n,a)}finally{_.p=s,H.T=i}}function Cf(t,e,n,a){if(Iu){var i=Of(a);if(i===null)of(t,e,a,Ku,n),S1(t,a);else if(DA(i,t,e,n,a))a.stopPropagation();else if(S1(t,a),e&4&&-1<RA.indexOf(t)){for(;i!==null;){var s=La(i);if(s!==null)switch(s.tag){case 3:if(s=s.stateNode,s.current.memoizedState.isDehydrated){var d=oa(s.pendingLanes);if(d!==0){var g=s;for(g.pendingLanes|=2,g.entangledLanes|=2;d;){var T=1<<31-xe(d);g.entanglements[1]|=T,d&=~T}_e(s),(Ct&6)===0&&(Ru=pe()+500,fi(0))}}break;case 31:case 13:g=Aa(s,2),g!==null&&ve(g,s,2),Mu(),Tf(s,2)}if(s=Of(a),s===null&&of(t,e,a,Ku,n),s===i)break;i=s}i!==null&&a.stopPropagation()}else of(t,e,a,null,n)}}function Of(t){return t=wc(t),wf(t)}var Ku=null;function wf(t){if(Ku=null,t=Ya(t),t!==null){var e=o(t);if(e===null)t=null;else{var n=e.tag;if(n===13){if(t=h(e),t!==null)return t;t=null}else if(n===31){if(t=v(e),t!==null)return t;t=null}else if(n===3){if(e.stateNode.current.memoizedState.isDehydrated)return e.tag===3?e.stateNode.containerInfo:null;t=null}else e!==t&&(t=null)}}return Ku=t,null}function x1(t){switch(t){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(hm()){case Dr:return 2;case Mr:return 8;case Ui:case mm:return 32;case jr:return 268435456;default:return 32}default:return 32}}var Rf=!1,Fn=null,Wn=null,_n=null,Ai=new Map,vi=new Map,Pn=[],RA="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function S1(t,e){switch(t){case"focusin":case"focusout":Fn=null;break;case"dragenter":case"dragleave":Wn=null;break;case"mouseover":case"mouseout":_n=null;break;case"pointerover":case"pointerout":Ai.delete(e.pointerId);break;case"gotpointercapture":case"lostpointercapture":vi.delete(e.pointerId)}}function yi(t,e,n,a,i,s){return t===null||t.nativeEvent!==s?(t={blockedOn:e,domEventName:n,eventSystemFlags:a,nativeEvent:s,targetContainers:[i]},e!==null&&(e=La(e),e!==null&&p1(e)),t):(t.eventSystemFlags|=a,e=t.targetContainers,i!==null&&e.indexOf(i)===-1&&e.push(i),t)}function DA(t,e,n,a,i){switch(e){case"focusin":return Fn=yi(Fn,t,e,n,a,i),!0;case"dragenter":return Wn=yi(Wn,t,e,n,a,i),!0;case"mouseover":return _n=yi(_n,t,e,n,a,i),!0;case"pointerover":var s=i.pointerId;return Ai.set(s,yi(Ai.get(s)||null,t,e,n,a,i)),!0;case"gotpointercapture":return s=i.pointerId,vi.set(s,yi(vi.get(s)||null,t,e,n,a,i)),!0}return!1}function T1(t){var e=Ya(t.target);if(e!==null){var n=o(e);if(n!==null){if(e=n.tag,e===13){if(e=h(n),e!==null){t.blockedOn=e,zr(t.priority,function(){b1(n)});return}}else if(e===31){if(e=v(n),e!==null){t.blockedOn=e,zr(t.priority,function(){b1(n)});return}}else if(e===3&&n.stateNode.current.memoizedState.isDehydrated){t.blockedOn=n.tag===3?n.stateNode.containerInfo:null;return}}}t.blockedOn=null}function ku(t){if(t.blockedOn!==null)return!1;for(var e=t.targetContainers;0<e.length;){var n=Of(t.nativeEvent);if(n===null){n=t.nativeEvent;var a=new n.constructor(n.type,n);Oc=a,n.target.dispatchEvent(a),Oc=null}else return e=La(n),e!==null&&p1(e),t.blockedOn=n,!1;e.shift()}return!0}function C1(t,e,n){ku(t)&&n.delete(e)}function MA(){Rf=!1,Fn!==null&&ku(Fn)&&(Fn=null),Wn!==null&&ku(Wn)&&(Wn=null),_n!==null&&ku(_n)&&(_n=null),Ai.forEach(C1),vi.forEach(C1)}function Ju(t,e){t.blockedOn===e&&(t.blockedOn=null,Rf||(Rf=!0,l.unstable_scheduleCallback(l.unstable_NormalPriority,MA)))}var Fu=null;function O1(t){Fu!==t&&(Fu=t,l.unstable_scheduleCallback(l.unstable_NormalPriority,function(){Fu===t&&(Fu=null);for(var e=0;e<t.length;e+=3){var n=t[e],a=t[e+1],i=t[e+2];if(typeof a!="function"){if(wf(a||n)===null)continue;break}var s=La(n);s!==null&&(t.splice(e,3),e-=3,Cs(s,{pending:!0,data:i,method:n.method,action:a},a,i))}}))}function pl(t){function e(T){return Ju(T,t)}Fn!==null&&Ju(Fn,t),Wn!==null&&Ju(Wn,t),_n!==null&&Ju(_n,t),Ai.forEach(e),vi.forEach(e);for(var n=0;n<Pn.length;n++){var a=Pn[n];a.blockedOn===t&&(a.blockedOn=null)}for(;0<Pn.length&&(n=Pn[0],n.blockedOn===null);)T1(n),n.blockedOn===null&&Pn.shift();if(n=(t.ownerDocument||t).$$reactFormReplay,n!=null)for(a=0;a<n.length;a+=3){var i=n[a],s=n[a+1],d=i[oe]||null;if(typeof s=="function")d||O1(n);else if(d){var g=null;if(s&&s.hasAttribute("formAction")){if(i=s,d=s[oe]||null)g=d.formAction;else if(wf(i)!==null)continue}else g=d.action;typeof g=="function"?n[a+1]=g:(n.splice(a,3),a-=3),O1(n)}}}function w1(){function t(s){s.canIntercept&&s.info==="react-transition"&&s.intercept({handler:function(){return new Promise(function(d){return i=d})},focusReset:"manual",scroll:"manual"})}function e(){i!==null&&(i(),i=null),a||setTimeout(n,20)}function n(){if(!a&&!navigation.transition){var s=navigation.currentEntry;s&&s.url!=null&&navigation.navigate(s.url,{state:s.getState(),info:"react-transition",history:"replace"})}}if(typeof navigation=="object"){var a=!1,i=null;return navigation.addEventListener("navigate",t),navigation.addEventListener("navigatesuccess",e),navigation.addEventListener("navigateerror",e),setTimeout(n,100),function(){a=!0,navigation.removeEventListener("navigate",t),navigation.removeEventListener("navigatesuccess",e),navigation.removeEventListener("navigateerror",e),i!==null&&(i(),i=null)}}}function Df(t){this._internalRoot=t}Wu.prototype.render=Df.prototype.render=function(t){var e=this._internalRoot;if(e===null)throw Error(f(409));var n=e.current,a=De();y1(n,a,t,e,null,null)},Wu.prototype.unmount=Df.prototype.unmount=function(){var t=this._internalRoot;if(t!==null){this._internalRoot=null;var e=t.containerInfo;y1(t.current,2,null,t,null,null),Mu(),e[za]=null}};function Wu(t){this._internalRoot=t}Wu.prototype.unstable_scheduleHydration=function(t){if(t){var e=Qr();t={blockedOn:null,target:t,priority:e};for(var n=0;n<Pn.length&&e!==0&&e<Pn[n].priority;n++);Pn.splice(n,0,t),n===0&&T1(t)}};var R1=u.version;if(R1!=="19.2.1")throw Error(f(527,R1,"19.2.1"));_.findDOMNode=function(t){var e=t._reactInternals;if(e===void 0)throw typeof t.render=="function"?Error(f(188)):(t=Object.keys(t).join(","),Error(f(268,t)));return t=A(e),t=t!==null?E(t):null,t=t===null?null:t.stateNode,t};var jA={bundleType:0,version:"19.2.1",rendererPackageName:"react-dom",currentDispatcherRef:H,reconcilerVersion:"19.2.1"};if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"){var _u=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!_u.isDisabled&&_u.supportsFiber)try{wl=_u.inject(jA),be=_u}catch{}}return xi.createRoot=function(t,e){if(!r(t))throw Error(f(299));var n=!1,a="",i=U0,s=Q0,d=z0;return e!=null&&(e.unstable_strictMode===!0&&(n=!0),e.identifierPrefix!==void 0&&(a=e.identifierPrefix),e.onUncaughtError!==void 0&&(i=e.onUncaughtError),e.onCaughtError!==void 0&&(s=e.onCaughtError),e.onRecoverableError!==void 0&&(d=e.onRecoverableError)),e=A1(t,1,!1,null,null,n,a,null,i,s,d,w1),t[za]=e.current,rf(t),new Df(e)},xi.hydrateRoot=function(t,e,n){if(!r(t))throw Error(f(299));var a=!1,i="",s=U0,d=Q0,g=z0,T=null;return n!=null&&(n.unstable_strictMode===!0&&(a=!0),n.identifierPrefix!==void 0&&(i=n.identifierPrefix),n.onUncaughtError!==void 0&&(s=n.onUncaughtError),n.onCaughtError!==void 0&&(d=n.onCaughtError),n.onRecoverableError!==void 0&&(g=n.onRecoverableError),n.formState!==void 0&&(T=n.formState)),e=A1(t,1,!0,e,n??null,a,i,T,s,d,g,w1),e.context=v1(null),n=e.current,a=De(),a=yc(a),i=zn(a),i.callback=null,Yn(n,i,a),n=a,e.current.lanes=n,Dl(e,n),_e(e),t[za]=e.current,rf(t),new Wu(e)},xi.version="19.2.1",xi}var j2;function G5(){if(j2)return Vf.exports;j2=1;function l(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(l)}catch(u){console.error(u)}}return l(),Vf.exports=L5(),Vf.exports}var X5=G5();class rc{constructor(){yn(this,"project",[]);yn(this,"status",[]);yn(this,"text",[]);yn(this,"labels",[]);yn(this,"annotations",[])}empty(){return this.project.length+this.status.length+this.text.length+this.labels.length+this.annotations.length===0}static parse(u){const c=rc.tokenize(u),f=new Set,r=new Set,o=[],h=new Set,v=new Set;for(let A of c){const E=A.startsWith("!");if(E&&(A=A.slice(1)),A.startsWith("p:")){f.add({name:A.slice(2),not:E});continue}if(A.startsWith("s:")){r.add({name:A.slice(2),not:E});continue}if(A.startsWith("@")){h.add({name:A,not:E});continue}if(A.startsWith("annot:")){v.add({name:A.slice(6),not:E});continue}o.push({name:A.toLowerCase(),not:E})}const y=new rc;return y.text=o,y.project=[...f],y.status=[...r],y.labels=[...h],y.annotations=[...v],y}static tokenize(u){const c=[];let f,r=[];for(let o=0;o<u.length;++o){const h=u[o];if(f&&h==="\\"&&u[o+1]===f){r.push(f),++o;continue}if(h==='"'||h==="'"){f===h?(c.push(r.join("").toLowerCase()),r=[],f=void 0):f?r.push(h):f=h;continue}if(f){r.push(h);continue}if(h===" "){r.length&&(c.push(r.join("").toLowerCase()),r=[]);continue}r.push(h)}return r.length&&c.push(r.join("").toLowerCase()),c}matches(u){const c=V5(u);if(this.project.length&&!!!this.project.find(r=>{const o=c.project.includes(r.name);return r.not?!o:o}))return!1;if(this.status.length){if(!!!this.status.find(r=>{const o=c.status.includes(r.name);return r.not?!o:o}))return!1}else if(c.status==="skipped")return!1;return!(this.text.length&&!this.text.every(r=>{if(c.text.includes(r.name))return!r.not;const[o,h,v]=r.name.split(":");return c.file.includes(o)&&c.line===h&&(v===void 0||c.column===v)?!r.not:!!r.not})||this.labels.length&&!this.labels.every(r=>{const o=c.labels.includes(r.name);return r.not?!o:o})||this.annotations.length&&!this.annotations.every(r=>{const o=c.annotations.some(h=>h.includes(r.name));return r.not?!o:o}))}}const H2=Symbol("searchValues");function V5(l){const u=l[H2];if(u)return u;let c="passed";l.outcome==="unexpected"&&(c="failed"),l.outcome==="flaky"&&(c="flaky"),l.outcome==="skipped"&&(c="skipped");const f={text:(c+" "+l.projectName+" "+l.tags.join(" ")+" "+l.location.file+" "+l.path.join(" ")+" "+l.title).toLowerCase(),project:l.projectName.toLowerCase(),status:c,file:l.location.file,line:String(l.location.line),column:String(l.location.column),labels:l.tags.map(r=>r.toLowerCase()),annotations:l.annotations.map(r=>{var o;return r.type.toLowerCase()+"="+((o=r.description)==null?void 0:o.toLocaleLowerCase())})};return l[H2]=f,f}const Z5=/("[^"]*"|"[^"]*$|\S+)/g;function Na(l,u,c){const f=new URLSearchParams(l),o=[...(l.get("q")??"").matchAll(Z5)].map(y=>{const A=y[0];return A.startsWith('"')&&A.endsWith('"')&&A.length>1?A.slice(1,A.length-1):A});if(c)return f.set("q",N2(o.includes(u)?o.filter(y=>y!==u):[...o,u])),"#?"+f;let h;u.startsWith("s:")&&(h="s:"),u.startsWith("p:")&&(h="p:"),u.startsWith("@")&&(h="@");const v=o.filter(y=>!y.startsWith(h));return v.push(u),f.set("q",N2(v)),"#?"+f}function N2(l){return l.map(u=>/\s/.test(u)?`"${u}"`:u).join(" ").trim()}const q5=()=>m.jsx("span",{className:"octicon",style:{width:16,height:16}}),I5=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon subnav-search-icon",children:m.jsx("path",{fillRule:"evenodd",d:"M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"})}),Ni=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16",className:"octicon color-fg-muted",children:m.jsx("path",{fillRule:"evenodd",d:"M12.78 6.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.22 7.28a.75.75 0 011.06-1.06L8 9.94l3.72-3.72a.75.75 0 011.06 0z"})}),Cl=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon color-fg-muted",children:m.jsx("path",{fillRule:"evenodd",d:"M6.22 3.22a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 010-1.06z"})}),qh=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon color-text-warning",children:m.jsx("path",{fillRule:"evenodd",d:"M8.22 1.754a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368L8.22 1.754zm-1.763-.707c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575L6.457 1.047zM9 11a1 1 0 11-2 0 1 1 0 012 0zm-.25-5.25a.75.75 0 00-1.5 0v2.5a.75.75 0 001.5 0v-2.5z"})}),Ih=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon color-fg-muted",children:m.jsx("path",{fillRule:"evenodd",d:"M3.5 1.75a.25.25 0 01.25-.25h3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h2.086a.25.25 0 01.177.073l2.914 2.914a.25.25 0 01.073.177v8.586a.25.25 0 01-.25.25h-.5a.75.75 0 000 1.5h.5A1.75 1.75 0 0014 13.25V4.664c0-.464-.184-.909-.513-1.237L10.573.513A1.75 1.75 0 009.336 0H3.75A1.75 1.75 0 002 1.75v11.5c0 .649.353 1.214.874 1.515a.75.75 0 10.752-1.298.25.25 0 01-.126-.217V1.75zM8.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM6 5.25a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5A.75.75 0 016 5.25zm2 1.5A.75.75 0 018.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 018 6.75zm-1.25.75a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM8 9.75A.75.75 0 018.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 018 9.75zm-.75.75a1.75 1.75 0 00-1.75 1.75v3c0 .414.336.75.75.75h2.5a.75.75 0 00.75-.75v-3a1.75 1.75 0 00-1.75-1.75h-.5zM7 12.25a.25.25 0 01.25-.25h.5a.25.25 0 01.25.25v2.25H7v-2.25z"})}),Kh=()=>m.jsx("svg",{className:"octicon color-text-danger",viewBox:"0 0 16 16",version:"1.1",width:"16",height:"16","aria-hidden":"true",children:m.jsx("path",{fillRule:"evenodd",d:"M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"})}),kh=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon color-icon-success",children:m.jsx("path",{fillRule:"evenodd",d:"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"})}),Jh=()=>m.jsx("svg",{"aria-hidden":"true",height:"16",viewBox:"0 0 16 16",version:"1.1",width:"16","data-view-component":"true",className:"octicon octicon-clock color-text-danger",children:m.jsx("path",{fillRule:"evenodd",d:"M5.75.75A.75.75 0 016.5 0h3a.75.75 0 010 1.5h-.75v1l-.001.041a6.718 6.718 0 013.464 1.435l.007-.006.75-.75a.75.75 0 111.06 1.06l-.75.75-.006.007a6.75 6.75 0 11-10.548 0L2.72 5.03l-.75-.75a.75.75 0 011.06-1.06l.75.75.007.006A6.718 6.718 0 017.25 2.541a.756.756 0 010-.041v-1H6.5a.75.75 0 01-.75-.75zM8 14.5A5.25 5.25 0 108 4a5.25 5.25 0 000 10.5zm.389-6.7l1.33-1.33a.75.75 0 111.061 1.06L9.45 8.861A1.502 1.502 0 018 10.75a1.5 1.5 0 11.389-2.95z"})}),K5=()=>m.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 16 16",width:"16",height:"16","data-view-component":"true",className:"octicon color-fg-muted",children:m.jsx("path",{d:"M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm9.78-2.22-5.5 5.5a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l5.5-5.5a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"})}),k5=()=>m.jsx("svg",{className:"octicon",viewBox:"0 0 48 48",version:"1.1",width:"20",height:"20","aria-hidden":"true",children:m.jsx("path",{xmlns:"http://www.w3.org/2000/svg",d:"M11.85 32H36.2l-7.35-9.95-6.55 8.7-4.6-6.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-29v26-26Zm34 26V11H7v26Z"})}),J5=()=>m.jsx("svg",{className:"octicon",viewBox:"0 0 48 48",version:"1.1",width:"20",height:"20","aria-hidden":"true",children:m.jsx("path",{xmlns:"http://www.w3.org/2000/svg",d:"m19.6 32.35 13-8.45-13-8.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h34V11H7v26Zm0 0V11v26Z"})}),F5=()=>m.jsx("svg",{className:"octicon",viewBox:"0 0 48 48",version:"1.1",width:"20",height:"20","aria-hidden":"true",children:m.jsx("path",{xmlns:"http://www.w3.org/2000/svg",d:"M7 37h9.35V11H7v26Zm12.35 0h9.3V11h-9.3v26Zm12.3 0H41V11h-9.35v26ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Z"})}),W5=()=>m.jsxs("svg",{className:"octicon",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:[m.jsx("path",{d:"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"}),m.jsx("path",{d:"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"})]}),_5=()=>m.jsx("svg",{className:"octicon octicon-settings",viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:m.jsx("path",{d:"M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"})}),Fh=({value:l})=>{const[u,c]=ct.useState("copy"),f=ct.useCallback(()=>{navigator.clipboard.writeText(l).then(()=>{c("check"),setTimeout(()=>{c("copy")},3e3)},()=>{c("cross")})},[l]),r=u==="check"?kh():u==="cross"?Kh():W5();return m.jsx("button",{className:"copy-icon",title:"Copy to clipboard","aria-label":"Copy to clipboard",onClick:f,children:r})},Sr=({children:l,value:u})=>m.jsxs("span",{className:"copy-value-container",children:[l,m.jsx("span",{className:"copy-button-container",children:m.jsx(Fh,{value:u})})]});function P5(l,u,c,f){const[r,o]=ue.useState(c);return ue.useEffect(()=>{let h=!1;return l().then(v=>{h||o(v)}),()=>{h=!0}},u),r}function Wh(){const l=ue.useRef(null),[u]=ur(l);return[u,l]}function ur(l){const[u,c]=ue.useState(new DOMRect(0,0,10,10)),f=ue.useCallback(()=>{const r=l==null?void 0:l.current;r&&c(r.getBoundingClientRect())},[l]);return ue.useLayoutEffect(()=>{const r=l==null?void 0:l.current;if(!r)return;f();const o=new ResizeObserver(f);return o.observe(r),window.addEventListener("resize",f),()=>{o.disconnect(),window.removeEventListener("resize",f)}},[f,l]),[u,f]}function _h(l,u){u=Ma.getObject(l,u);const[c,f]=ue.useState(u),r=ue.useCallback(o=>{Ma.setObject(l,o)},[l,f]);return ue.useEffect(()=>{{const o=()=>f(Ma.getObject(l,u));return Ma.onChangeEmitter.addEventListener(l,o),()=>Ma.onChangeEmitter.removeEventListener(l,o)}},[u,l]),[c,r]}class $5{constructor(){this.onChangeEmitter=new EventTarget}getString(u,c){return localStorage[u]||c}setString(u,c){var f;localStorage[u]=c,this.onChangeEmitter.dispatchEvent(new Event(u)),(f=window.saveSettings)==null||f.call(window)}getObject(u,c){if(!localStorage[u])return c;try{return JSON.parse(localStorage[u])}catch{return c}}setObject(u,c){var f;localStorage[u]=JSON.stringify(c),this.onChangeEmitter.dispatchEvent(new Event(u)),(f=window.saveSettings)==null||f.call(window)}}const Ma=new $5;function Ze(...l){return l.filter(Boolean).join(" ")}const B2="\\u0000-\\u0020\\u007f-\\u009f",tv=new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|www\\.)[^\\s"+B2+'"]{2,}[^\\s'+B2+`"')}\\],:;.!?]`,"ug");function ev(){const[l,u]=ue.useState(!1),c=ue.useCallback(()=>{const f=[];return u(r=>(f.push(setTimeout(()=>u(!1),1e3)),r?(f.push(setTimeout(()=>u(!0),50)),!1):!0)),()=>f.forEach(clearTimeout)},[u]);return[l,c]}function Di(l){const u=[];let c=0,f;for(;(f=tv.exec(l))!==null;){const o=l.substring(c,f.index);o&&u.push(o);const h=f[0];u.push(nv(h)),c=f.index+h.length}const r=l.substring(c);return r&&u.push(r),u}function nv(l){let u=l;return u.startsWith("www.")&&(u="https://"+u),m.jsx("a",{href:u,target:"_blank",rel:"noopener noreferrer",children:l})}const av=({summary:l,children:u,className:c,style:f})=>{const[r,o]=ue.useState(!1),h=v=>{o(v.currentTarget.open)};return m.jsxs("details",{style:f,className:c,onToggle:h,children:[m.jsxs("summary",{className:"expandable-summary",children:[r?Ni():Cl(),l]}),u]})};function Ol(l){if(!isFinite(l))return"-";if(l===0)return"0ms";if(l<1e3)return l.toFixed(0)+"ms";const u=l/1e3;if(u<60)return u.toFixed(1)+"s";const c=u/60;if(c<60)return c.toFixed(1)+"m";const f=c/60;return f<24?f.toFixed(1)+"h":(f/24).toFixed(1)+"d"}function lv(l){let u=0;for(let c=0;c<l.length;c++)u=l.charCodeAt(c)+((u<<8)-u);return Math.abs(u%6)}function Ve(l){if(!l)return l;try{const u=new URL(l,window.location.href);if(u.origin===window.location.origin){for(const[c,f]of new URLSearchParams(window.location.search))u.searchParams.append(c,f);return u.toString()}return l}catch{return l}}const Ph=({label:l,href:u,onClick:c,colorIndex:f,trimAtSymbolPrefix:r})=>{const o=m.jsx("span",{className:Ze("label","label-color-"+(f!==void 0?f:lv(l))),onClick:c?h=>c(h,l):void 0,children:r&&l.startsWith("@")?l.slice(1):l});return u?m.jsx("a",{className:"label-anchor",href:Ve(u),children:o}):o},$h=({projectNames:l,activeProjectName:u,otherLabels:c,style:f})=>(l.length>0&&!!u||c.length>0)&&m.jsxs("span",{className:"label-row",style:f??{},children:[m.jsx(uv,{projectNames:l,projectName:u}),m.jsx(iv,{labels:c})]}),iv=({labels:l})=>{const u=se(),c=ct.useCallback((f,r)=>{const o=new URLSearchParams(u);f.preventDefault(),o.has("testId")&&o.delete("speedboard"),o.delete("testId"),ca(Na(o,r,f.metaKey||f.ctrlKey))},[u]);return m.jsx(m.Fragment,{children:l.map(f=>m.jsx(Ph,{label:f,trimAtSymbolPrefix:!0,onClick:c},f))})};function ca(l){window.history.pushState({},"",l);const u=new PopStateEvent("popstate");window.dispatchEvent(u)}const Kf=({predicate:l,children:u})=>l(se())?u:null,Tn=({click:l,ctrlClick:u,children:c,...f})=>m.jsx("a",{...f,style:{textDecoration:"none",color:"var(--color-fg-default)",cursor:"pointer"},onClick:r=>{l&&(r.preventDefault(),ca(Ve((r.metaKey||r.ctrlKey)&&u||l)))},children:c}),Tr=({className:l,...u})=>m.jsx(Tn,{...u,className:Ze("link-badge",u.dim&&"link-badge-dim",l)}),uv=({projectNames:l,projectName:u})=>{const c=new URLSearchParams(se());return c.has("testId")&&c.delete("speedboard"),c.delete("testId"),m.jsx(Tn,{click:Na(c,`p:${u}`,!1),ctrlClick:Na(c,`p:${u}`,!0),children:m.jsx(Ph,{label:u,colorIndex:l.indexOf(u)%6})})},nc=({attachment:l,result:u,href:c,linkName:f,openInNewTab:r})=>{const[o,h]=ev();Cr("attachment-"+u.attachments.indexOf(l),h);const v=m.jsxs("span",{children:[l.contentType===fv?qh():Ih(),l.path&&(r?m.jsx("a",{href:Ve(c||l.path),target:"_blank",rel:"noreferrer",children:f||l.name}):m.jsx("a",{href:Ve(c||l.path),download:sv(l),children:f||l.name})),!l.path&&(r?m.jsx("a",{href:URL.createObjectURL(new Blob([l.body],{type:l.contentType})),target:"_blank",rel:"noreferrer",onClick:y=>y.stopPropagation(),children:l.name}):m.jsx("span",{children:Di(l.name)}))]});return l.body?m.jsx(av,{style:{lineHeight:"32px"},className:Ze(o&&"attachment-flash"),summary:v,children:m.jsxs("div",{className:"attachment-body",children:[m.jsx(Fh,{value:l.body}),Di(l.body)]})}):m.jsxs("div",{style:{lineHeight:"32px",whiteSpace:"nowrap",paddingLeft:4},className:Ze(o&&"attachment-flash"),children:[m.jsx("span",{style:{visibility:"hidden"},children:Cl()}),v]})},tm=({test:l,trailingSeparator:u,dim:c})=>{const f=l.results.map(r=>r.attachments.filter(o=>o.name==="trace")).filter(r=>r.length>0)[0];if(f)return m.jsxs(m.Fragment,{children:[m.jsxs(Tr,{href:Ve(nm(f)),title:"View Trace",className:"button trace-link",dim:c,children:[F5(),m.jsx("span",{children:"View Trace"})]}),u&&m.jsx("div",{className:"trace-link-separator",children:"|"})]})},em=ct.createContext(new URLSearchParams(window.location.hash.slice(1)));function se(){return ct.useContext(em)}const cv=({children:l})=>{const[u,c]=ct.useState(new URLSearchParams(window.location.hash.slice(1)));return ct.useEffect(()=>{const f=()=>c(new URLSearchParams(window.location.hash.slice(1)));return window.addEventListener("popstate",f),()=>window.removeEventListener("popstate",f)},[]),m.jsx(em.Provider,{value:u,children:l})};function sv(l){if(l.name.includes(".")||!l.path)return l.name;const u=l.path.indexOf(".");return u===-1?l.name:l.name+l.path.slice(u,l.path.length)}function nm(l){return`trace/index.html?${l.map((u,c)=>`trace=${new URL(u.path,window.location.href)}`).join("&")}`}const fv="x-playwright/missing";function Cr(l,u){const c=se(),f=rv(l);ct.useEffect(()=>{if(f)return u()},[f,u,c])}function rv(l){const u=se().get("anchor");return u===null||typeof l>"u"?!1:typeof l=="string"?l===u:Array.isArray(l)?l.includes(u):l(u)}function Si({id:l,children:u}){const c=ct.useRef(null),f=ct.useCallback(()=>{var r;(r=c.current)==null||r.scrollIntoView({block:"start",inline:"start"})},[]);return Cr(l,f),m.jsx("div",{ref:c,children:u})}function Cn({test:l,result:u,anchor:c},f){const r=new URLSearchParams(f);return l&&r.set("testId",l.testId),l&&u&&r.set("run",""+l.results.indexOf(u)),c&&r.set("anchor",c),"#?"+r}function hc(l){switch(l){case"failed":case"unexpected":return Kh();case"passed":case"expected":return kh();case"timedOut":return Jh();case"flaky":return qh();case"skipped":case"interrupted":return K5()}}const ov=({className:l,style:u,open:c,isModal:f,minWidth:r,verticalOffset:o,requestClose:h,anchor:v,dataTestId:y,children:A})=>{const E=ct.useRef(null),[S,O]=ct.useState(0),[X]=ur(E),[B,b]=ur(v),p=v?dv(X,B,o):void 0;return ct.useEffect(()=>{const x=U=>{!E.current||!(U.target instanceof Node)||E.current.contains(U.target)||h==null||h()},R=U=>{U.key==="Escape"&&(h==null||h())};return c?(document.addEventListener("mousedown",x),document.addEventListener("keydown",R),()=>{document.removeEventListener("mousedown",x),document.removeEventListener("keydown",R)}):()=>{}},[c,h]),ct.useLayoutEffect(()=>b(),[c,b]),ct.useEffect(()=>{const x=()=>O(R=>R+1);return window.addEventListener("resize",x),()=>{window.removeEventListener("resize",x)}},[]),ct.useLayoutEffect(()=>{E.current&&(c?f?E.current.showModal():E.current.show():E.current.close())},[c,f]),m.jsx("dialog",{ref:E,style:{position:"fixed",margin:p?0:void 0,zIndex:110,top:p==null?void 0:p.top,left:p==null?void 0:p.left,minWidth:r||0,...u},className:l,"data-testid":y,children:A})};function dv(l,u,c=4,f=4){let r=Math.max(f,u.left);r+l.width>window.innerWidth-f&&(r=window.innerWidth-l.width-f);let o=Math.max(0,u.bottom)+c;return o+l.height>window.innerHeight-c&&(Math.max(0,u.top)>l.height+c?o=Math.max(0,u.top)-l.height-c:o=window.innerHeight-c-l.height),{left:r,top:o}}const hv="system",am="theme",mv=[{label:"Dark mode",value:"dark-mode"},{label:"Light mode",value:"light-mode"},{label:"System",value:"system"}],lm=window.matchMedia("(prefers-color-scheme: dark)");function gv(){document.playwrightThemeInitialized||(document.playwrightThemeInitialized=!0,document.defaultView.addEventListener("focus",l=>{l.target.document.nodeType===Node.DOCUMENT_NODE&&document.body.classList.remove("inactive")},!1),document.defaultView.addEventListener("blur",l=>{document.body.classList.add("inactive")},!1),cr(sr()),lm.addEventListener("change",()=>{cr(sr())}))}const Av=new Set;function cr(l){const u=vv(),c=l==="system"?lm.matches?"dark-mode":"light-mode":l;if(u!==c){u&&document.documentElement.classList.remove(u),document.documentElement.classList.add(c);for(const f of Av)f(c)}}function sr(){return Ma.getString(am,hv)}function vv(){return document.documentElement.classList.contains("dark-mode")?"dark-mode":document.documentElement.classList.contains("light-mode")?"light-mode":null}function yv(){const[l,u]=ue.useState(sr());return ue.useEffect(()=>{Ma.setString(am,l),cr(l)},[l]),[l,u]}const Or=({title:l,leftSuperHeader:u,rightSuperHeader:c})=>m.jsxs("div",{className:"header-view",children:[m.jsxs("div",{className:"hbox header-superheader",children:[u,m.jsx("div",{style:{flex:"auto"}}),c]}),l&&m.jsx("div",{className:"header-title",children:Di(l)})]}),Ev=({stats:l,filterText:u,setFilterText:c})=>{const f=se().get("q");return ct.useEffect(()=>{c(f?`${f.trim()} `:"")},[f,c]),m.jsx(m.Fragment,{children:m.jsxs("div",{className:"pt-3",children:[m.jsx("div",{className:"header-view-status-container ml-2 pl-2 d-flex",children:m.jsx(pv,{stats:l})}),m.jsxs("form",{className:"subnav-search",onSubmit:r=>{r.preventDefault();const o=new URL(window.location.href),h=new URLSearchParams(o.hash.slice(1)),v=new FormData(r.target).get("q"),y=new URLSearchParams({q:v});h.has("speedboard")&&y.set("speedboard",""),y.toString()&&(o.hash="?"+y.toString()),ca(o)},children:[I5(),m.jsx("input",{name:"q",spellCheck:!1,className:"form-control subnav-search-input input-contrast width-full","aria-label":"Search tests",placeholder:"Search tests",value:u,onChange:r=>{c(r.target.value)}})]})]})})},pv=({stats:l})=>{const u=se().has("speedboard");return m.jsxs("nav",{children:[m.jsxs(Tn,{className:"subnav-item",href:"#?",children:[m.jsx("span",{className:"subnav-item-label",children:"All"}),m.jsx("span",{className:"d-inline counter",children:l.total-l.skipped})]}),m.jsx(ac,{token:"passed",count:l.expected}),m.jsx(ac,{token:"failed",count:l.unexpected}),m.jsx(ac,{token:"flaky",count:l.flaky}),m.jsx(ac,{token:"skipped",count:l.skipped}),m.jsx(Tn,{className:"subnav-item",href:"#?speedboard",title:"Speedboard","aria-selected":u,children:Jh()}),m.jsx(bv,{})]})},ac=({token:l,count:u})=>{const c=new URLSearchParams(se());c.delete("speedboard"),c.delete("testId");const f=`s:${l}`,r=Na(c,f,!1),o=Na(c,f,!0),h=l.charAt(0).toUpperCase()+l.slice(1);return m.jsxs(Tn,{className:"subnav-item",href:r,click:r,ctrlClick:o,children:[u>0&&hc(l),m.jsx("span",{className:"subnav-item-label",children:h}),m.jsx("span",{className:"d-inline counter",children:u})]})},bv=()=>{const l=ct.useRef(null),[u,c]=ct.useState(!1),[f,r]=yv(),[o,h]=_h("mergeFiles",!1);return m.jsxs(m.Fragment,{children:[m.jsx("div",{role:"button",ref:l,style:{cursor:"pointer"},className:"subnav-item",title:"Settings",onClick:v=>{c(!u),v.preventDefault()},onMouseDown:xv,children:_5()}),m.jsxs(ov,{open:u,minWidth:150,verticalOffset:4,requestClose:()=>c(!1),anchor:l,dataTestId:"settings-dialog",children:[m.jsxs("label",{className:"header-setting-theme",children:["Theme:",m.jsx("select",{value:f,onChange:v=>r(v.target.value),children:mv.map(v=>m.jsx("option",{value:v.value,children:v.label},v.value))})]}),m.jsxs("label",{style:{cursor:"pointer",display:"flex",alignItems:"center",gap:4},children:[m.jsx("input",{type:"checkbox",checked:o,onChange:()=>h(!o)}),"Merge files"]})]})]})},xv=l=>{l.stopPropagation(),l.preventDefault()},Sv=({tabs:l,selectedTab:u,setSelectedTab:c})=>{const f=ct.useId();return m.jsx("div",{className:"tabbed-pane",children:m.jsxs("div",{className:"vbox",children:[m.jsx("div",{className:"hbox",style:{flex:"none"},children:m.jsx("div",{className:"tabbed-pane-tab-strip",role:"tablist",children:l.map(r=>m.jsx("div",{className:Ze("tabbed-pane-tab-element",u===r.id&&"selected"),onClick:()=>c(r.id),id:`${f}-${r.id}`,role:"tab","aria-selected":u===r.id,children:m.jsx("div",{className:"tabbed-pane-tab-label",children:r.title})},r.id))})}),l.map(r=>{if(u===r.id)return m.jsx("div",{className:"tab-content",role:"tabpanel","aria-labelledby":`${f}-${r.id}`,children:r.render()},r.id)})]})})},im=({header:l,footer:u,expanded:c,setExpanded:f,children:r,noInsets:o,dataTestId:h})=>{const v=ct.useId();return m.jsxs("div",{className:"chip","data-testid":h,children:[m.jsxs("div",{role:"button","aria-expanded":!!c,"aria-controls":v,className:Ze("chip-header",f&&" expanded-"+c),onClick:()=>f==null?void 0:f(!c),title:typeof l=="string"?l:void 0,children:[f?c?m.jsx(Ni,{}):m.jsx(Cl,{}):m.jsx(q5,{}),l]}),(!f||c)&&m.jsxs("div",{id:v,role:"region",className:Ze("chip-body",o&&"chip-body-no-insets"),children:[r,u&&m.jsx("div",{className:"chip-footer",children:u})]})]})},ke=({header:l,initialExpanded:u,noInsets:c,children:f,dataTestId:r,revealOnAnchorId:o})=>{const[h,v]=ct.useState(u??!0),y=ct.useCallback(()=>v(!0),[]);return Cr(o,y),m.jsx(im,{header:l,expanded:h,setExpanded:v,noInsets:c,dataTestId:r,children:f})},Tv=({title:l,loadChildren:u,onClick:c,expandByDefault:f,depth:r,style:o,flash:h})=>{const[v,y]=ct.useState(f||!1);return m.jsxs("div",{role:"treeitem",className:Ze("tree-item",h&&"yellow-flash"),style:o,children:[m.jsxs("div",{className:"tree-item-title",style:{paddingLeft:r*22+4},onClick:()=>{c==null||c(),y(!v)},children:[u&&!!v&&Ni(),u&&!v&&Cl(),!u&&m.jsx("span",{style:{visibility:"hidden"},children:Cl()}),l]}),v&&(u==null?void 0:u())]})},Cv="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYgAAADqCAYAAAC4CNLDAAAMa2lDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnluSkJDQAqFICb0J0quUEFoEAamCjZAEEkqMCUHFhqio4NpFFCu6KqLoWgBZVMReFsXeFwsqK+tiQVFU3oQEdN1Xvne+b+7898yZ/5Q7c+8dADR7uRJJLqoFQJ44XxofEcIcm5rGJHUAMjABVOAMSFyeTMKKi4sGUAb7v8v7mwBR9NecFFz/HP+vosMXyHgAIOMhzuDLeHkQNwOAb+BJpPkAEBV6y6n5EgUuglhXCgOEeLUCZynxLgXOUOKmAZvEeDbEVwBQo3K50iwANO5DPbOAlwV5ND5D7CLmi8QAaA6HOJAn5PIhVsQ+PC9vsgJXQGwH7SUQw3iAT8Z3nFl/488Y4udys4awMq8BUQsVySS53On/Z2n+t+Tlygd92MBGFUoj4xX5wxrezpkcpcBUiLvEGTGxilpD3CviK+sOAEoRyiOTlPaoMU/GhvUDDIhd+NzQKIiNIQ4X58ZEq/QZmaJwDsRwtaDTRPmcRIgNIF4kkIUlqGy2SCfHq3yhdZlSNkulP8eVDvhV+Hooz0liqfjfCAUcFT+mUShMTIGYArFVgSg5BmINiJ1lOQlRKpuRhUJ2zKCNVB6viN8K4niBOCJEyY8VZErD41X2pXmywXyxLUIRJ0aFD+QLEyOV9cFO8bgD8cNcsCsCMStpkEcgGxs9mAtfEBqmzB17IRAnJah4eiX5IfHKuThFkhunssctBLkRCr0FxB6yggTVXDw5Hy5OJT+eKcmPS1TGiRdmc0fFKePBl4NowAahgAnksGWAySAbiFq76rvgnXIkHHCBFGQBAXBSaQZnpAyMiOE1ARSCPyESANnQvJCBUQEogPovQ1rl1QlkDowWDMzIAc8gzgNRIBfeywdmiYe8JYOnUCP6h3cubDwYby5sivF/rx/UftOwoCZapZEPemRqDloSw4ihxEhiONEeN8IDcX88Gl6DYXPDfXDfwTy+2ROeEdoIjwk3CO2EO5NExdIfohwN2iF/uKoWGd/XAreBnJ54CB4A2SEzzsCNgBPuAf2w8CDo2RNq2aq4FVVh/sD9twy+exoqO7ILGSXrk4PJdj/O1HDQ8BxiUdT6+/ooY80Yqjd7aORH/+zvqs+HfdSPltgi7CB2FjuBnceasHrAxI5jDdgl7KgCD62upwOra9Bb/EA8OZBH9A9/XJVPRSVlLjUunS6flWP5gmn5io3HniyZLhVlCfOZLPh1EDA5Yp7zcKabi5srAIpvjfL19ZYx8A1BGBe+6YrfARDA7+/vb/qmi4Z7/dACuP2ffdPZHoOvCX0AzpXx5NICpQ5XXAjwLaEJd5ohMAWWwA7m4wa8gD8IBmFgFIgFiSAVTIRVFsJ1LgVTwUwwF5SAMrAcrAHrwWawDewCe8EBUA+awAlwBlwEV8ANcA+ung7wEnSD96APQRASQkPoiCFihlgjjogb4oMEImFINBKPpCLpSBYiRuTITGQeUoasRNYjW5Fq5BfkCHICOY+0IXeQR0gn8gb5hGIoFdVFTVAbdATqg7LQKDQRnYBmoVPQQnQ+uhStQKvQPWgdegK9iN5A29GXaA8GMHWMgZljTpgPxsZisTQsE5Nis7FSrByrwmqxRvicr2HtWBf2ESfidJyJO8EVHIkn4Tx8Cj4bX4Kvx3fhdfgp/Br+CO/GvxJoBGOCI8GPwCGMJWQRphJKCOWEHYTDhNNwL3UQ3hOJRAbRlugN92IqMZs4g7iEuJG4j9hMbCM+IfaQSCRDkiMpgBRL4pLySSWkdaQ9pOOkq6QOUq+aupqZmptauFqamlitWK1cbbfaMbWras/V+shaZGuyHzmWzCdPJy8jbyc3ki+TO8h9FG2KLSWAkkjJpsylVFBqKacp9ylv1dXVLdR91ceoi9SL1CvU96ufU3+k/pGqQ3WgsqnjqXLqUupOajP1DvUtjUazoQXT0mj5tKW0atpJ2kNarwZdw1mDo8HXmKNRqVGncVXjlSZZ01qTpTlRs1CzXPOg5mXNLi2ylo0WW4urNVurUuuI1i2tHm26tqt2rHae9hLt3drntV/okHRsdMJ0+DrzdbbpnNR5QsfolnQ2nUefR99OP03v0CXq2upydLN1y3T36rbqduvp6HnoJetN06vUO6rXzsAYNgwOI5exjHGAcZPxSd9En6Uv0F+sX6t/Vf+DwTCDYAOBQanBPoMbBp8MmYZhhjmGKwzrDR8Y4UYORmOMphptMjpt1DVMd5j/MN6w0mEHht01Ro0djOONZxhvM75k3GNiahJhIjFZZ3LSpMuUYRpsmm262vSYaacZ3SzQTGS22uy42R9MPSaLmcusYJ5idpsbm0eay823mrea91nYWiRZFFvss3hgSbH0scy0XG3ZYtltZWY12mqmVY3VXWuytY+10Hqt9VnrDza2Nik2C23qbV7YGthybAtta2zv29Hsguym2FXZXbcn2vvY59hvtL/igDp4OggdKh0uO6KOXo4ix42ObcMJw32Hi4dXDb/lRHViORU41Tg9cmY4RzsXO9c7vxphNSJtxIoRZ0d8dfF0yXXZ7nLPVcd1lGuxa6PrGzcHN55bpdt1d5p7uPsc9wb31x6OHgKPTR63Pemeoz0XerZ4fvHy9pJ61Xp1elt5p3tv8L7lo+sT57PE55wvwTfEd45vk+9HPy+/fL8Dfn/5O/nn+O/2fzHSdqRg5PaRTwIsArgBWwPaA5mB6YFbAtuDzIO4QVVBj4Mtg/nBO4Kfs+xZ2aw9rFchLiHSkMMhH9h+7Fns5lAsNCK0NLQ1TCcsKWx92MNwi/Cs8Jrw7gjPiBkRzZGEyKjIFZG3OCYcHqea0z3Ke9SsUaeiqFEJUeujHkc7REujG0ejo0eNXjX6fox1jDimPhbEcmJXxT6Is42bEvfrGOKYuDGVY57Fu8bPjD+bQE+YlLA74X1iSOKyxHtJdknypJZkzeTxydXJH1JCU1amtI8dMXbW2IupRqmi1IY0Ulpy2o60nnFh49aM6xjvOb5k/M0JthOmTTg/0Whi7sSjkzQncScdTCekp6TvTv/MjeVWcXsyOBkbMrp5bN5a3kt+MH81v1MQIFgpeJ4ZkLky80VWQNaqrE5hkLBc2CVii9aLXmdHZm/O/pATm7Mzpz83JXdfnlpeet4RsY44R3xqsunkaZPbJI6SEkn7FL8pa6Z0S6OkO2SIbIKsIV8X/tRfktvJF8gfFQQWVBb0Tk2eenCa9jTxtEvTHaYvnv68MLzw5xn4DN6MlpnmM+fOfDSLNWvrbGR2xuyWOZZz5s/pKIoo2jWXMjdn7m/FLsUri9/NS5nXON9kftH8JwsiFtSUaJRIS24t9F+4eRG+SLSodbH74nWLv5bySy+UuZSVl31ewlty4SfXnyp+6l+aubR1mdeyTcuJy8XLb64IWrFrpfbKwpVPVo1eVbeaubp09bs1k9acL/co37yWsla+tr0iuqJhndW65es+rxeuv1EZUrlvg/GGxRs+bORvvLopeFPtZpPNZZs/bRFtub01YmtdlU1V+TbitoJtz7Ynbz/7s8/P1TuMdpTt+LJTvLN9V/yuU9Xe1dW7jXcvq0Fr5DWde8bvubI3dG9DrVPt1n2MfWX7wX75/j9+Sf/l5oGoAy0HfQ7WHrI+tOEw/XBpHVI3va67Xljf3pDa0HZk1JGWRv/Gw786/7qzybyp8qje0WXHKMfmH+s/Xni8p1nS3HUi68STlkkt906OPXn91JhTraejTp87E37m5FnW2ePnAs41nfc7f+SCz4X6i14X6y55Xjr8m+dvh1u9Wusue19uuOJ7pbFtZNuxq0FXT1wLvXbmOuf6xRsxN9puJt28fWv8rfbb/Nsv7uTeeX234G7fvaL7hPulD7QelD80flj1u/3v+9q92o8+Cn106XHC43tPeE9ePpU9/dwx/xntWflzs+fVL9xeNHWGd175Y9wfHS8lL/u6Sv7U/nPDK7tXh/4K/utS99jujtfS1/1vlrw1fLvznce7lp64nofv8973fSjtNezd9dHn49lPKZ+e9039TPpc8cX+S+PXqK/3+/P6+yVcKXfgVwCDDc3MBODNTgBoqQDQ4bmNMk55FhwQRHl+HUDgP2HleXFAvACohZ3iN57dDMB+2GyKIHcwAIpf+MRggLq7DzWVyDLd3ZRcVHgSIvT29781AYDUCMAXaX9/38b+/i/bYbB3AGieojyDKoQIzwxbghXohgG/CPwgyvPpdzn+2ANFBB7gx/5fCGaPbNiir/8AAACKZVhJZk1NACoAAAAIAAQBGgAFAAAAAQAAAD4BGwAFAAAAAQAAAEYBKAADAAAAAQACAACHaQAEAAAAAQAAAE4AAAAAAAAAkAAAAAEAAACQAAAAAQADkoYABwAAABIAAAB4oAIABAAAAAEAAAGIoAMABAAAAAEAAADqAAAAAEFTQ0lJAAAAU2NyZWVuc2hvdHGOMr4AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAHWaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjIzNDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4zOTI8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KmnXOOwAAABxpRE9UAAAAAgAAAAAAAAB1AAAAKAAAAHUAAAB1AABxIC1bFLAAAEAASURBVHgB7L13tF/HcedZL+eInAECIAmQIMAkikESRSUqi6Ngj23ZK8u2rLFlr3c8Zz27Pp7dtXfOnOM/PDNOs+u8li3ZEiVKJCVKlJgpBpAERQIkkXPGw8s57fdT99XDxQ+/38PDCwBI3gZ+797bt7uqu7q6qro63KKXXnxp9LFHH7OtW7daX2+fjY6O6memvzZxiPdFShb36Rz54okj5EufvMn+ZhTIKJBRIKNAPgrkk6mkyxefK2vj+Vy4ReTX/+KiIquoqLAVK1fY+97/Plu9ZrUV/ec/+s+jzzz9jLWePq2co1ailIjvYf0d4WYMbrGuJcU8F9nwcCgRPROATwL9HyUT+fhlIaPANChQJF4rLi52oyXAJAbM5JiL/PxGRkY8ezwHDJ65Bwf3BNISN5kQeeIa+SeTN0uTUeCSUaAAe9MD4OXqmmrbdP0m+4XP/4IVffbffHb0xPETNjo0ZHWlxba0stwG1UEO9Q1Y9/CI0bVQDLVVxbZsHurD7OCpYevsGTG9dqVQonfl80qtqKzIBk4O2XCXOpmUSBYyCkyHAmVlZTZ//nzr6+uz/v5+47mjo0MGyvB5wcLodXV1nranp8eFfnl5udXU1DgM3peUlNjAwICnq62tdeXQ2trquM6HgPxYW6Wl4nvdA5vraRlaoZDOByN7n1FgRimQFrmJvZMffDpdKkVkoV/Qd37rd37Lij7+kY+Ptp5utSZpgc8uarINdVU2oFHAM21d9t0T7dYri6q2ssg+9+5qe+dVdAKzzTsH7OuP91hHj6yv8iJrur3G6jZW+X3PngFreaTTBk+fvxOnypbdZhQ4hwJVVVX23ve+13p7e62rq8sFb0tLiwtlhD4COQQ0SoNnrP/KykobksGD0Edgw+wIbRTNVVddZQcPHvRRA/EvvfSSzZ07166++mprb2+37u5uVyoopMiPIkAB8H7OnDkG7uPHj1tzc7MtWLDA8fKe+FOnTll9fb3jpRz8KDujlIaGBk8zODjo8ceOHXN851Q8i8gocKEUyCf0Q+LnwsqXVmkiOf2IvveLX/hFK/ro3R8dbW9rt5WVZfaHaxdbtRQFA4OWgSH799sPWcfQsM2pK7Y//XeN1lzrPiYphhH7zb9stRNtI8boYcVvzbWyphLGJzbSP2KH/u609e4dyC1W9pxR4IIogLX/kY98xBUEwhdrH4UA8yJcly1b5sIXwb1kyRJ/19nZ6aOEvXv3umKA2RHYCPkdO3bY2rVr/Z78WEpPPfWUj0w2btxoR48etdWrVzsehHpTU5PDRLmgmHhGESDgN2/ePJ4P+IcOHfJyVVdXuzKgvOAkD7gY/aAgUEC8Q6G9/PLLRnmzkFFg2hQoIPTHpX4aQYG0aQWBYfO5f/u5MwpiRUWp/cGaxdZYVuIK4kDvgP3BriPjCuKPv9hgi+dICQjj8bZh+w9/0zauIJb+arNVLi5zBTHcPWyH/6HVevdnCiLdJtn9hVMgFMSWLVvc7YPw5oeAfe2119zq379/vx04cMDuuusuF9yMEvg988wzPlpAoCPAGQ28/vrrLqQZBTCyoBM8+eSTriiuu+46VxBXXHGFjwJQOAj0gMfIgPLg8iLviy++6KOVW2+91ZXGrl27bPHixQ4TxXPy5El/xmWFkjh8+LABm7zAZBRDuVE2WcgoMG0KFBD6DjckPw8TpItkGFX0jc/8zGfOKIhqvb29qdbeM6fO5x5+eKrDXu6U1SZ3U6XcSLfIvfShGys102328EvqgG8MWE//qBWValJjTbk1vrPGSmqKrXNLr3X8tNeGu5OJwWlXPAPwtqUAowWENFZ2DHthXNxIWPU333yzbdu2zS100hHPD/cSIwBGGo2NjbZRwn9Y6Z977jm33BHgWPBY9QjsgB2uH2CjWMAPLGCShnvwc2XUQsBNxXvykod0wEUJcCU+nlFS4GUkhAuLK7iykFFg2hSYQPBPFnZaQVRUVtg9n77njIIoGlWnkJ+0Vi4mZg+6YGQpB/CiFMo1AV1b5QuirLN31AaG6KR6qXfFeldcqZUgGmAMy/00Oqh8Gd9Ptl2ydFOgAEIX4c+kNcI2X0BYI+RJhzBm5JFZ7PkolcW96SkwCwrik/d80oo+8qGP+BxEriUzGurkTU+5rAIZBTIKZBR4i1NgFhTExz75MSu68113juInzRTEW5yBsuplFMgo8NalwDQVRHo8wIq7cRfTsqXLRlmhMTySLUt963JPVrOMAhkFMgpMjgLFRcU+f/cbv/kbVqRVGaOs0MgdQUwOVJYqo0BGgYwCGQXeShRg7o7l2l/5ylcyBfFWatisLhkFMgpkFJguBUJB/MZvZCOI6dIyy59RIKNARoG3FAVCQXz5y1/OP4IoLimz0opKK6/SrlDd93V32GBPp2k7hB/gx1lNWcgokFEgo0BGgbceBUJBfOlLX8qvIGqaF9ui6+60q265SbuqS+31R79nB1/8kd1YW2I7uwetdWh2NznERqa3HumzGmUUyCiQUeDypkAoiF/7tV+zonnz5vkqpvQkddOCK2zZpvfb+g992BrnNtuL3/66bbn/r+2m6mLb2ztoxwfPVRDr1q2zz3zmM75x6bHHHvMjCdra2mz79u2+kYkdpASQ84vALlRCxKMcPv3pT9sDDzzgZ/DwbuHChX5o29e+9jXficqRBRzgxvk36XKTNgsZBTIKZBTIKDB1CoSC+NVf/dUCCqK2ya689n3WeNsHrbF+xJqOPmf7tj5ldac7bMuxTtvV2nMOdk7d/Nmf/Vl79tln/bwcDkZDQYAMJcDhaQh6DkTjzBoOLUMZcKAZyoOVVEuXLvUza6688ko/AkHKy/Nz3g5K45FHHrEjR47Yhz/8YYfzj//4j37swTmFySIyCmQUyCiQUWBKFAgF8Su/8iv5FcS8snJb1TDHiq661ZYtn2u3zztsi5fV2f6t+2zza0ftG5v3n4P4/e9/v+Gz4hA1Nt5xBAJn6HB65sqVK/3wtPXr1/s7TrfkADPecWYNowFOtuR0zu9///vG0IZzazhw7V3vepd94xvfsA9+8IP29NNP+wFsnLHz05/+1O69997s6IRzWiKLyCiQUSCjwNQpEArii1/8Yn4FsUgnuy6uKLPXdcTNtSub7T9+5mpbfMVCe/EnO+wbTx2wR7cfPwc7I4iPf/zj9id/8id+7g3Pa9as8VEBB6Lt3r3bhT5xf/u3f2u33367Kw7ecdomp2Nyvg4K4vd///d9He4rr7xi7373u+2f/umf7KabbnKF8KlPfcqPU37jjTfsW9/6VqYgzmmJLCKjQEaBjAJTp8B5FUSTvix3ZU2ZvdgxoCO+a+0Ld66yxuY6e33HMbvvpcN2oqPvHOwrVqwwfj/5yU/cdbRq1So/iZMrh6kxX3D99de7cP/2t7/tiiMmo9mUgYuJgCK54447bNGiRX6cM26pv/iLv3CFgVLgwy4cvMb7Bx980N1T5xQmi8gokFEgo0BGgSlRIBTEL//yL+cfQZQVaSddSZF16puiFWWltmJuna1b2mzP7zpmx9t7bci/NTo53CBjDoJjkj/5yU/6kcucg3++yWXO/b/tttv8Qy2c2Z99WGVy9M5SZRTIKJBRYDoUCAXxhS98Ib+CADjrjFhfJPmuez4ez8ffdcT3FPdAgJQRA4rhfMoB/BwYxY/AJHasdvKI7E9GgYwCGQUyCswKBSalIGYFcwY0o0BGgYwCGQUuawpkCuKybp6scBkFMgpkFLh0FDhLQSxYsOCcjXKXrmgZ5owCGQUyCmQUuJQUwLXPVoJf+qVfsiLtMxhlAjjXx88zP7QJv4sVMrwXi9KaY8ra+KIQ+1LRmcpdKtwZ3ovCWo5kNmjNoiL2thXt2rVrFAUAktzAJjZ2Ol9MBUEZWBYbH4DPLdNsPVN/vlfMN4xjcny2cKXhgpdluxe7vlGGS0XrS1HnaGP221xMno42pi9dinCp2pj+dClofanwwtNvlTZGBj7++ONWpEqN0ogRhoaG/JYEMBYVnqrApGOkz2DiOUK6g3Ifz+nOFHGRZ7LXwBv5WTUVOHiXrg/xxPGj7tAi/X6yOElXCG+8YxVXbgi801EQwKCOXCNEvXgOOsQ1HRfCY6p1DnzpK+WIMgE37ql/lCs6U7pMaRhTvY9VcuAKvOAIvLTxdGhdqFxpvKQJGkR6hNZsGFuBh2s6RJ2Ji348k7QOvNQ72niU+1QbU+eZpjV4g9cvdhvHasrAm0vz2WrjqC9tGbSmLNGfZquNOcHiLAVBQV7YvNkLcbUO36MwDDW4XmgAFuctbXnpJWue06weY378xiKdw3To0EGbN2++H7cBXOBztEYIDeKm05kQAuy1YARUV1dn27XB7iptsDtx4gSgbZU271E+fjU1NY6XkQPMPB0FAYNwBlWfjg5ZonOlXn/9NVuyZKk6aJ9vBLzhhhs0ShlyYQUeykmnZaPgdDoSeA8ePKDzqY6Z5pR0nMlpwU1oevjwYd/RDo4QGuCiM1er7sHUU2ljJ2aeP9SLHfLQm82TnL91ROW44cYbXXCGlTWdNs6D1qM45mXXzp22cdMmPwtMI2Sd/bVI7VxrlTJ2yvmp/jMpLEEM3q2vvmq3au8OnZfjZHp7e2zOnLnOU/Sj2agvwp/zyeDlOXPm2D6deQYvLxfdCfiSaQ/wz2SdgXlS7dve3j5+hhp1po1HVP8K4SPMdJ3p02ym7dd1w3XXic5Hbafa+8orr3Jcs9XG0Jcz4fhxqgPPyJUaya15c9XG4qnZamP68AHhXbFyhc2dO8/7FnHsFaPf0sbw3EzT+iwFgTbkTKTnn3/empubrbGx0a8IlqkIDwiIgjh+/Lg3IvdLly6zffv2SmCtdcZq0e7puvo6pevxyh6TIFmn85rAOVWGph4w0Q9/+EOrl3Lo0PzKMglrLBsYuKqq0mEjqFEOHChYLmGN4lqyZMm0FAR4f/DQQy6AVkoJcR7V3r17XGiXlyeCCeXBXpKGhgYJziO2dMlSW63jR2jkqXZg8KKI2zva1SWLXCB2dXZZj3CtWrXSOLIExdHV1eVKGeVRJiFCGVFU0HoqbSxkeQNtj9Bi9zxMjNJEITVLgKE42A0P7afaxnmRjkUiqDdvft7e8547fXMl/Nfe3uYGygLtyudAyNlQEJwE8MwzP7G77/6wnTx50tj1T4elPCt1FhlGSSjpicp/oe8Q1KGAFy9ZbE8+8aSfMtCos84InHxMG0+Hv/KViTbGCGtpOeV127Z1m/P67bff4YbBWh24idE300ILIxLeOnjwoN1yyy0ywl5PjMDtb3gdl0nGLNWZbjPdxsgVeOlVGQEoCPpLq3iZ9kWuoJCXL1/udZ5qP85HZ+KAf/p0i2hZ6UbAT3VuHX1+rg4zLZVcu0J9DKNgpvvTOQoC4rNrGWHSJAUBk01VQUBQ4D388MM6YmOTE3bFipUuFNGCAwP9duL4CVuoIzPoUBydgfCAyByvMVWGBi/wHpKgpmNwKOBcCabaulrX/ggmtG11dY208VzbtnWr4ybPe++6a9oK4qmnnvLGQsFinXOKLXXDqqqXUiCup6fHGUnmtHemO3QgISOdqTIWSuenUgLUZ+vWV23F8hWuLPj4OAJx8wubbc3qNXZKnbmpqdnPvmppaXHBRTlnWkHA1Cx8wNLDqsXagaFpWzo2p/lSrplmaPD29fXali1bbMOG61z579+/T0pqjdOZctylNp5p4QFe2vS55561W2+9zQ+sRDnSngsWzPdy3HzzO9womElFDF4C/eyll14Uny32foYRhDEGT3P2GWedTbU/JRjy/21ra1V7HvI23r17lw0PDbvM6JRhBC+uknKaaQVB/9kpg4P6zJec2rlzh4zZJhkh29XP53qd79Q5cDPdxsgVDB0UI6NiZMhLOj8Ogxa+QlFs3LjR5c1Mt/GBA/vdCFi//hrr0IgNGYNhcKMUFaM4yjAbBtdZCiJYAIsXK5TODOPRwFOpMIRESOzZs9sa6hvcnUHlaFQ0IqMUiF5ZWeE+Uvxpra1trhy4n47wAC5Db5iJsu+RoELDU55EOVS7IkCBMEQOgY0yIW4q9YV+wMatwkiM+h0+fEgn1C63XgmPUxLI0JQ0w8ND8h+WOjOj+VEg0+lIwIRBwc0R6V2qZwkWhdruqIbgCAvw0J5lOjplYGDQR3fghVZTbePgmXxXGLhbI5Zu1R1BSfkYoTFqhAcoz3TqnA8ncdAijplnBAfe5uYmrzMWLe0y08IDvNQXXCGIqRsjNkYWpaUlUhQLp8XT4CgUgu8YCXdppE7fw7CDrzG2CNPpT4XwhrETqyChOycynxavz58/30pVnpluY/o1Rhe4MTR4RhGu1CiNK4JyNtoYmuLGpT1RxPA0+OApykJfm6c6R/sXotlU4hmpMYJAEVI3aNrT0y28Q2e18UzTOq+CoLJUHiGN1QfSqQhMBA+/NDyITAA+v3QgbQQ623QrG/DACTzqA06eqU/g5zmddjoKAjh01gjARhBy5RdliPekJQ+/6Qgt8gcOYAd9ieMdeCMEDXiGDigNcE+ljQNmvmvUK122qD9xM9HGk8Wbrhs0mQ6t8+EkLl3fwEdc8APX6fL0RLiBT9tGOUjLM2WhD852nYP/vI2hh36Uaabxgge4XMFFfQncE8cz15nGC1x4FtzgSgfeQWvezUYbB17a0unr7YxMSepOfBjzlGOmQl4FkQaOgoDQwfDpd7N1D7GxCmaD0BOVGbyJhT31EcRE8Au9C7wzzdCF8KXjg9aXoo1DMc0kQ6frlu/+UtE66DxVniY/QgJBD4zojzxzjxGSL5AHYcl76FyI1iFYcwUfMHnHj3cBLwR/4KUcBIyrCBPRmnfkIX/UhT4PDuJCAZCOHyHeBfxC14nwFsozE/HgvRRyi7LPFu5MQaQ4IxhrOiOIFLhJ3wbeTEFMmmRTTnipaB0dOARorqDmfTqkhXkIaNyy/NauXSO3Rp0LBVwcvMfVgqAFTsACBi6RMn38i/mAtPAnTRoHypq5oVWaSI8QZeQdk7O4I5kcrpDBuF2++GuuucbdHaTHxUTZrrrqKi8PecGRNgKiXKTnft++fT4PBU1I/4IWFqxbpwUqchHt2bPH501w/2KklqhuK1U23EfnC7l4z5d+pt6D962mIL75zW+evcw1l1jZCCKXIjP/fKkYmpoEU2cjiJlv1zRE6Mwqvvvv/65Wj5XZO7T6hqWZQ7Kir5RQ5WNZo6MjErBX+9JofNw33MC3U0rsR1rkUasFDKx6YwXgdddt9MUWzHMh0Jn34KTl22673f3jTz/9lBTGEl/0wEIEVsrhF1+zZq1WVr1uixYu8rmRPi29vummm32ugqWi37nvPnufds4e2H9AvinTyr9l1qS5m23bWJ201+68870+gmBxwdOaJGXZ+KuvvuKK6frrb/AyVVdXSYkc9YlaVqodP87qm1at1FviOI+oLJVaiVM5tiwTujDJuljvWR3U0FBvV6690n748A/1Jcl3ex1YIt7R3uHLlplfO1+4VP0p+tJUR4nnq9dE72cLdzaCSFE9GCsbQaSIMku3QeuLPWq6lHiZsP7bv/0b26T9GXL2+Iq94xKOWOJY2qzMWaT9GkwsM6F86623+jLpXbt2+wiBPRVbteIOYcyyVgT1gNwyz8vy/sQnPuGLIbZt2+pLi1nifNPNN7vFz3JmYDJ5zUQqS6FH9D2XBq1eY0IZIxC3Dp/33bDhWs/DqISFDc8//5yvysKi52Nf3d1dvt8AVxPW/UMPfV8jmivFJaMqa6cvK29TPKMZFp/Qvu1tyXMixLQ0UysYUYpPPPGEKwCW4e7atdNHINdvul7fmhk2lnF+Qt+OYQUcYbP2ZqFkWBV1vnAp2/itNoLIFESK24KxMgWRIsos3Qat3y4KAoHLCOLP/vS/+34bhPyjjz7qI4gPfuhDbomzOuaaa671UQLLNVnxxmqvx5QOy3rxkqVaPrtNCuZ627F9uw2PDGs/yTpfYokCuOeee/xrjq/rm/C4Yq5YfYVGJebuJdbrs+S4V8qA1U0IdJZAr1u/zlfk4PNn/87KVSvt4IGDPrJk1R/upFYtZWWUsuHaDT6CYOSAAjsht9NXv/qPWq20wG7U5jhGRKxiGhwa9L0XJ0+clEBnn025vbzlJXcdbdy4yVfYse/n8ccf8/kUYFPevt5k4xurc9joeP0NN7rLChZEmVypfRWxIou4QuFS8lamIAq1ygzG08CXgtDBWJmCmMHGLAAqaP12URDUFyH+7LPP+iYrhDT7YuR8d/89AhTh/a53vUtCs9Ine/HLExDe3PND0UQ877gHNhZ7TCKzhJqNkDHnwPv4kT8dT7703AXpRqR4In/kAxf3LLdk+Sp7dlhC/Kr23tz8jnc4jEhDujYJ/RfkNuPTwelln1EProxC2MDJCKamptqVHX0v4ESdqD+uMfZQsaT0fAH86bmP86WfqffgvRRyi/LPFu5sBJHijmCsTEGkiDJLt0HrqSoIhAZCgA4JrMkG0rJ6hjZOC9rJ5p9qOvBSVoQiPuq0ICQOYVtRUe7KAXdPumzkTT+nywCcNLz0u/R9KJZCcNJpC90H7YABXujILxmRnJ0LoR90nsgnj9LkxwiCdCiFCNAFGMy10Na8C7pFmnzXKCdpp1PffLAnigNvKCbaEPyzsfckXxnAPRnlBB9A1+DFfLAwIOiXtEc2SZ2iUDQwDZtm1FSSWbkNvFMVltMpVDAWuN9MdUYAcSQMvnGYfrKB+ka42MJjpvFSfjZ2YqWzSmmigEC42Pw1Xb6mjZkX4eyjN1sbI1yZs1k3dp7dRG0zE++iH0+kjMGDcmC12nPPPeeKOR9uYOAyZEVapiBSFAqGzhREiiizdBu0nqrQwuLmKBWYHaZ/OwYUOsekfOADH7CVK1cWJMFkhUdBAFN8Md02ZhL8wQcf9ElzRoxvpsDIYcOGDToP7D3jLr3ZLP9k2xhD4TXNUT399NM+J5avTJSdhRS4O++9995smWsQKRg6UxBBkdm7Bq2nqiCY8H3kkUdcQbzZhMdMUTUUxJ133ulHmBSCO1nhUSj/VONnoo05cJMVW2+2NsYKR0FwmODFGJlPto1x2bGYgJEZrr18AQVxnU7JvVmr4CZUEEPDo7bzcLsNjrJLM5kwywdw5uPkTxvQ0QAX2YfIyo4BnW1SpnNzmKS7aAHfpVZ+UN+LS2dqCK111IZWmkzW5eKcoD9Lmiusvlo7X7UGfzJheFTnVHXqCOyhXs0bjIz7qFnyOdlAuyyo1dlCA8N2fM8uO7LzDR0Qd/YIgnoUl1da9ZKVVuQ0PRs6wiYmas9+M7tPjHTwTc9c0DEeRcPWVywf/chAYbDir5gPUCMXTjfNN0CuEN2vWbHBaiprfJVU+OQny1vpInT3ddueg7tsz+HdvmIr/e5898NaxltSchH78FiBgrcwMtlvMker0RbWLbLK0kobHBmy411HtcprgrY6X8XyvVebVpZUWENRnVl7qw33dOZLZcXaf1NS12hdct3t3/aK9fjJz+cmRRZwsnWz9pw8+PRzhUcQvQMj9nv/vNtOdXFe0LmAZjNmRAIEproQ4THd8lBFrfdwcXVx8SarTBB+s9d9C1NnSrRWQX/m1nn2wQ1NVlU+uY7YP9Rvf/38n1l7b6uOO9fxDWKq4gsUWKXFpfbBKz9uV9UstxPf/bq1b33JRnI6HHQsrau3db/7X3RtOKvi07VqzwJ2AQ+TtfAuAKQvJ3394Db78/v+qx04sf9Css5KWvprfXWD/c5n/oNtWn2jlZeWj0/aTkVBdPZ32Pdev8/2nNYpsRKuFxKmwlsXAr9Q2jReZEipDuT8zHW/YCuaVtmRjkN236v/Yh39HMk/c4Hlyg3lDfYzyz5hLfd/zbr37BDwcwV2qXbfz3nHnVYxb5Ed/cE3bbDtdMFC0IdKamrtpfoVhRVEd9+wfe6/v26nuy6scQpizV68pSjwS+9eYD93+zyrr5qcVdw72Gv/18P/q9HxCbDwhSrEEimIT6z/jN1Yd7Ud/pv/Zu3bpCAGz7bIgFlcWW03/um/WnljstEKfIS3koIYGOy3LbtfcgVxsj35EFZSy0v3t1ojh1//6G/aHRvebZXlVdNSEK29Lfb/vfBXU1IQU+GtmaDauXiL7Ndv/W27ct56292yw7764l9be5++TTLDobakxn57zRfs+Ff/0rp2vpYXemlNnc297S6rXrbaDt379zbQPpGC0Chcy61fW3trYQXBCOIrf7/TTnYM5dFHecswc5EMWS7QupwJ5AiQqVg708V9qfB6uadAa4Tw59813z6yqdmqK84+2bIQLQY0gvizp//YTveedhfTmc40eTXBCOLj6z9t62pW2dFv/J21vbLZRuWeOyuIb8o0crj2P/2plda+xUcQB7bZ//PAn9mJ1uMXv4+eRXS6q76MWFFtX/r4b9qNa2+ekRHEt7f+i+06lWwKzEE3wSN+gDA+Js9bEwCc5KvAm+BEfJUWl9kv3PBFW9ksodx+wP7l5X+wjr7EQJok0PMmg+7NFY32+RWftZPqE9173sibp0Tfv5n/ng/7COLwd75qg3JHFQyCWVpday8vXFdYQQwOaoPKLn3KsFSfqpRv8eKF/HMQDN8GNS8yrCNuS0uKrHzGfYyXZg4C5YCPuLz88piDGO7tduVcIiu8UID5F1QOWUONvukwiQPUgMPOX3yw/UN97mIKv/iFuPMYTs+tmW8VozraubPdBjRMHhXcs4IKV1xeYdWLV/g1/e6tNILgSIpjrS22efszdqTloPpEuqape0lLfOPJt6JT8TN8SzvWVzfabdd+yObq+y9V5cn3R6a6EGFgeMBHmx197W5QFCouLsZRze8UsX9ELh0My0K8NaJNhPB3SWWVfPLlhUBOKR71wHdW6Mecwj04pC90anC7as5ia5RwHhoetBPdxzXf2H8W/CElxltTVVE8JZmGgqjQHER9Ua0VdbTZcHeBOQj1CUYRJZLlA20tNtzfe1Y54gH5qikcG5Ex9sNnJpiDGNYhWa/+0f9sS//N/2SN190S+Wf9CqF904cqlLbmGdG8uLfTHtnaZl/+wGKbV3/maOGZKFQIaiYSL8bKgyjzpRJa4Hda92mNvFZdxHzAkYe+aT37d9qaL/3HKGLe69Hvfd0GTx2zK77wO3nf54tk7oH6emcaX5s/uTmMgIeSwEYDjrQDlTg7uAEnccWmKzRZKlwqWoPXeVp0TvN0qmgXfNs/qO/H7+mwVw502qffMccaqtlglweM6MOBd7TxTOHOg8XbpHdg1P63fz1gX3zPItu0MvnW+1QVhHMJvEI7TxD6xYN7/v6/2sIP3GP1V290o6DQ5Pig3Cq7/vqPbdmnPm+1q9dPAPXCX7HwggUfrGDq6R+x7//0tP3lj47a//0zV9jtaxuc50mTW53W7iH74wcO2ufvWGDrlhQ2ys5XokEWm2gi2nkgH8l44T9BQoPlFmQMQVf/sD36WpsdaR20kiNPFB5BDOvTjVv/z9+wZZ/9FWu+4bbzlW/G3hfqTD0q+LO7Ou3Hr7bZb9292BY0zrAFIILBWG/3Za6HvvtVKYhdduVX/o8J2/Tgt/7O+k8dtzW/9nsTpsv38lIK6kLCI185ZyquEE9PBz4K4tndnfbq/m6fC2qqGRMOOUBnA3cOivHHbvXR3/qH3fbrdy2ym1bXTmsOYhzoeW76Thyx3X/1x7bo7k9bwzU3uu+8UBsPaMS188//0JZ/7les7sprzwP5wl6n6dzVN2LffbHF/vzhI/Zf/u0qe/fVZ7s605BbOgftD791wH7lvQvt2uXnP848nTfu07inawR09A7bw6+22pHT+ibPsQkUxIi+Gf3Gf/tPtuRjP2sN6zZFWWb9WqiyfeoQW/Z12U92dNgX3rPQmmsnNzk62QKDN1MQZscffcB6Du21VZ//yoSkO/aj+6y/5YSt+JlfmzBdvpdB66lal/lgTibuUuKd6REELoxXD3bbtkPd9smb5lhdZf4RRKH+NBl6XWga+ugfSdj97G3z7Jql1RdFQQy0nrL9X/sf8q9/xOrWXqOlzYVXTw3JLbnvn//SlUnNirUXWr0J06fp3Cs6YMh+7ZmT9rsfXWo3rCp8hlRHz7D9hRTJZ26Za2sWVk2Io9DLNO7pKggM8ae2d9jx9gEb2vdo4RHEqI4waNuz3WoXLrGy2vpCZZvx+EKVZQ6iW0O39p4hW9BQbmWah5jJAN5MQWiLgfz6I3JJVC5YPCF58WOOagURy+YuNAStMwVxoZQ7kx4vAb5rhBHGUqH9KIX60xlIM3fHJzAPtPS7+7day58LWfIzh1HeEvEgbqbyhmZfvYYbpRBe5ir6Th6x8qZ5Pg8xk+VI05m2QU4dbR2w5XMrra6q0ASRjr/QvOqRtgGbV1c26SXjueVO456ugmAOolOjCEaoj/7gOxMoCAnMPu1Yraiq1ATXzFrruRVMPxeqLG41Fcl9kvjLcafNZABvpiBEX441wF2pj9VMFCabLh+MoHWmIPJRZ/JxdObErVy4PxTqT5PHMvmU9FHK5P1TXvdCgnryECeRcmxeywWjiIGMKIhXL+HbIha4aC5rJkOazjQKypLJ3lKh4YNOhQLldZopzQTJCmX3+DTu6SoIykM7AvPe7ItyZ+gOQTIFcYYes3kXtM4UxGxSOYE9k8LjQkr7dmtjDhREfpzvwLwLoeFk085WG0/6uO/paqXJVpR0l5KxxpfHzfTwZAICRH1hrIsdgrFYucVSyPTBd7Q5loSbZOMFy7WESBFxnjrJ4/mSV/E2SZekcQtL5lWVRqdeBnUsMkL28+MdwwlgBzcGMx7BrXAGb/LMX1JSz1otzY26YzGN482BdwZK4KCAAIpnoKaKMfY6iT2TFvconxfls6EIEvhs/IRSkJ8FLh4cUQLKh3RpREm0p+QPSccvZ/DyCmueuqKMwZm/jcmdAsRj3uAYzy6u0o2hH7tL0kBU3NQcB87OXMrgONzil2lNvT1ncvVHxUw3gAVas2qLY1gQ2l6is9osaAQ23sazpzxTv+RxvIKUlBDR3I/HKXJIe3Lq6+u9rrQxR36cCZGLHKl7f4xnvTlzO541aOOv+KM8gZc7RijQt0xtDM1p4/G0aVzjENM3CcKz8I7Bv//++wu7mADBkbsze35MumCF7+NME1K4wDir9IXzTfcNHYglrqEQE6aeLtTz50/jvVg4o1TQms9Efv1fvqHPPh53zkNZ8aF4ysVvGLqwvHSMERE6UWYvr56BAy+y38HjEr6z0rJSh1WsYf3w0LCE45CvFd9w7bX6nOUttmXLy/bU08+oOBJiwssqMserzjWkdeucmwRuAnCBX6o4hA4ft6ETIoAJPCehyM/iSfImim5Qa9QpwxKdM/PZT99je/fts3u/9R0lT4Qnh5QxkUzbU1+C84KnEGzhIT9x1JWO6C4e0YR6ec9Wx+I9eIHD5z75EBDp6Uf//nd+W1+AO2IPfu/7/slO0vFBnQEJE2gKXupI52eZ7qjcFJQveBK3Bf/8Y0NKlQh70iQ9Ojm/DPzFqsuA4+XTnx+++0P6GtsCu+++++2ovhMNPBQGbZzQNGlnb2OVAxyUP93G7HOgPQhe//E+ST3VRoLFXgvKNKQ25vsWmzZttDvf/S5BM33XoS/J26WvznW1WJ987qXF9aaSKK++QaB296XJnmp6f0pV/2Ydhf6yPkj0w4d/7AoZWlJueJh6q4JO36ij86fe80x7siT1TIC+COKEXvEOGoV8BGap6vCVf/dlO3jokD3+5JN2Ql/Voy2hMTxKen6K8GenmfLRnqQZRLCP8d4Z3OIp398hHoTPCF6WBDcw+ODS++680z/m9L2HfmDH9LU/4Hg/Ur1BSTovg0pE+9LW4CTQZmedaaYMZdrPUadvjBdJw47SKfMFPtYB4GB4r1y+hDMY58Qcq1y6AWYQRV5QgZcrv0I0yZt5GpHgImBxBMOMM9I04E4mK7hhDj4V+Wd/+f9aiz5NCfNWlomZdKjeiDYTiZXEwOY8gGCGyWAuF3oS/jA3who4dL4hOleKycuUBsFBBxqSkB3Q6jhoe4OOFP7A++5SR3rKnnjqaS8um7KLRsWsvgCBjVYjrkzA61wu3KFgoBGbxRDcTkG9o0wevAPpAzPsaZGwQDgg/KHvihXL7Rd//uds+46d9s//8q+eXNXV0RDF1jXQow5fpbormk7i68oT/z6wEWQuSOjIwgd+YA5pUymCnMB7Fxp6pJ496kMoLmD9wf/+e7Zfn/T8xje/ZR2dnarriEYzlfpedJs6ZK0NjyI8EnzAoIMjYKBvCDDqHbzCNQICvFxCOXgX4462QFB/+p5P6hOmy+wv/sdf2amWFodZWa621Kat0ZKypI0lD+nntJO38VhbUocoP8oZGtDW423sdEr2DpEfJYLVzqFvt7zjJrv7gx9QudhoS03MWvbtssOvPWXFa3s1uf5exTQ4vZIvz2GcJZREyCfCl5rlBEVAByiezHecnQIBjtJ6fvML9t0Hv++HfyIU3UhQ+cvKMDoSuN6uwlVCvVR2eAW6Jq15Bi8YaANomryU4BWt4G+eS4QPWv3e7/4vbnz88MeP2pGjx7ztUPwodyz9Mk1KeGnFP9CEfMG3KPWQB2cwax5DdIUX4DfyUndvK/hbMKtlZHzsox+2pYuX2Fe/9nU7cbLF0zte9Q+Cb8QVXeBXYHFOlBtUggVdQvmT1nlJBsSihfPOryAoDA0F4IsRIBAEoxIELJ2LES4V3qgbHRoaX0xag5vOjCHQ26erysDO5N5DO+zIgaeteNVynbW0QXFlbuHhmoEfPPgl1XkjPnk79jfpBEnSsXx6gxCqlOCCaTlBt7sn2dXZe3S/te141lpX6lvKNfqUpU6opHPU6OAw8owH4RqHlhdvkjLwcqUkCAU6eaO+8Uxn7OzSrnEFzqXp2PyQnVoxrBM4b9NEZp23RWNDo+fxRPxJ4x179ndJNcduJTj4N5aWVwTatrGhzq1T6ktnH9Jqsc6tT9qRkZ22aO0HbHiwXvlK3AWGu2C8lmOVTS5jDwlYerMH+Jf6RRkdr/4gSGpr9YlTtSunI6CwEO49B97QiPEFK9W3pxuq1ikfKwP1FTS5/cbbeAwHKBCGjgocZ4Wk8rnvGMVUarNrTU2VhKBOEh1b6LL1UKtt3rXdPjX3CRuZ/0kbsibnecrPD9xcE4MiGXm5UEM4jtWP5+IyCTZp8oaqehfuUSQUQWK0mL7U12nHTp627kFGC9rIphVD0BQ6LWxUmcbgRV4hdvqd/cxTUiYUR7/6SInkIXSlnFjafdpsijHCaK25scFHAqdb261VS1gZZWIYDY1IOcjwadTpA6zwohxJG/nfPHjH6Cwc8Ap9k3IzSqF+9J1BKSugoFwb6sVbiu/r1+qpUzrVVYYGo2tsF5QIZ6bVV2kgQJ0JSbON3acfkijyPPHEY5evgqCyMESmIJIGm42/MDgKwq0bMTwdAabavXuH7dj2HVu/TmctNb5XzF3l7eAWnfJER3UrBsGBhSuG0qvxAB+OFCUWS0WpdvHqXzrEERnkYYRB2LfvoL5T/LRdveINq5v3KRu2eS48sMwc5xgfozTAjaAgnnpQdgJ4R6VLRnS8eFWZjlRQ2dIBQami6j0dJ8F7QkLk6ScetzXLXrC5Sz6l/EscUKVcXgnA5AK+BG/iQjqDm/eqoQTAwHC/4+XcqNwwouMWgIcSIH13T5+9uPknNtj9Pdtw/edsaHSR6lLiQhphkNRK6VVgBBB5KEMSxurs8DQCHR30Y6WxDHMDR0wgEZLViLTxsO3avs327njQrrx6oVXW3yEhVCGlXZUIa6WGRpQ16OxCOU8bD6uNEeaF2lggVCcs5aRcPVLMXfoWQflot5VV1MnqrnQBCPOAK3FRJoqN72uXSrlQFNwvXCvkCvR2132+kFYQjC6lE8Tjyeh8SG0PfYAD/0FPLHiMUWDiGqMM7koVg4SRipsUGPB/qdKnA3w91hIOL9qYOvfKAEAxMiLz+im/u4lkyePGZSQGXtqDQpE2RmbgpmykoUzj7UrhFSi7I/YH3QKDCPE7Cos+BY8D2+effHTEiLw8wafU4GZETr3ARz2ZvwEffep7Dz4wOQVBBn4XI1AwKsUPImQKYvaoHgqCK8LD/fq6P9nRZa2n99lSbX8prlomr4f81WI8Op8HtcsYnzojJQoCYQ2fxxsJYf3jEcs0HQ+cYGiYGCFAaOnq1Q7OFltZcdBKatdIUGvEwr8xkPADZR2HpfhEWESaJOFokXhICqK8RP7tlIIYFx7CBRwsW0KnDs3ZdbLdlpfttMraK3SuD5Z8goUaK2lSt8DPO/0ITjvhcAtbcUMS1NS3RErg7CBLUGv2SYdigmaMnvafbrfy3h02t3m58DZ6fOQbr+8YPlwnodRCqHk5Ha/cWGpDXCW5gclTxFi6jY+1dVpn215bUq/yqI01VvdKJtVS3cYq7c/UX+VWpNcXnNCVq3qqX89pY+VHGYf4RMkTsMIHJTipC0KvRPGjCVKHQ71wjXjdsfildPnn7aUyoGi8zg7t3D+kQ7EQEp97MmldrHxwjo+y9M7bzduCulL1ZM4F5eD4xupKOsl6jQbaRHt9e0HfduDsI4wU8ignqMaCDK4xVyavML6cx3gQDniV42a4xzWFYeb1JE4BmsKjBPDiPG3v7dBfCXa5e2vKalwZn8GbpCU9gj2pU4lcRrhTxQfCR1wi8JNjPsLII4+Qu0KibXGtUT8UMWXkLKyH5J477xwEhckUhJNzVv9cChcTzAMTO2O5gkiYfUCCjE8+lohhqnSwGVYYViKdB6sMnqATw2wDsgixdJhkpPNGBzwfsRgm01Ng8XA/MFnb1d0l5TGgYTPHDiTWMnARcggUzrtB2GDtESo0rKcck8WbGB5JBwy8g6oTnzEdGtSZRWU6l0rwUFp+vo0sPYQatPAOLqFHKJOPHevO75V+MoZMWkEgDIZEwy7tNert6VLd5MaTr95pOlZHaOTCwa0+CU4JPkY1bkDJvYAbqkI/OnqEM3dJDPQd9hNvERQSri40zHpkZba1dwgvfnThljsIXsCKpFFG5b7BTeRGg8rpLj4Bp86UEbzQY6KQtDFySiMg4SAgyHBzEaiLKx7KL14kINR75IJj7iQWFoAPxVHpcxlJOngwEZSezf/wDC9TRq78MEBwJ7piGYNPecDHogiv1xkQ59yBjaPqf7zzMRfg6xdcbQ3FdVZTVe10IIPTXLiBS39wuog21CNkJyNzcDFCgnZRnnMQpiIY8Tyz/3k/AbapqtE2zFnnfIly8fqPpYWGTDJ7P5ZiAC90hOaJEkoS0p60c0K7FCLdJvTSwiQpuF4ds3S874Q9/+gzmYIIMkEgOh6MSJhMh4+8M3G91AoCZooOh0ChPCKJ+zz9oLexSkInhttunalT9PT2uA+U1wg9mF/RBUN04qA1z+PCQ3gHhNetY4SQBEKUCYAopW7cExLOPuksK47JSMpShethAsRpvElnSFYvAbdvYMjaO7u9k/Dsq7jGRhc8q/voxNA+LyeKiY4LPgRRlcpIxysUovzgjIlyBB6Cl/q0dnQ7rcEJz/kGxTH6YdWVaNIeIU4dWQlUW1sj+iRfxZvoK4RBCmWTgkuMAOicWLHyz3fLCOhK/NooWdoujGHQlxWFUixKyqc0iZWpNpbwLtTE6fqGAiUu2hgjgDkvgrez8PoHpEQL2g/XCKuIcOWwRBZmQuiCkIn+np5u76esOuMX+IDHfbofU/dhwR8cSvo0MBgdpAP0oC2UNW/wdpNFfrKjxd18zfrwDjuysW9QCKXAdBiJtwNB7TTWCHJgArwYW9A8Xf50AVBMGAQnWk963WtlqNXA92OGUZnyo3BI531XV8pK4tNtHRpzJLTEqEri/ZVeo9zLvN9EpWkHjCHMNQxAeA1X6eM/ejhTECKbhzRjEfF2UxDUF8aGDjAIjJL0pcRSEz+eE0jLD2b3AH8qwHDceocXzAh0hujECA/S8Ry05hmhmQTSJjAiP1fwnRPG0FPmpDy4cdT5xhKm8YZi4lXgBSdHyStzkkOIXdkIADCS3xm8dCQXNLxQtNcXOihf2hUbeAFKudIKwoWIFAATmOCPtNTZYQdeucucEMrvdUsSOF6esRB58NEB5RkLwCeQJkaJ422sePI5Xmo3ni+54W+gCRg8BwXGk/MyJ0Q9wBuWPHGhIBitIbD12o8HwXOdhkfa8XoKdjxDc+I7uyX8hqCQXCIa7VVoRZLPFaXSRhtDGo2DfDREHEhRKml65hTfH13gp144X0pYUxaMgZ7efk1465htWfINmvwlnh8hDL1RWfKcTcWSON6EcvYHlYP654Y0HN5R51Cy8COKs1sr+3Bv1VezTDjBC1185ZWuuBF7NFHtc3zQWGUsZTu3ENM/fC6H8jpyURH+0kgdfikbM3Sc1iLeA/fflykIp5P+QORgLOJCeMT72b4GY2FdRueebZzUOVd4oBy65JPv6k3W5lMGOjGHwUWAucjrHc15TwJKHYI4rEwsXYbUdFwEF9ZgDOXpBKTLpyC6+wfdsqXzJ/2nyI+xdlEnpAlTJx3HH/QCvC4uKLfcNcMSPoxicAFheYfADrz52rhPVllbV2Jle91Uv9pKrShDyQTSqDzXYgQyCshLpvr2qIPiiy/2kQV1DRdCCA6y5bYxyqFN8y5DCEy9B1elBB5Cz9F6RxYt/CUJ9H+sztBwWOXuYX+B3mNtwze4+xAmgZd0uW1MWWhnnxAdr+C5FeVVOpZyRDmBMdZIfht/PI/+RBtzpSyhILrFW21d/VJQKrO+gZB8cCoZzUWZIQR4gQVO5pMIxPWJ1sh6zY1bmVZcJQsWknYgv9NlzBOAgugf0TuN/rBhaDO+m+1wBctdeNAB4KkAz7iBoDjeATNxl6lcAsQqNOhXzShGo6kIpMMI8HqoLdp7tHCgJBHo4wpCid2gEAFy8Xr7KV8E3vseG6WEt3D99mk0Rb9ik+l4GYU32hgFQd/V9LbThlVW3gcEDCUS7j1lERHlcpIC8WXoog2jcWjMO1ZOfee+TEFEW5zFWES+XRUEVmWr3C09Pcm+AbpIuZRWaYm4RpwTygsGYoiNa6RMH4eBgUnLKo6ubgk9WU/lYtYSMSguiQoJ7BAWXPMpiA51PFwuvvxQ0ICPwAQvnRrc4xaVGLqyutInZuFqhF1HZ5vwMuzXTytyKlQulAUhV3gQF23cJYsQFxOdHlwotHJNjoKfydQQXMmoSvMy1cmGPuIxwDp7WuUakwDSbGax11XLRXF/jOEFFyFXQQzIIjyl5ZDAxaKng6LUigU06AwOXA1cfc5B9A4ZMqCPvnRpJVSxhCAKqbxCtFZ9EQhRZmgdwmN8BDEWF6t7ijSZj7BgTgSpxQe5+JBNuSxP6IALiPL0DWhKWu9ZrqnLuDDxyukPopYPeVGHaONo81AQ/QO9ErDiLbk6mNdRtTwfrpCkzEAWDuohnOQfF+F6xcQ+ARWCdUx+6EwgP+nDCEBB9A4LxmC38zD7BdJ0IR3pI8R9rpFG/Bm+Y2URCitpIxRUlICy4MN3wa2yt3b2u4Kok1sw2hNcUT7uI4CDdnNhHpG6ItQJUTfvd0JEGcdeOC2YowNGkVbPseiiSC4i8AbdeQcfdI0t7YY2lIll30Wa/6qpYpkzdVHNlJbVTd/5TqYgnCBBlHTDhfAYTzDLN7nCY5bROfhgGq5p4YGgwLqlE8KYflKoD0VTpRrrV7yPTkenwTryzXKCSUdxSzqVZpzR87iYwJus7U46RCJAkCAJssgbpUjjdqbm62KScQgM1otHuSIdafK1ceJiGnNPKbdPSKvMLv0DGdc8dUZ4MWczzIobJWGCOz1aijKQPbeNUUjQapj66T+C2ecCAhGZKEeq/mfgURfNh9BOKCbv7CgGMiWdn2u+No54rklIMoUgpt2TyurqtyjoRPhCgtRbzx5xASspciJUwU/eEFS9UjI9OoUW5VujEQR1PicofZ5YJ8PJNo0gUGRKgRKu0oa/Su2JoLxRxmhjhGDfmIKokmLFmImQywsRz3VCBYHgb+90pUmfqdHX4sBLnSlFsoqJB6XTHE9tpeZSMBZIoDAR3lwFQdpQTOTt1EKK1tbTqneVVWtPEnxXpFVVlSpDaVHiQgsFUVo0ZHVKE4oJWCgbRj/Qh0CZtObOGmsFQ8ZUBNIysX///d/NXExpogRjEfd2VhDtPQNu/bDJp0YM3lzHRCyW/Bi14HXdw/PsN0CwJOyvaOXpkoWI4GO1BZ0Y3yZClxCdON8Iokub9U62Jy6TMs3+NdeymU7W7ZgEcrRjeIVZVm4yISh+9mW47VqeS5mFxSdzsWTjGOzAm6+NmaQ+2d7r8xDgmFtfrslnrOqolYowhpeYoRF1TKxf3csG8xETli2jACaRGba7cqMkKRi5CgLhfqq9Rz5jrD/TJqpyq9fO6nQe6kIokoJmcjoIDfYBlRsXEzVm5IAATEYgZ/DS2XNHEA5wlv+EcONKfUJBdIq32jqTYzcQnpK3HryW+pPQOaGtj0zlzotK407s6mXOJjE+RqQYq7TprKo8aHTuCGJAo6tRnQpQKfqk+zTlghe45gZ4PQQr7yIt98xPdPcxooMtkzmJmAvgmlYQHbLka1Q+X601xgfAwojilxvAmTuCoJ+Qh5Hs6ZOHbceOXVI4Gh3X1NuQRgQVNXNs7uKlmgsRNKXDxdTdh4tpWMpL+4DGCAwMRgV9WmKcjIASHhnSSK5eI2LmUyKQFp757nczBRE0GWeCaLg0M40nmsWbXOExi6jGQQcjcKW+MBNWLXMQpzq0ckRCGMuiRvMPw3Q0YvQe4c0Ha5j7wlcvlvd/WLI6u8FOd7N1n3kJlsMW2Rydu1MtSw88IajzKQgY+0SHhIcEQLlcSwg73EQgxuWBa2MAuEKTxovVzb4DygxeJfWOVicBVKdJRDpO4M2nIFgZckKKiclTjhnB+quWu6ZEiDgXv1xxXBFD4C2WA5z1/8XCCe5unZ/fKasY3NWiFQqxkU+AKn1a2Oe2Me6CVvnjOyQ0yYNLp1J4VVz/+eS/7rGYeYcwHNKqJmCimGiDDn13oE/zHxWiL2WZW4drBosW7MC5vBREryZQ28QftCW8pOp5WRGu0A+ioQAq5G6rluAv9WVCiSLmHSuaEj6idskcESNVrH6EN++ijeGDQfGjDWv5Mi44jI2xQDr6OtcICVyE/hkXHe8CJjTl1yEFAU6ywqeVahs9el362aSmukhSiy80X6DRTa6CCNyBN3DkUxChxEZliLW0HLA9uw55XZvnzNFRGEt9ot73WshYAq5PUstwKBaf1MilllYQKAaWicN3BMqsxbfq31KeuQpCo437MwXhdPI/wQSZgtAks/yoWBveUUSdxBefKAd6RZxJA4Pht0yWPepBgU7P98Pp5AgqnsOXHR0MWudTEH1iyl51MFw0SQcUrjELiHaJ1VLJmvKkI1ekXEk9wosiQYHIOBdezZHol8YbwoOyhsDoF94+dRzsVmoROEmD/538KrILFOZHmOx0hQoBFFAe/RLWBL1S2WXRS6lQB/JGyFUQCH7mP4oArkC9SU98lAP81BdDm3ZgcjJgsiII3IxcoDVQUCLgjTpA68tpBMFIC1dHMr+QzG2oQi5dKSsV97ZXXZJdxaqLiJr4+s327HxDyaWcNZqqqGlU3eTn156E+fqwGXVO92MUxMCIhP1In4+wAoZAe4Cnor9HHNdx/34qEr4hgKNHS62hNQsDwp3oL/XnzCR1ieamBtzFlCxHjhQJH+XDC+xot0gdCoI2Z7ky/ZJRefBupIs2jklqFBPLWYNXKC9LtNnjkB5BjBRzKJ+WWEshR3CeuRAFkeuTC0CzcY0GhjBULpcQs4ETmIE3Gu5i4Y365AqPiJ/NqzM3vcy1AAAw4ElEQVSCBCNXF3hiUAQ6k6dY077WWtI2OmyUhXYhT4SkfzMMllWmfB2dHb6ahw7JMJz0pGFlUXTifAoCvP36IYSxKN0SCyS6IjQJgZmRC8Hh6+3pltPu1kpOBk2sQIR1aQpvPgUxKF4DN1VKu8McOPDHbs7g1Z0iiUelsMmOuicrWpLUg/IP19QkZ0kFnNw2xsU04HMxjI5ww52xRBMo/E2sbAoHfugYoV/KpU+z4+zJiDah/Zjwxd0UcSE8oo0j/2xeoUe4RyhHuJi6ZH23a8UYwlvV9ZEWI7TxRlWh4BFvU9VVYMbrDMwtzz6hlW7dduj112ze0mWu2Fevv8bWX7PJDRrSRBszIOnXHIQN9riwpP7AjRDCN565kt9HI6l0AdPLJHD9vSekWDQKknHiLiGl7R+QW7FEnxaVQnchr9Eco0NGnLi30oI/Fy/wCcBKpyMOGhIozvBQhxRiV7KYQbgJ/UNsIpTLSSMA4IzPQWgVEy4mL5/SgYFFCV1dXf5AXZyjdIXvhjUarpFbtVwjdvo7ZbzvQlYxpQlLwWYrUMnQ7FxziRbliOtMlSOYAHjgBX4Ql7jAF1fiZiqAO1Yr5IMZNJgN3OB1xlJ9CQgYLB/cHhUaOrPeGouZAH46xXgX0w2L9ZiH8M1OWNuDOqrj6CE7fvygziTS9x60qqJCQ9258xdaI596RGkIR3QSYNIhYVb3T3drVY4wcLgYE5gwNvj8ZFYkSgRFDqqTkg945bKE9sm6ZJVMb1ur1c1dqJVFvTZnwUJbuGi5d7zAGyDCUuR8oFb5xRl9NAovJ24SSE+aRNG5qnMacO4RdOKfxIKdPnHCWuQf1njJejp7rLqe7z4M2boNNzte6ggsaI0AgAbEoZiOnda6fum5Bh2kNm7FqW50WqeL8pGWICprdJH4zaFRn1aknDh2UH7lHvV+zYGIctV1tbZk+WqttDozQRlt7G03BssBzuKffLSmLbvEV6zugX4UhVVSFbJ2g0Zc2cVeU4n1qzqLt5KJe/GaFOozTz7uk7PHd++0hjkLtEppyNZcs97Wr99wFm9RNVwpveJHNjnCSUwoQ9PAFTyYS4bobxFPXRDU5JPclxXeqd3vcltJ8OM+oqAlHCcjf35slINr27Sar1g8WldbO64ggRXyLQ2fe/DyS4eQCxhLg6oHAh5+5IA+YJXoCA6UAnN/itB94toaUj/AcKiWkiCQlk2K3V3iFdGVZ444wfXmgbqpcsxl4TZzBfGd831ylBMExagUMjRZAm32/zKMwvKJzUU0TgRvqNRzxM/EFbzUNY46vxh4YRiGq1wZvubSGqYOoTITdQwYQUfgY92GIIE5+BEY3sf5P6T3DjYGAJ9vYvHQ9RXEdMBoP33K2rTaYlRWdJk6UIVcAHU6GbVG3zZPwwpaU18YFqsbJlZfVoBhgem3wot1xUR5IjAd7xmW8HSnjh3WpK0mfWXRV7IvQHnqG5ussXGuOs4ZoR+0pqzQnCWCWPLA1n/hTQBTJurrCoIXCqwZP4v1FN/Z3mYdUkr9mgDsF/5qCYRy8dGiJSu805Ev2ph7+JpnhD0+YUlLkCZ1JYFu0wrCo4LWwifKqJwjfkRHu45qZ+JxUEeUUN8aKac5UsZl2kQW7csV5UCb5vIWsGcrgDe3jZlnYFOiU1N/fCSh+kb1oTykRmnQZNBovD2U+MiBfV432qZcfKWPUjuP1dc3nUmnd7QxdHZ3EAAV4BnnX5ApuJAce+cRY38Snk7HJO3n5VBWXH60H5Y2cUCnvQjgBW7IruChNMxCeIEVdXVg+kPaCChV2pA0KE2MMngz6sSVjXBMlHPFqEornITX1dccILg4ZkX8HAhSV3D94KGHCq9iomAIDbQkgPldrAATM0SmDEGQ2cYduGIYGgLyYuBFUAVjgTfNFIE/l3EifrrXoHVuG6fLUAh3vnjysdoG/7FunZlJx/yBW4K6JwRe7oPWaZzEnxPIK6D58JLWaed4xavg1A+cCHUC8EkTyx2pM3HnwwscTwNMh3T2H4cjWHRYJXTcCIQ464m86TYex0tXTXrr2QDzPOWrcwJHdZDgTMonIThW33T6oDVpyHOxArSmPxHG25iHydRZhIbWqJKgOvfJ5rbkHW1M4BKGB3WkvmFhO0/QJmMhTZeIu9Cr0zqdiQKk2phX4E3TekbwAni8Lgl1Am7QmrIFrUke77kn5JY9932SKvn7HUYQ8p+OonlyA4BgaiyPixnASwNfCrwQdjYs9YnoN5n6RqNO1JgT4Sj0LnCHBVIo3UzHB95L0cbw1tulvrRb0PpS1Jn+lE+2UK6Z5mVgEqgveIO3eL4YIegcCvFi4Y26pWXmTOH2Za6nTp0axXrNF0A0Ww2ZD1/EZXiDEskVS4TRXKF2Ojv1hT1ltL4wek019aWiM+W9VLjz4UWQIVMKKY6p0jedLx/e9PvZur9UeKnPbOD2EQQKolY+07SPbLYImMGdGgUYyXVr5UZdXfKls0uhtKdW8ixXRoGzKRDLQLHwMz4+mzaX29O3v/1tK8oUxOXWLOeWJxREfT2TvMlk2LmpspiMApc/BTIFcfm3UZRwQgXBkIUVAPjU0PbJhNiZSS7exzAxbQlwzztCOj6QZtcLp8D5FAT0Jg1tRFuFEsEXG+1GW0V8ugTTaaNYJQJOJn5jxQTx/BiZ5gbKCs7p4A2Y1A08wAJu+J6pJ7/AMZM4A3d2nRoFJqsgaEt4Or2fI42RNs3Hz+k0k7kHDjxEucAV/SR4KmAED8Vz+hp8lo4rdA/P4mYLeFxj3oJ31JsyTAQzTZuZoEGhsk6oIGicJ554wq655hqbP3++uzgAdOTIEWtoaPAKIACoFCsGSE/FiUOxUMlYLVKoAFn85CgAbXExFRpB8J520WjQrr76aqc9bcG8xeHDh739aJcQ4FzJQxvBnBMx40QlBPauXbu8ndeuXetLGoG5f/9+O336tF1//fXe+VjqCFPDK3QImJq4qeKlTMBhKfKrr75qy5cv9y/gUSfmaXDFhXKifnT+MHQmqk/2bvYpMFkFQdu+8cYbtnHjRi8UQhz+QaYAg/anrafDQwCmn+zdu9f3FwB7xYoVzqs1OuiOd/QVcIE/LYwximKVJ/eTLQdwDh48aG1anrx06VLtFzpuV111leOij9CPly1b5oYeeMFPvcGFnKVMyIKjR48afY734J+NUFBBUIlDhw55R0cZUEAKRqdDKCxevNg7J/F0SghLg1JYOmLE3XDDDWcRdTYq8XaACT0nUhDQfuvWrS6U16xZ48xDW5EPhrviiiu8bbinfZqamuyENnfBWLfddpsriqnQkY712muveX6MiNbWVscDXhQFDA2PENjgQxoUB3V5//vf73wz2Y6VWz74ER6FH+lIdF7qHHTauXOnP9PZKOc73/lO7YdozAWTPV9kCkxWQcAvmzdvtve85z06g6jFXn75ZecXhOru3budnz72sY9NWzgilOk7COCVK1favn37nJeQZZSBON698sor3o/4FC/9h3rwHkWCEoPXJxPIh+Jj9z38evLkSYeBUY2spQ/RX7miRLiHz4FPmVAozc3NrsToOxjws8XXBRUEAufFF1/0Tk5HpKCbNm3y+lNYCkQF6PxLlixxKw5CQjg6I/FUCAWBoMjC9ChwPgXR2dnp7YWQJC0MtGjRIheWMPzChQu9vbCsgzFRFAjrW2+9dcptxEiB9oZxKQNtDlzKQcehM8ybN08nUO7wMlGu7du3Oz7wkmY6CmLbtm1eH+oBH6IM4Et4lg69YMECN2bofOvXr590J55ea2W5J6LAhSiIZ5991m655Ra3uMmHXGLkiQGLoLz77rtdaUyE73zv4BWENDwETIwmRqTE8UNAh8GMYYw8g49RKhjG8Pt111036RWG1IP+QD3AR3+kD+AdINCXkKXwNvekC+WFhwDDDqWAkmEEhXKKvOer64W+L6ggYggDQY4dO+YEQknQAakgPwiFMKKCCAEKS4fHSkVY8MzQiUpOVQhcaIXequnPpyAQgAw5sUBgIKwiGB8BeeDAAW8fGJl3CG8YnI6ABcSIg7ipBHiDEQR4gYmlB7PSceAP4COw586d68+kY3gNT+AK4zrVQB2xJOfoVEusOoQHHRvehSfBDXx4mA6GkpytofhU6/B2zIfsQB7QFhPJBdoTJQ9vYngif7iHp+F1fh/84AdnREEgsxiJYviCA2WBgQUfwU+MfOlj4MZApi+F9Y/sY1QzWUOYvoxioJ/QN1BCwAI+OOmjGDvUF4OLNFyJBxe8DE8TT1mRs5RrNkJBBcGQnVEABeDKj8aMBuV9biAtgbQE0hIXeTwy+zMlCpxPQUR7BXCeg+6596SJd9FGke9Cr7Q1gjrgpWFzn+aTdBreTUc5BOzgtXjOx4OBl2vckz4Ll4YCk1UQtG26faO0CMjXX3/dFQOjwskK5sife4VH+YEL/gieTd+TJ3iH9+l33F+InAt85Is6cs+PEPD9YexPukzpeO4Df278TDwXVBAzATyDMXMUQEFgJTPcDCE4c9DzQ4LxGAlcLHz5S5HFvtUogEWOUTFVwc7IGMGK0MSSD8FaiE68Z7SCQXK+tIVgvF3jXUFoiJNtlLvMOQAFgauIYfDFYnIUw8033+yd8DInT1a8NxEFcEviGgxLeraLjmJgHgH3zHRHrbNd1ssNvh/3rQYbxTK9WILnciPCm6E8WFx79uzxiasYbs52uVEQ+Hjxf2Yho8BMUAAZg5HDiiQmfi8GLzNSYRIZv/5URy0zUfc3Gwz6/wMPPJDspM4UxOXdfAypmcQiXIxOBR46M8ohs7qgRhZmggLwFJPPjIgvVkDQ4YpCOWRG8OSpDt38LCb5tkens9wwH8oQYukGIW5Ec9vikbHje2OiO5mcIb5Q4POVyu55+UIYgWcgRD6eeZfGOUI+peFjGxOAB9xZwT8GkwMP+MQT+LzjhQTKz48P4Pi/C8vuPlssrsyavxCqZ2kvRwqEcsgE9uXYOmeX6d57702O+w4FgRAPAZu+j2zpONLxnI6Le4QZDIAWioBobekc0sfgtYtWnxjkoyH+zWIJTj63GBIc2UnacRmqm0On+vWFKb6hVaTvpyYfuOjSR+L59jEfhx9WOXr6R6ypRh/ISAnvkx36mlOVvpksfEQDFxkfOPSYuk+w8m5AHwRp7RrSB+A5tiJJRb6jrf02R3HAS2L94nDjOYFydnxLpza6qbzz6susSvXnQyMXEnAxZQriQiiWpb1cKQAfIzsmqyCQKbkhZFRuPCNtQlru5KbJfQ74+WBO5l3AS+efKF+k5zpROt7xC7jp+3QccOKZ+0IhcPGe9OnndJ6IJ803v/nNREEwBGOVDG4M1hwjkHhmjXmsOiAN2p93rLsFAGvNec8qAQBzz1piNtmxBI0Gw33l64tLyu3FvVqbLtm4oLHcth7qtsW6Hmjpt43La9zCBiZCHsHeIMHP91xLlOHHW7UHo1krapQXBVCmuAp9nP3oaX0aU4J3cVO57TvZZ9curXEFVK5PRvb0D9u2wz1WX1lqS+eUW3NtmT5BOGoHpWyaavUdV5UXhbWwscy6pVzae/SREcGs1k86S8pgwJULwpy0c5S/Vx+ILyvlU4JD1qxyDvChepUJuErmyiopuz4dqHKeaB+0uVIKrx/p8TLvONprH9nU7Ioi3Sjnu88UxPkolL1/s1AgV0EgN+IXdeBZ3cn7HUepxAY19gbEngNcn6xoQmaEQmBugy/tXXfdxvE4ZFC4SYFLiCv3zz/3nG3QHAVyCqVFX+M9MNmYxj4D3vFMPD/gIfvYKMrmU+RhrJRCRh7UgpJmyU72/bjSIo9gU17gRHn4GiBffkPmRpmoD4G9ZMAiP7jYk4EcBReymPh92gTLfgrkNHAjL3VIP5MW3OwdYTDQqHLxOVb2dICbfRghY1hE0NPTbfPmzrPHHn88URAAhrgUIDZkxLEMuDUAygYnAvcrV670tBxnwFJIdtOySgBkKBY2f3CmCQqDdc/r1q2zxuZ59uwufX9XbXRMlnh1RYktbaqw/S19fj0uK5vPDBL30r4uu2pRtYSyPvcoQbvzRK/VKf2prkEJ8RIX0Asayu1Y24A+tF3sima3FES7BPe1S6utWVb+a1IOfRphYLXj3tm0otZOa1Rw4HS/C2vGACiaI4KBMJ8rXMelmPqkBK5ZUmNdUjDbJdhRWPPqy62zV2cbSSFUSEGgvMqUh/IsUXl3q3wol1VzK+35vZ22VMoM91aNynxI+EiP4qFMH72+2Sj7hYRovMzFdCFUy9JejhTIVRAIwcOHD2luos8/lVklgcwnM3v1PXGUATvvTxw/Yddpx3CnhCT7IEgzZ06zbzijn7HpslpyilU3a3Q+ESIWgVgvQdgjGXT1uqslhwZst84Nq2+o93dtbRjAzbb5+c22cdNG62jvsDoJWwzCYQlUhPsTTzxuN954k+NE8VRV6sw5wb36as5OGrZXfvpTmyu51yvDmjxsxuyWMD+kiXi+VT1HG0STOZcBycsmLy8bSpGfhJdeekmf5tXRGquv8I1xtbV1rox69dnalpbTrrBIj3w+JZm6QPCpz5C+w11WVu7ydVx5CX+laMCmVN8cqyub6UplvJeVlfo9ZRgcHLDBAZ2bJxjHjx23puYml+nQv7MzUUKUGZr++Mc/ThQE2uunqiw7UWNnLBoIbUIBKQQKAgVAo6EUKDRKBG24T5qMvFQcrYqm5x4FgeZjR/WceQvs1f3d7ip69WC3C0kE6V4Jdj4U3yJhWy4h2qT7do0K+O7rmgVVdlQCHAF7vGPAhXJbj0Y3svaXzKmQ0B6W1V5iCyVwserfONpjq+ZV+gfvXxEOhPk1S6rtkEYDKyS8qSf5+4dkVRQV2QIpj80S6OQH195Tfe5GWq17FAjKAwXB+53Hex0fH1lfLyXUImXTL4WxZkGlvby/y91mC6RIWnq0U7JtUGUocZib93R6mZqlgF450G13XdPo9XEOmeSfTEFMklBZssueArkKAoH/8stbJLx3+3fDkRvsyD8gOXL7Hbf7prhyCcMrr7zSlQVjgPkSyi0tp2Q9N7iMYQSwVKuUfvCDH9hC7UY+deqky6ijR4/ZmtWrXbkwGnjooe/bqlWr/JiKClnhTZJtCNE6CeZTgsfnWpdJjiGvsKoffvhhWy3hTZmRHYcPH7FNUlQoHORjUpdi+/GPfmTHjh+zDRuuk6E81w3mBQsW+nLeYSmUtvY2F9ws7123br2nQS6Sn4Blj6JEzjZJiCNfS0pLvAy8R+4uWbzEP537vQcf9FHEEeWZO3eOf24Wox54KEToh4Bv0zfSkcPIDo7j6Ozo9FHO0WNHXVb3dPd4+VC8fEudduAb1XGuFKMUX+YqwD7uYijH6ABBT2IIxzNDHSrCEIpMaGaO3ED4844RAwHNRSFpSArMcI33WL0MEUtKy+TX1yFuErCtEua4gdD+zEUca9fHxWWxI2BXSMBvO9RjS5oTK3v3iT67QnEoiisXVtmWA136sP2IrVusoZ9GE/j35zdorkBlaJfCYH4Dt9V8CX/CMbl5lsvFNChh3iBhv/d4n1xc2jijEQDKCZiMMGorNWyUYsLt1DswbCvnV1pv/4iVCx6jgw6NIKSkhbPYdh3rNZQIkw+NNSW2RzAh4lIprddVdlxowNsul9K6xVVirsQ1hhJEcTCPcSEhUxAXQq0s7eVMgUSonpmD4Hn37l1u9SJfGhsa/YiNRglFLPX9+/arvw9I8K2yw3KJIJMQ7F2SLQjcPgm39773vZJbK+wnP/mJH4uxb99et5IR8suWL5M3Y6Ufn3Hw4AEJzXaNFtpdJiFQX9dRMfN0lAYGb2VlhadDltXX1dtLW7a4POzu7rL+Pp0IXFHu3pCFCxe5gkBpINgfuP9+H33gRSmVwTygOGTe5s3PuwFdXS0Xuiz2igoZqRIiCGXKhgcGRYjlX6NREfAQ7i066oPRSJEaslzKAjfSaik6wn36iA+ymJfgw1vT3DzHRycNGh2xHP7nf/4XfIlqfX2du6c2XrfRejQqQUidlPJkNIXyQDDddvvtPrXAyOe0ZD6HWlI25PsDD9yfjCDQWPi5EOY0EgVFKEE0FABuIkYSjBaIQ/jj10IhAIh3MelE4ckLDH5oWn5F+qEQIuiVV5I4Jpjx8+OSQYEgzBHgJEEwI/Tx8+OWOiyXDc9Y9swHAId0Su4BIc6kNa4pYPIeWBCUe/Axr6ERo79PVhYlMACAwqI89WOT4QnUM3+pAmUhXzK3XqS5CI4l0RS6cAyp7LwjAKtcZYRm0IKyiQxJec6APO9dpiDOS6IswZuEArkKAjmCUIx4qoFMQaAmLhE+IzCoPqNjt/UufP3IpGeeeUZ9uVgH+r3TrWwMUmQUvn3kTU1NteSSFoZIrmHYIkwR4BWy1JFduJHAi+xql5UPPvoaygelATz6LrhIU6V85MXSJ55Avz4mq3xwUC4tCXLwIEMpJzKVvCStlHsKeK7gpPwoZ5/caMflPiMfdUYwAzdkL3PCITvYx8H9T55+2uc3muUaYmTFXAJwmSvBe4OL7MabbvK6ggNY465plbW9o922bd3m5b5KbiTmNKiD11vlZT6DsicjrocSBREF8xpnfy47CtB4MPJ4Q192JcwKlFFgchQIRRAG5eRynZuKPoGQBw4CG6H2VgshuFEM/M64tZLjQ3Lri+eHPIXkBO9QYLGnCsUE3NwQk9p+1IaI7Edt5EuYmzF7vjQUyBTEpaF7hnXmKTBTCmLmS5ZBzKXAN77xjWwEkUuUy/E5UxCXY6tkZZoKBTIFMRWqXZo8M6YgEGAMXxiF4Pci8MxQxecfGMboeVR+RSXSL5mTYMKG4KOXPEMdf6k/oyPDpHK/YsQ5PMUXyTdJII3fTwiHiYcEFsvWfFJA5SUfPsvxoHeU7XzwxtOf58brCT7wTFC+s8BAL/2YLGElxIB8nNXyUWYho8CbmQJTURDIEfpCyJZ0/b2PKGLS/Sqdeew+4I/LqjxpkHHgIA04J8IX8hD3F7ADPs/k5TcRrjzox6OATchHi/FEqRtwkSfwURbKHj9cTtwHPNKShvRn7aROwTzvbRAH5ADDp0XDM1nC/gcCkzPEMxHkyCXoBlpP2ZDW2lYtXm5FJSKWFMZgR6uV1Tf5s0o6hpvpqDP35BnRRFV505wz8RL0A+1aK1yliR35H/tPHbeKuZr5dwVFfsIZGDwNawPIQNtpK2tosp5De12pVM7TCqsqTWbVnBG+oyJa/8ljVrloqYqUMIRDGyufC3wHzZ/ARQpCOi7BP9St8ss/WCIcxeVaIuxKLScfjUbewKGGGuxo83oPqtwjolfD0hUJiuxvRoE3KQUmUhAhPKNqPCNr2NDFyqJYXs/7eAc8hFkIuMhLHCEEIukJIbuSfpssHkFWndBHfFjiGu8DPnmAwZJ+JoZZkcSkerosgStwsGwV/35MjrNFgMnjpVr9SeC9r+zEOFV8On+Ul3TAo15c4xdLV6FHpOEKnMCfvmeVFatKmWemTCw6YkFNg1aLgXe7vky3RFsXKCv5kk/4ssG5yh555JHExcQED8taEehslgMRBWFGm7iYmQcZFWAJFsBBRj4C8bHrkNl73qMwqAhKoqq8zHoOJkIZgT6ipWullTXWd/yQVUphIAzLG1EAWv3T3mrlzfNsuK/HRoVzuF+rHFpOWNXSVVZWq5UHbS0uaAfaTllZXaO/79m/y5o23WpDPV1WpJULqquE65CVVtfaqJRLsZaYDSpfx45XreGaG637wG6rXXWVFassQ1pBAMMwiiiprLZ+4eo5uNvmv/vDTov+k0d9+VGlFBDKAeVSWltvI1piR11QIkPdWkHhiqZO+Y8Lb53KqOW1ne2ufEYl8Fs2P2GNG96RKDHB4f2IGBzcFXO1mkBxpTWaOBJTjAz0q6xbnQbVK9a6Am5YshzyZCGjwJuWAhMpCCZZ+doaaeq1Q5n9EDUSbK+//po+s3mtZFKrrzRi49cxbfJi9eSrr77iJ7Uie9AB7Gwm37JlSyWDSnwvF7II2UVAHrFCp64Og1Ab0LTskw1qO3Zst2uFg1VGXZIHrCoCJoLzqJTD/gP79dnl6x3nt7/1LV8eilxEgLM1APgoDmQncg+BfOLEcYdNvVh6u1h7GZC1e/fssRtuvNFlbFVVpS/RRTgja/maHaugkpVcw35UuSswwerW3gVgDQkXG/QITNCThyWv4KVMi9iwJ1jARLY8//zz2r+2zMu+QxsPUQhshGNEs1972BCWyPGmpkZfOuzl1LuntWy4iElqALGTGoWApgQZWg4AVJTfPgApQMCVK5Od1KxDhogUmrSh6VEIxLHsio11bDypREFIiKMYuvZut9rV6/wZgVwswTogoYp1DYFrlq32dB3bX5HArrLqJSt9hIAbqWLeIhf4vYf3WfXyNS6AEbT9J45aldL1HT2gChdbxfxFrhyAOdTV4aOU8ua5LsilzQzYKAiUSM/BPa6QEMrDUjBljc3We+SALfrAPZ6+7dUXXFA333SHFbOf4+VnpSAalGa/lFGpVcyRcJcC6D91zKqXrXKF0acRTVl9g49aSmukLFSPrj1vWNXCZZ6WhiMwkqL8ZQ36ELlw1191nSsjRjEoHR+taDlbn+rQIEWahYwCb2YKTKQgEHBPPflk4hLR8lV2N2OpJ/sgVvrOZYQZx/gg3JFFyKuFCxe4fELQEwalYO5417tcBj311FMug/ZIViHA12qfFruSq5VXoHxj2nFZ2AhWlAd5kXcvvviCBHWFf/8Zg3efZNldd93lshEFsUA4EfbAvOOOBBeCn7S4aVatWmVPPf2UC//Tp1vcgF69eo21SDkdOXLY1q2/xvdBgO/nfv7nHQ47qxH4yMyt2pe2a9cu+/Uvf9k3BfJ9bvIjV9lRzvJW9oUs1vJXlsvOnTPXTpw84cuBoQ97KVA0wHKlovpxrAijiPla2lqr+qP8OBZp3vx5Xj4UJ/RmOW9XV7cdFm1dQSCU2UnNBjg+hg0QBD47qRH2IEJhoLFJA2HIA0DW0TKEIQ1DKZQMhSIvDY72e5caq0JaHwUxrA0b3ft2WKOs/Y7Xt7jgxP2CQMdSx+3UeN0tNnD6pAtuRhclsv4ZTTDKQJCiF7t2v2G1a9drVCA3k0YJfRLOuJhGNNpgtIDgrZQy6RPMwdYWK58zzxrWbfJ5AJRR94E9VrdmnfUe0zb/w/utds164TzlZahassJHCQvu/KhfKTPpGq+9yUo0xETQM9pgpMPzyIBGEqXlnrZmxRor0yiAOtasvNIVSOfObVIcV6gsR6y8YY7cTdLuUjS4zSgzv2MPf8tHNqRDOaBAGB2hgHAx9cg916ByZSGjwJuZAudTEBiiyI1du3baqpWr3ADF0Fy5aqU20x3UM0f/1Eou1fveg66uTn3LebFt3brVRwW4aRHwGzdtckGKXFukTWvILzbCkRerH8GIYlm16grbpQ1rCJUrrljtoxW+H/HC5hdctnH8BsqDUcz73/8Bj3v4hz9QOWr8fCUUxK233uow2aMwLCO2SgYv37E+rZEMRjMjGMINN9xgB7RZb9/evdqNvU717HK5SX5GD9SdzXzrtdv6NX1Wde/ePfalL/26jxCeeupJ5b/RvTYoRXZX8566s0fitde2uWx2mJLPrfIAoWzAidzGI8TGNzbcofhWaiqgSK6mRx951EdAHEGCwY9sxegnPzvRXUFQeAgMYVEACHUAkpArGoh7tAvEokCkQVOiJFAIBIZFpCGOUQgjE1xPFLRKuxBREMw59Mnar16+Wtb0UremRzRk6j9xxCoWLHYLHUu8atEyF8DDcuNUzFvoBvfIkNxSUgYIdOYRiivkNxPxezVqwK9ftWi5BPlBH3VgtZdUqRHZvShl5sMtai/DHZgoktI6HVolBhvu7fYRASOIgOcjFwlr0nbv2ynXj85Jmb/YBTsjEt7HCIJ5DdxN1UtXOszeQ/t8RIKJ4nMjKj8jGfBQTvLDkV4HjQ4o1KnnHrOmjbeo/nI7SXGo0IIxVy6wcuvVfEifytm0aq3SnhuoXxYyClxuFPA+l1OoiRQE79hZjHsEHz2H3rHbGflRW1sjAbZHBmiz7uv8OIuVK1f6Ao4OKRRwcYYTfn5cMOzERlYhv3Cb79mTjCBQCFj6c+ayIUxGrXBg7ZO2Vu4p5BqyjhEHsozzlBDoCFBcL5TlgNxNWN8nTpz0K+UAf7iHcDdhMGNs444vkYxCruA+q5OMZfSCEb5PCgFjGw8LfZgfMhVlwXlK3G/YsEHxyRxIu0ZUwINOJzVaSGRypRvoy7Vj/NSpFq/Htdde6+WhTPGjTChJlAtKAqXBO0YznDHFmU3Ib8pQqmM+BnRe02OPPZYoCAiBJuRKgQkUDgAMmSgQFUIT8kMDExD+pGO0kQ4hsEjLvRdSCXChIHARiMwlYOXrpQtDX6mk9D4prFEEBNVLn0j2iWfSEZCFIpjDlbAlwvGpLJ5ujNAIYocR+cg7FnDbeLT+uAsnXnDVVmlf4aRb3Em897IprcMkj/An5VBaWQzDGuJh6SPMKYPDpN7UX/ASXNBCeB1XAiPqiNuNeZZSKTSniafRH8epM180j8EqplptqY8QNI7n7JpR4HKlAP0/wkQKIuRJkpaOPtZbyK/+hFsFWQPvkzZ9Tx7wMBlMvwmrnXTcIyC5R74hswjIsZBzkR4YwEdh8D5w8Z5n3vMuAu8pB/HA50oYEQ7SFStPwA5cuXBQJhECH2m55106LmBFvXjHjzJEuZDjuYE0wCQNaaMMwOFdhPT9t+RK8xEEI4eoWCTMrpcPBWhYOhZWCyHdiLmlnOhdbtrsOaPATFJgIhkS7yZSEDNZlgzW9CkwY/sgpl+UDMJEFAgFwfA2HXKVQe5zOm12n1HgYlAgFEHgyn3GkicurO5Il10vPwpkCuLya5O8JUJB4BvNN4JIK4X0fV5AWWRGgYtAgbRSiPu4MoLATZIpiIvQENNEMW0FgeDiR4ABwk8nH4j75ln6yT4DOePdh5j48pMJm3xld98+PnsxkACOJ4l5gmCyEISJD3882Zkbx695grH5CVYFeV78gTHvMZY65hMcG3inE8Cr+ZPxHdOpOpwPLPnG64XvVcv8mI8h4DcMF1Ok4Zq+J108c5+FjAKXggLRR9PXuKc8MYJgLiDig5fDEEKO8Iv50HQ6YPDML3z+5GcOlcldJqTxr5M/4EZ68pKHQPoOnWzKCa747IknjrxM/pIn8nuGsT+kY9EOgT4JvtyQxpf77s30PK4gWE9MgCBULn1PXG6A+BAqNrXQ6MCggZgh10vfEzCk5ZnV7JrWpAiTuqw2qtRKpaISlIbwMNlL8HsJPAnCPq3YqWiepxVKTLSQJhG67JpmiSvPQ51tWi2knYDA9fIqHcUeL+qoNqudTOIU3aNVRWVahcR+Cza7jSstvWPSfLhHH9PQ8lPguTICnCa5XaEFTOgAjkDkZeaZeL3QhfQsqWWZLRPWxbiExunnmZO0ZFNwWo/VvefwPs/HKq+Sch0prFVcPkmu/HQcmBEXU7RHMG9cgRfvcu95zkJGgdmkQMiN9JX73OdQEIwgQogzOmYFUJnitujjQaw0atXzOq3nZ5EMApt+RDq+nYCApi8ELGTRK69oOauWfLK8dECrEVnpBHxOfCU/8omy8AU3Pv/JKiA+AMSeAcqCEcZHiEpkQAIDwQ98NgMzUUwa5Bt4n3vuWVu5cpUrE5abxson0nPPUv9QbrNJ89mGPa4gqAxLU0MDg5iddWwcYekTxCMNBEAILdJXm4jbtm2bE5M0EAYYd999t+9+doGnVUCx67liznxrf22L1azQJjg1YHmzGlI7mxGbbEzjaIsiCdUeLWGt0JEa7KRGQLKruUzLUdnPwH6BQe2eHtK+BDbPJbuvtcFM8Eq0jNRXDGltb7EEbNfeHRLyQNdPjFK1YEmypFZLZX0XNyuGVKe+Iwddr9RfucGTosTYzVyp9MDvbzmW7N7Wvoyyeu3aFnOy3La8aa7DYzc35WKvBPsb2GHNkR7t2uMx5+b3WL/2L7A6ySuqEQHKiaNFWOpaLprESqkTjz3gSqFGm//aXnnemja905fpQu+wrGIOgg4RiiGutFn6nucsZBS4mBTIVQY85/6QIQjuUBAYPqzh37tnr+9/YB8BX13jyAeW02/RR3tYQcnmLfLyKVKsfjaHqWu425WdzMgiFMyqK67wJap8/AbhT7pD2p91zz3/P3tn19vEEYXhUUiK62ClaYIDFTRulBCoA6rSkPYGIYEUVY0EqOIP9Nf0X/QX5A4JLir1hnDBh7jgM6SuIz6S1CU4QrYLrjHp+5z1bNaWWyIRRw3akezdnZ2vnd0975yz8575wRbfuXLlsvIkbRotoEJduNlQQzX1s78xDXTUTWlNhSVNb71x47q1lSmxH+ndZfr+3Xt3tTDauE0bhdVt750sHxD6MAOPjx+1hdN2su87UVcIENxEmNQgJ/Nv2TJfFnULohw3iNWPSMeMp0wmY/Ew/Tifz+ctDaOA2dlZAYRWiNOoHc1h7dovRkKDN/C6sGx8AhjCgASCFUEL76EmDQHTDMGY0wIGuAq9YjtDNOvWiACyXO+hEfdK5TDS7urZK+GbNN7AJyemxcaW1iBxjwCGucyIHsY1gh7/T9U1MSaVL5mR6wqN9P9eF6NSQAOHITU2YWWWcg+MqZ0+/b25zCjnH1k73mpeMFoNbUiNZY2oB48CjaTyJGcaAxpTQoQ/OA8AFDwPCHrV5wVxP5bdwNQpaVHPBH6DRq7blxk3kOJJhxgIrwPto3jrqgBC5BuRcQjexIQqzMMIQAAavDDNYEHqTdMTR3GIe2AnegDZQGgGBMxETKnkh8k0mCLKlgEnW7QBZA8L9iBz8BHEspeQbgEPRvAMjBD+DEyxWiCPFhcX5W5iUAsAdbsj4hHAQsZcPDo6ZqaiBRHNPocMpmblfsu58xcuWFvm568Zb4v3htXkyAuhDcJvVfXhimLy60k3MXFcZLmbtvAOpDEcZk6dnOIKjVMBTyMvXgbcCVxbsCIna0sjD0mTzWbpjl0dQoBA6MA4hPBGx+PPBLUKYgX7oD3aAeQK0Brg4MfNAzBYWJyOASBmZmYaALFknIDCr5fEjJ42YQeBbI+IbpiKyhL6kOESEqIVXF2I+Aa7+LUIc6R/ef+28Sb6spO2jzO9WqVsbGhcdbwRNyA1ckzzjWsmXAe+PeNeiGzGd4/k4Yzbp3P4YCr9/tBG6Qjlcu5hUKbKx70Gv74vJ83pX0qs7L/kn4k2sN0vP0xoOFWBEeatxOBBE+r4YeoTo5r24TwwKf9QaAtJARfOCOEzoAWUVS/gh+sNrq9aWHEHv7tobkaKt+bdgbPnAm1DmgdaBKQ5DYgMONsBhKnXelE8OBSL6w4XAcy3thEMj6LuI2VwHIbIbhgX78Q9sB09EGCClSRdAbkYAAQxksxwEjC3pOXKwWsMXoPwAAEIMNCEIJceSiudlgOWbMFMxNKedySXcCfByBxgwOREuQ8k3Bk44TaCZY4hpQFGaB2sGokfpJSWDV1dXZFrjTVzk/GxTE3X5W4C/0dD6cADBPkBHRjZYwIXykTWsSob3iAAHiwpgMDCowUT/Ph6op28ZwBcQemyE1m7dpYVBfgAnN0eQoDgQlDRAANAgpsGAxG7XdT0BGgwcgXJ6RhuMnFQ49knHx2DCQdneGgJJTmcS8mNBW4nYBLjTM+0B5lXvIkp8dmwq0joY56B/YxJCS2DhwxQScostVEXCUWqKQIawYwbCtxZADA6Yd8tmm6I8qJBUB8fphH8mHUADTQMBDllvvrjiXwpockMWBz1cT5xQA+argdWM98TytIsuvXAVaQZYH4CGDiPSar3iyMNT7Ua7ag9pKcuAzy56OiRE0IY3ZjZ6Be0i/6vYE3jKLBk6UlDe2Fe4yiwLyu3HtKOeAijGgQAUdNDDBivrBaawcA6IEaEpucgPtjBHoggRqPW/WIsD0nwY57xGgQmJuQFW0J0QEOaaPDnovGM5vE1hEbNABVAaZeOpUBxHwEYDGcyVifpfFl+v11e0hDvz9Gm1nzE+fP+HHGE1uMgdnf9z83NBUQ5VDhGqNw0kJ4AEHCRfNkH9T2Zjjh/Q3wnRDuJm86HXsxDCFe8se7hA7HKJiCU9R8wjht1qEBLh6CHiUy5VqbKYXaPfeS2GT18sKZderBIo/KNPe1Z01ZD5E83OAzcbA6IU14LxFG+6qRugh3rPIxmawNpSce1SIV9q37CXIbw14UG7WFfwp3rDtjhVlSQT+2lPuIBOAM+ncZJYTRwjvrMg63yAA6+DQAE94cXwmsQXEbk6qJFxftxD/xveoDXh7cNucAPucI2ChA01suSrTSc9ySYTLL5jaNdPt4bZBcyDZlFvXHYeg+EGoQX/lvPGqfsZA8YMDUqYN8DBNoaAMEPQOHD2bYFaX31P5/a9xiALwxyjlZNyU9LXZ5l3WZ8d5dGb4m0S/T0h0njnbgHWnvgjQZWmEE9QPAsI6zfByBa64iPO9MDMUB0pl/fu1QPEDZS+heA6JLW1CPA2K6wUSq69Z9/crXlJX0VD3znW9n6iPd4esAtVG66+samD5revZ+6b0Z+dMODpyyZb+u72hNqh5GE8cgu0hkf2C6O8+po6VIlAAYPEAx2iPOag9/+1+XzjPl00f12eThP8Onbpel03Lva6OtvTbedbW8t29e5lS0A8Q8AAAD//8ED5cAAAEAASURBVOy9V3CdSZbfmfDeewIgLwy9d+V9l7paquqe0UgTI21opYfd7X3QRmyEnrT7KIUepQiNNvSwD7uzMbuKlaZH1d3V3VVtq6rLsByr6D0BkCAI74ELD+j/O3nz4hIFkgAJy0KSF9+9+aU355/n5DmZSUNDQ3O5ubkuKSnJbbmN0QJzc3NWEJ58pqen3cTEhEtPT3czMzNudnbWJScnu/SMzBUr8Nxgj+v9D/+bm7x9w81NT86nu3+/a3q+3F0YPeWm56bi/rmZpe6lXf/cNZR/z/z6+wfcr3/zG3fw4EHX0dHhKisqXHFJsZWTANQjOSnZfs/Ozri2u3fd8PCwKy0pdfv27Y2nu/XlyWqBqalJN6PxC31JSUmx8cuTsYxfoDvhuVjtGe+Mez6pqak2lvALaSwWZ2RkxMJmZj54jjCvKA/pLseFOUq5E7+HNCjf5OSkS0tLs/SD/8In4cbHx11GRobNDerIb8qDH79t7mi+U87lOOJOTU3F0+H3wjajjNSBci50f/M3f+OSVgMgKAgOIkYBcRSCSicOBBoHRzi+8y7xPQ1DWjRMor9F0h/eJ6axWJgQdjM9w4DjyQeAYNDQudSXNklWm2SsJEAMdLuev/yXbur2dTenSR13Bw64my+Uu/Ojn94DEHmZZQKI/8U1VrxuQfv6+tz/95/+f5efn6/yTrnCoiLzHx2NCgRKXFd3l8vNyVV9Zl1WVpZrvXPHQISB+dabfy+e3daXJ6sFpkSAGA/MceZxmM/0O35hzvKEVkCwGO+8w+HHu9HRUTcw0G+LjJycHJek91lZmaIpaTYvonoPfcnU2GKO/PrX77mjR4+57Owsl6NxRzqAVVlZuS22SI/w58+fd42NjZZfdna2hZuYGFdZU628lHNsbEzpZMfnIaBz61aLq6ra5srLy92VK5etDKWlZRa2sLDQynzlyhWLV1paau9Fa11BQYEjPvlTx0kBVF9/n/wLrdzJyUlK+5blVVtT61JSU1x3d4/btm2b2mbCFRYWWVqhbUiH+kIboBG0MXUeGho22nHzxg0Xqauz999884179tlnrYyUgXj9/f369LkjR45au0SjUWsX+undd9/1AEHlcRCj0GGLfccPRyH4Hn6bZ8Kf5uZmNV6V+bS1tVnBK1hRFs+vKCkcg8FWkWrA7u5ue0/aOBqPQrIaLSsrs0KTXxg4xKfT8evq6nLV1dXxd5ZALI0QPvhthmdoV558qOe3OQgBxENWR8up69xjAgQD9dKly65XQMHKhw+DjQmWl5fn7rbfdRm2akx2UU24HI05+oxJ89TJE8sp6lbYTdQCELXpKQj+tzkI5magNzwhVrdFHO+2t2sxkePytNi4e7fN6ABj5VbLLdGSMVdf3+B6e3vduAg5i44S0Q/iTYievP7660ZvfvGLd0T4d9rcobkAndzcHPfyy6+4a9euuatXrxix7enpcSVawHjimmxPgIOyOJG7/eKgW++0GjHNzc3TPBy3tKBXhw4ddidOnHD/5T//Z5uL6Rrr0K/tO3a4Hfr87ne/dVOTfoFcUVlphLmgIN8dO3Zcc+WiLfAg4KVlparnXVck4o8053brbTcsAl8kepmenubSBIKUjzl27Phxt337dqN1LMq++uortUWP2717j+tQu40oDGXu6ek2kOgU/aypqXHZak9Azd4JcCgni8zBwQH7/tZbP3RtWrRdvXrVTQpIAciWlhYPEExiCDFIGdCPzEE+CkXjEgZChQPNIN4Qd+JA+HlCEEB3nvzmfbsKfVyVKtKKks6AsJMH+ZHmuXPn3AsvvOAuXLigSu62uBAXVqIMms7OTlepxiUuRJLvpAu4DA4O2mqURiVfBk1YBVBmgIXOD4PQCr8J/iQCBO1FXb4NEMkxgFgZ0eDjAgRlpqwz+oQShXZnWWF10hd1adzxHiKxGUE8XomtLw9sAQ8Q3+Yggqgj9D1jgXl96tSnNudJ9PjxE+7SxYuORebBQwfd+JgXxWRphcwiFA6hre2OvYfoXlTYV199RQuSfK1+f2XiS2jA9RvXrYxPP/2M0Y+f/vRtm09wshDwWXG10CtojkaoOyCuGWI6Fh0z2kH+LHLKystcbe1213TzhghxVHTredHCavfRR3+0sjHGK6sqRRtn3Z49e9zHH39kNPTq1WtuZGTYlRSXiOgXueeff8EADe6lobFBC9xuy39a9BVaevv2baOXR44ede+9967Fg3DD6dTU1qh8B20B1tLS7N5//30DEECAuABNjughdLNfNBy6e/TYMXfnTqu7ITCC9iLqNVqqRTy0nPq9+uqrBpqXLl6ysAcPHnItAl0TMdE5Z86csVV/U1OTY7VPJDqPDPhcvnzZGgtiHIlErMEg6iAb6E6GyP2MSAg8yBhwoSMh/KAeecBikR/v4DJ4HwCDOBB9CCLhgnyMcAANnVhbW2vgArqBhgACaE5nMxjgJgC3OrFVABBP0HIzuYcBBOAM+nv5agLFfYxKTkdHXefvf+4me7vcrNIPLrO63EUb013n2AUtqKaDt7iBQtdQ+YYrKThsfqzeevv6JW+esXZPSRGApYuLGIvGQYBy4wCMTC0CJjU5ETkxoVLFSrMAoa8QHUxp1cmYMlGaOA/iTur93OychQWIFNlliYtisrKo0LCy8UcapDmh1Svvp1UmykOA8XHt5WhclJWWWDpWoK0/q9YCywEI+huxDH0FXUGEc+7sWStbXX29LTLaRQBZjQ+JTuDH6plxxAKyW2LMN954wwDirOJBQwgPwWQlXhepc7ki9NCOZtG5MomHoCnQDhasFRXlGj9zRs+6RUcY04yrO62txqXA9bKqJyzEPDsn2+3du8/Sg9NJ1aob7hhCDg29dOmSaFGv5kO6cQYsYCOindAk6NzNmzfFDdXbyp94iMmKtaCFq4YGQn/hepJiBJ06E35IQMpeH2kAiuzpVQuo0lVW8mCuDAwMKJ4X69OAiHmhk0Yj1U6IwxD/Acr5aqdDhw65pqabAoc2AWPU7VA5T58+7QECgkSDQqAh2KzGQXgaAuRkwtEBIBRIBeEFCFrU0IBHQDyAAj8KSEMAArx77rnnjJh9/vnn1lCkDzFAfndHbA2/AQ8qT6dC5KkcAAEXAxcAGFGZvXv32pPGhTOhLIAH5aVspEe+hCM+flsA8fD5H52ccX/9db/rHNKmoiZJcI0ls+7vFn/qModOaW3lOUjepaQVuPSaf+iSC49ZUAZ4e2eXWPNB+52ZkW5EuO1uh/o+I75iY6yNaSVYUV7qBgaH1H+pmhCjmoiSSWsiJEkUwaQY1CSxTW2NA8bHoFhuACJP75CvMnFhpyvEntO/U5pQw8MjGj8ar8kprn9g0CZYQX6uG9VKkAk3LdDIFmAQt6a6yvK2wm79WbUWQMY+Pb00DiIsLpn7OOgIQIHIhw8Ek35kDPGduW/EUOKgDz78wAjvvn1+0Yg/NIbwfvGQFCeY0AXekw5jJ+RLeiHv0CCEY5Pd3inP4PAnLIs0xE4AS7J+Mw6hRZSX/PkQDj8AEP8g/ydfysCHMuEIhz/l5knYUG/AjLCA1y4tuvlO+wBWiJsJhx/xQr7kR9o8+eAIzwy3eaPv1I02onzUC4ff22+/7QECD9AOFguCSoKgE6tzWBUILoSaREgAMY+t7lQgKgTnQGagFyCCIywEHX/8aCRAhoKTFnkQl99wJYAClaMRjA2KdQbx+MDBwBkg8yM//EgHtKXilBX2KuTFb9KjgQm7mRzlxoXOZrDQ3rRXGDjJIoLUTZVbkar1jc+6f/XHYXdrQIN6noFwT5VH3f9Y9o4r7nvXJc9OxPNKyihxabt+7FzZa3E/ykv56HMGK87qQhn1LvSDDxdb1SvMtMZUquKE9+Fp8fWHGlo6sScTgD4PfonhiZPoCMP7+FMvV6bFEnPZ+n6/FgAgmONwBYwL6AdPxjL9EsbJg/rwfmkn+of+TfTb+v54LRDXYoLQ0IkBZWhsJiGdBmFi0sOG8TuxUxOzJzwuhCENPgwA/HABdcNv/EJ44vMhDgMo0eEPUhKf1SQuhOOJI51QhsRBl5iXBdwEf0KdeFKntQSIloFpAcQ8B/F0AIjeX7nkuQSASAcg/mfnyj1AMH7YXJzVKr20FPGNX/Eh6qH76RPqE/qGOvGdcPQ344wNbtj4sJLjfSAqdDMbnaQRxgxdGdoKrZY5xo/3tDCWscIzbhBVscLz4dkw9XsfiMVYuCCCQDUXdp70ySukTx/wIY0UqwfjzeeNqAtxGe/hYFi85OXlar5k+zBKhJT8OOSb+pTK8E1xQr0IRZrB+SAxcJMnZTdQVPzRqagbmRzRNz8+SDMlmdWhnkkpriiryKWlaDW8AaBwrQAitNvWc+VawACirbN/LkOD2Q/jlUt8K6VHb4FA9HhCTOYkY3Qzk64gF3m6Zz1Xi4N4VICAwF/RRhqEkgUH5UzXHsSY9iBYXIh2SUbKqlFyVYE8YILn7l27bDV5S6JIOMTIjohrkZgSlhnCXiwRJkoPiJcQRwFApIMYifeQWoAEgo4MGA7UyqB9DMoAkUQrIzOmEowIANAplY0Gi40vvjwt8cSH7odvvWnp8Y70oNXJAhHmxYQWJ8i52RwFwEiXviEcYq59e/dYP6FGODU1bWJZAAdNkDQBINwyHDXlAvxQt5xQepSVtgB4QAoDQ/U1T7j2bdp7GxAnr1e2b1KuTdIUtUH/uFQTR/rd2KTfRI1ORF1uZq5EgzOuKEd7hrnShklTumrr9XZbALHePfDo+RtA/N+/a5q7OyTZnmcAHj21rZir0gIQorTkOfdcfbp7enfxqgFE//ic+9cfDUnEJA4iYQ/iqbIx9z9IxFTU+94iHMT/JBHTq1ZviCbEDKLNJjAbfBBECDnEG8LJXgQEFz3tAe0RsKouKio0oonhHCtxxIu8g9gBOjnZOUZI0QcnbQMaiSfQPMEBAHADcCoACMSUsmQIRCDqRpRFrCGWpMk7Nq4h2BDrdu2zIU5l/wwgslW66o8KJe/5mAxXxJ4VPnr3lIMPAEL92Ddjs71Lm4CjI6MGPNRtZmbapSo+3/lHGNKn1GNS1wQsTfU3xq0ASnBicEOAHXmz0U6lAF0TKYrm940JIKL9BnyknaoyjE+NK9kkV5BV4Mrzylx2WvamBgjair60tlO9cGHhhN9Cxzv8eYbviWEWxglpEYbUWGjggn9ICz8WAg9yi8UhfGI5wvdQDn6HeCH9ECY8QxqLhUsME8LxDOn7Gvl2mvcjxNKdAcS/+L/Oz51rl8bHdGiipSewFXINWkDdkiu6+t89XeD+wTMVqwYQw1Nz7j+eHnVtg9MipPP1OlAy4f686Pcuf+AjAcT8JnVSWr5Lifxj54pOWmAGbBjI+iq3+HgKgzUMcH7zgeCG74lp4TefricC86W795sP6/NOjMd376u/Khy/g5+BjrzDbwtowRbPi3ChPIRNTAsw8u9I/14CEdINT9ooViz7YkTKN5wFCeWx9ChvLC9eSuAlMeCUtZm9kB/hiJOeog1SiZtCfMKvp1sOB0EdaEP6BNBGSQYNyNDGvMOF33wnDgQ2jB/AHMMvtIiiUk5Ay6m62iuqwJlZeyoe4TFIK5fmIxwunDp7mGwARyIRM/QMCjso08Q5S7Uxjvh8AHwWHTjjAOWHQxMIBZ2GxkZbYFAXNDDZeyEOCwHUTlEMYoFBGpQtpEcdfbhJd01qsrXSAiUc8fggymTBEPaFh7UXC0daqvqQzi2p6UYiXpMz7PdYwZbxxwDin/+f5+bOtElbQATiURzNlZLiBzApTMfk1yyKWDmiEaPyLupSYiunRK2ZxQJanyiNxZLx3eXfkV4Ae8pBvsTFHy2DhIXxYtnE/SgWcaZjZV+YBr+RCSc6OmWp6SfGW8r3/Mwk90+fLXB/8XzlqgHEpOZe27BW7VooJNYjP33alacNuLTJXtGiBORITncuq0JL6sKlVGErzAq3gEb3ohMCwrKR3HIAArEayjKI9OC8IHqoYEL4UHWH2MPloYCSn19g3xFhokLfervVNOCwSL4mUeffe/NNiwfRHxoaNE4UzR+IOuqwNB96/qikogWJkgRW2DdkMwHXWlBYICI8alwvatK4w4cPx2wlUOq5qL2rXlMbvX37lpWHMmKshkNd9QtpbQIK28SdAhZoc+XJ2K5S4dplOHr3brvZgKFsg5amiWMzs4x7hVNFaQhbhjap0GI7wW+0TSl8v/bOsMugrJ0CNbh2FHbQKs2UeJX2wACPttu3b5/2Bb2xsRVuiX/iAHHurnTM1QYQxkAcwneGG2MuEFuefoUJcsPeJrm8LDVuWrIryU91F29HLfusjGSXL/+uQamaxYg14cP4JZ3tpRluaGzGDUWFngmFDvkFop6htNMEQuNT2iikLApLfBz5p6Uluai0cOorMt2w0ivKTXU3O6QSpvcQ+rL8NDcwOi2Zrd8wtbh6GUvCykQ9+U36+VmpLj01yfWOeFRn0tWVZ7hb3RPWPhnKj3YqzUtzg2PTkvumutYeaWusEhe2FgChqm+5rRZY8RZYDkBgS/DLX/5SczDZrKjZn7kk7cV8qdpDcAEGQAMCywr67Nkzpt8Ph4C9DTr+UdnEMI9fefll40KuXrtqAAAhffHFF82i+Pe/+52J8aKy/dm1a7cZ6xbLgA1tylbZPDQ0NFhcxKKs1rHSZv/ptde+5+rqvF3VX/3VX9kxGWh0QsAPHjgoOpQqM4EW0+Y8+dRTsXjZpraN+j22Zfky4sPyG5EmmpsnThyXGn+prK5/Z6DB8TmAFko5cDDPPPOMgdbJk0/Z3tbf/u1PXLb2jOEmagQ+pHFVtiNwE6RZJY6kqanJbCrQQEWcu0e2Gk+pPCaiXEYPxwHiSvecqyrMcNki6n0iiqzoIardQ1JBzdRBViLOkyJ+GSKaNH5L14R+z7ptxemuXOFYafMBOEbHZwwwKEeWCPugCPZtEc9xEefSvFRXViCki864MRH7w7WyhxB4dCof4hWI0ELIC7JTDFSIB1HfUZbpju7Icd/cGlG50gUC0ktO1QZhLK9DSufDy4MuIsAZlN9OAcUXNwmb5rIUjj3AW0orMz3ZgGx0YjYWlzOgZLSlcpIvdQXUxlW3frVDRWG6BmuSQGzaHYvkuuaucavPoe057vMbQ66+TBadPeNud1WW++z6sBsU0K2G2wKI1WjVrTTXogWWAxAc+3D69NeuUMQPjpzV78joiK2KscfiXC+MyVhxYyfTJ7V6viMCgviyfzU0PGScBStuQARRCxwARmbHpSKPGvypU58aAJSXVwhUSkWYOxxWycZNaFUP98K+FgQVsRHEmtU7XAsgRD6ffPKJrdo5kBIr57q6euMAbly/YfHq6usFPGcNEHZKEaOrq1Pl7TOiDtC1SBGDY0MoJ3lcFAcBGI2KQ8KSG04JwzaMVmmDF14QuCne5cuXJEIbMFEVS1qOMaGtgro/NmOIHQFKXI7shigbbYTIajkuDhBNfc4d2ZFnxO6giG1b34RW97IfUAE6xQFAwJ9qyDMQgLjf6BxzfcNTbpcIY/vApCsUYYc7qCvLMI7hdu+EEdtnGvNEqIfdtfYxA5jGyky3rzrHwOKT60PuqAgtxJgVe5cMtHZWZrmeYW0waoUO13FV8WqLM7QhN61D4TiESzI6gRWgcXdgwtK6eEdGcdXZ7uKdqAEboAbBB2SKs6UxosbbpXS/ahp2EQFNtdL75OqQqy5KdwMi/GW5nHWS5AYFEDzTNDDaB3UWiepDnt36DmdQofA9SntG6eUJSD5XvQAXwPRoJEdtMu7a+xMOuVtOTzwk7FoCBDJcHIMpbJ49pHj3vGawJrqFIo/wfqF/Ypz7fSfuUuKFPCgJnGFiHONK1XFwqWgP8f5BLqRFmMR0HhTnYe9CmiuV3sPyW8/3ywEI5PDYTkGQ0TBDDRg1YsYkBNrk9PqdorGJqIh2JCyracIwXtmnQLkBIOA77xHtoMQAJ4J2HHkQPuwH8JtxwDv8yQd6xkkAKAwE5QWIMOmSH35YK4d9AbgHxhagRL8at6N0yYPvGNuNq1zkwxlTwdKZeqGMwNlPlBewgJMJexykx/sQDlsz6k2avGOekjZjGX/KTljABoe4jn0Jwi13vMUBokXnUh2J5LlbIuwN5Tq6QOIarE6rtIIe0oocYl1RIAIpUID4ww1AqOEI+mzFn+omxBHUiwB3iYh2iKjCWWRq9Q6h/t2FQcm2Z93J+lxbkRdkp7pTWoE3inADNBDrfPnBvcAZILYpk5jo3G2dA6W8yetATY4R534BFHkV56S4QgHAdYHVThHyK+06ByojxV0XqEC0GwVepSL+NBqcSXP3uNtRkuFyMlPdl8r7oMDpQquO+xDnBBLXChAvt+nIBoEEdQJkjoprIK/2PiwRZeg3IlXHdB2Sp/J83TJiQAWQvLA7311SXMBpNdxaAgRyWVZuNWJXw7lKDFpvH+CPsmCyscIKjomJzBStHrR4gp4/x2dkS65LfBwrOSYsv4nPIGbQhoFr0KL+4jdhEkGKuExeNKHCJPbl8pvGTChWfExSVpqkgaYTExeWnPwYC2M6aoOjCph0eSJClIdyBuKiiFZWZNLkjzYWx3Ng/4CYAbVVq4vyY/VGcMpBXUifdPTwyGRfgujS14kInP9DeCY55XuS3XIAYqO3A2MMIv4oxHaj122x8sUBAg6ClX23CPRtiUwyRKThCqITM7ZPwAr/jrgKgEHj393tnzIZfboIZYnk8HAcbF9mi6hOiqAXi7gHURUEFCLL5idEHTHOhDbEETEBp5N62h6DwuXoHWIjiDNEhv0LRFCkUaS4whi3TWDSLwIOIYiqPHq4HImOyHe/OIkzt0YNjGok/kJE1CuOpFOARfhSgRZ7FoQFwOBscJAEysB+CHsPiMsId3RHronLLt+NmviIcrMhnycg4kgKOAvA62R9njsrMKO9VsOtJUB8pfNXIPZFYqMvasMQYoyWBys6JgcrFI7KSJPqaKZWR6yKeL9jB1oWhe66WGxWM6yw2JDbubPe1EppF9jnzu5eUUyJKxU/WOnn64RLDmKjH6CtEFpkwhGdiImWBr/ZMOzSpmDNtkrXLPa8S8d6cMYOaSESYGV1/PgxI77nLlyUdkq5VFjbXW1NjduuA84434l0hpUOx4JA8HEcFsnGnm3yCThQtcXGpFCblGw6divPTuXFirKxoV6rszyLRzoAESCUqzN5bt5sspUrMl9k1xARVq60JecRpes8HspbJpFId7fu3tDm4549u2xVGwDSEn7C/jxJAPGEdc1DqxMHiAsdcAx+gxriyERl/mg+2eYwBJo9BxwEGWKLQz6PLY7ohk0+88TPvsT89JuJycwPm9Sxnz4DwipCXCuIpPWbMGYkpqeSsDRtZUl+9i5kEnsqDBvZcBzE47utIhXYtKQsDWk56Uk5KBN7JjiyJEN76quCWFxEXfwDpGgXK5QKQxqkSRkpG2Ipfls+pLXCbi0BoqmpWYR81NVFIkaU+T4yLOtgEUaIHOw7NgZjWnlz1DK/IYacn5QpDYw+yUcBFcQDrNIrKsrsSXtjpNYn7QtbOccIaJeIJcSbvkJuOiiNE069ZLWOkRxpW1wRXVZwGMmx6icseWNPQHrIajl8jTC9vf123gzGdbyr1CFsYaXOuUzEZ6UfzoiCE0CDxjpefUrevAckh4d1D4Fk43BQ1AUbChz5RAVqjCW4DWwgOKIZK3ISmhI4MEDYdARYAUzApEqbmiMjUQHVsH1H40RD6Il1WwCxebvWAOJ//+uLcxc7dViUVsNbbmO2gCRp7s9P5rkfnSi3FTwEh1UuMtMVWX2KkGGYJKoXF5WwKofAIWaC2JOPz4snr8J4gbx50Qrv4TBMxAR4ingikkkSAWUlQYwQL4h0wm9annqx+EAcxHvLEzQOWREo5nzYGMLLj7DEISgy2SAeww+gCo78KB+l9vXxZfKLHp9R8A9lBAyoNMZrQbRGOqHslibtJD/iJDrSCmF9ur69KB/AQviQX2K8J+X7agBEYt/TtrQfC4nFHO/pP9o59E2Is1h40uY97kF9Q5p8CM8+Q0h7sTQT/ULZg3g0vLMxph/4bxRnAPHJhY45bddIvr9RirVVDpgV7/RFg1XnTbqK3DlXXyVFAQiwBiUAwfG+K+KUx1xUN1ANcxJrPPMVSZpEksR5JBVoZS1xy5b7brWAv1GO403mz9WCCAbjrUBYA5AytoMLhDwQ7BAGzhH1ThYCvOP4b8ScIS1b7GgJQHj2erBBwFaCfAnPHGLRwPvET3iHxlG21GLZLF4IEoTHjzRZ/HBkDHYGiEITy064hY704a7RuIpEIgnlnTNuGGM6VGw3ijOA6O0fnMtSYyxWoY1S0O9UOcAEiLRhg1/tzs5IH1ty7OysjDhAsGJCfKKOe/zm0cA1F56Pn+K3UwjlDM9vh9jyeQJbYFz7PRBFiCoEmpXy/QAC0R+2EByRglor+0qcfYU/J/6yD4YmEeO+tfW2NHSkbal9Kg5H7JUKKdo7xMMIjfwgtpwHhuEcLKNdOCSuGwtm7j/AgI59M2wZKBfAMyw1Wc77ShNXgBptlvKC+HtxpL+/GbChnJwdduH8BffmW29ZfIzyAB80nbAEJx7AhYYSZQMgMKRrkeot9g3sq7H/hfiRMBjs/cmf/MmGGQUGEGrwOZByJQGCxqYx6CQQHMd3iFpiPoTjN+9AX74nvsePMAvjhRYkj4DapJEYN4TZjE/qhQv1Y9AxeFl1BQ4iAMRK1Zkc7XRRy3nj/zFYZOzo49sJWI0B3X2KTxzGCY6QYeyYxxL/3JMG4y/WV/eLrhGtPO8d1/cL+yT6oxG3VIBA3fTjjz4SGHBuV5apkEJYr8hYDmtkiCh6/rdEYFnhMycQ0+GHXcSIiDs3vKE4cfHiBfcXf/GPlE6m7jX4r9o/qrTTepubm228kB7A0yVjspdfecUI+YcffGCGdCgwdMhfAW28sCeFgRv7RhjxvfGDH7gvv/xCQJcqbuCOrjn9O3YiMNeHwplwvS4gw/4Se1uIJrEIR7V1u4zbmlQG5i/jgtvvGJPQYE4V/rM/+7MNMwziAAEiB2IUJhCTh+/484EQhWdg1RLfJdbq+vXrdq8EfrBiDBKs/OjIQNBIn4FDo4HgoCfGHCH/sNqAHeM9DUqckDffg2ohcbkDNqQdykpYPpvN0a44ntRztQGC3GDX1YCWJ3lDCP1ewr1El19hHNBXob+Is+YuNi7ZP2CFGcYj5QhlTBwTfNd/IyqozVIX2jcxntWBQFZtP+7DPPDxpa4aI/hsurN5Tf8k5hfCWd4qI+IVxu930S0XIKAXAAWGa/v27dcqPF1XfDbZ7ZFYRdfU1NpxHBirARCMU1bsAAFnK1VUVpg19OVLl92Pf/xj4wAACG5+g6P45OOPTcuuRtptAARGbm/pJF/um/7ZT39qdAS60nqnVVprjWYHMSPA4ggQ7oyGzhzSkRtfffWlOJpc40KOHTtuIi84HrThzspAbnvtdru97vLlS8q30biJ89KY47iPpps3bTxEIhHbKxsY6JdxYKEdmfGjH/1owwyTOEAweDExpzMx/AAFQTMIOgjIBMAvrARoXCYEiI6xBgScTqXDYKmIQyPDDiJX4+Y30oLYMyHDd8JyZR7sFhcCNcrEHHaL8tDpOIg/+fGkfLB3sG+E4zdp8AGBKQ9sHeUkHwCHQ7tsom6YZn94QWhbHM81AQjlYwRTG9Wob0IQsQ2gH2H3TfdfM5HrRNlcpX1RB0X7CEvW9XKBuBsnqj0Z1Fdh8zmiAZVaxgLjCDk4Y4QxjHyaVSeAAnFHNAEaoLIKIccfTSVUVRn31JE54bW2ciwNsVoeP6ziSe5mU5MBCqq/LEiwBCYuq18DC4Um7++iWw5AMK44koIziaApnFVEG8I50wfQGGgI5xupI20s0qa0LTSDePTb11+fNvHT93X9KGIpxgSiJ2gEC1LON+LqUSyVoQ9YRzPmERmFBSXA09TcpPwLLD/CogkHKJDmgGgN6tz0cwAr6B/xeUL3UHn2N27qXCTNE8YjdIz41IvvLJyZZ9DRAvlVi9ZtFBcHCAqEPjjEF6Skc+gICBQNSOU4657K08igKB1GHICAMExWGp8nHzqRit8UWu7cudOBllwrCjjQgDQs+cDy0WBsOtFYEHni48eAoVHpQAALxEc3nQ4n3pEjR0weSWdAADgZkUYHycmTTiff0OkbpeEfVo71AQiJsQQGHLVtR10LILjaE6MyylMgNVdYZSYF99ZWVVWaDn9NTfXDqrNq7xknlC0ABOMFQz/GV2vrHY21Ylt5IjpjHHFHA9xGagwIAIimpmZdTzpgqryo6nKBEE/SABQwGNypi+UhdBACG0sLAOLS5StuUO1G+hyQhq0D4RnrOPFlWwChuUjb0Vc8F9ukDrQjjH9ru6R7xXOEwTG3Ex1ATDzGQFgoQoQBDhsfek+cxDwIz/uQVkg7LCi5mhNNPGxZCBvKRfkJG34vjJ8YNrGM9l3paNVgnI9fBvoQxCEdyrNRXBwgKBynBELoMV6CXaORaGjETzQIRJcJwqocPxodQg1QwH2AlHyIT+MBEKAiYZ5++mkbEFyCTdrEoSMBHgg6oAPYEMfQWROWxgJ8SJNVIMAFR8Jd03AQsKIABOUEPCAAhKOccBe7du2yPAANyr+ZXBh4PGlLwBSCtVp7EORjK2o1EqtwVmEYsjGQsS9AphssoinPoO6SxrgNu4hgX7Ae7QuxVxFtLOqbPRlXcD1cysOigTZjLPEhLA4OAvEQpJvwjDPGM+2sprBJCqcxrPFbojHF2GKc+TSY3H6Skz+OviE+jtVsyI8nCRJ3PdvJCrZOf5bDQaxTEbeyvU8LxAGCAXxFJwJCjFnVM1Fg5yDmsHx8R8zDRIFAsDJiUjEBmBAQZNKAgAe2GiRkcECcgx+Aw0QCIMgjoDsTOXAOTDbYQdKGcIUnoig4lJMnTxonQX4AFlwKeVBW2EDyIg6/iR9WKvdpgw3pTblxPNcCIMgLYhfy5fdmccHoEYBLkPssXnwBBBuQjA9EZbTtQ+MsSEnDzi849AWR28PajHGK6Io8v4tuCyA2b6//5Cc/cUloMUGgw0APq20mD4ObJ5/gj18g2lSd38QN8fkdHH78Dn4hzfCeZ3jPu5DOwsmEP8BCmERZ7sJy8B6XGD/kbS82yZ/Qljyp02pzEDSLsuIvfzalW0rxE4am1dHXeXnVXW4aC8MvL7f1CM18Xbl8twBi5dpyrVOKcxCIeDYjIV3rBlur/NYLILyB0VrVciufjdgCYcG2UmVbLkCwIEIqwKIICQG/A21iXoRFYfgeyhnmTOLvxHhIJticxr4BsSKicha9iemFuod4Ia3v6nMLIDZoz4fBznOtOIgN2hQbrlihb0LBlktMFsYP6WyU53Lr87ByLwcgkBIgpkZdFYDAQhnNIoCCdHiPCJnvvGcPk/ZkrxOiz0IXcTjGdSgYUBfEe3a6rwr65VdfWRzSY78V0Ajib/Y12f/E6I5N6S3n3BZAbNBREIgIzy2A2FidRJ+w34FmVKIGzFJLaX2qNDaiQ7IEUV1JkFgOQLAP+cEH75sxGgCANl2xDm5EKQYtSjTndu7cZVd5Ykz3T/7Jf297ob/4xTtuW9U26xf2mNB6PC1V10ikzqyhAQHOLUOZhXdYQdfpHQo07Fvu3LXTuuOOlG3+5E//1PLciP2z1mVaEYBgwIPsPBlYQU0LwsYHNi4+4JgYJt9cRMgZJo3SwDEBkYknSb/9u+ZoS5wRE7UDk4XVzmpqMaHeek0GjkwgjH3oU+53sImqU0s5/hrbAlRD0fPnlFfC0KF+z2fOtJt4R7+jJYR/qRQaBrQ6oz85XZXxEOqCZg/2FxhDcaS2aUpJvZZzpsiXVSFjByLD6pAjwGkTtKy4CIXyeU2mJCkloMaqO0t0PLcC2aUy2HLgNyh13VGtMlkdEpc0M+SPSiontvryW5M/9A8aUheuXNN1k5MiShWK7y9noV6MfWxGRmInzWKPkao6Bn+6lbZhDqALtREdYkbaIz5nH7OQywEICDgWypzme0cWysTFyIwnavc7ZSdFe54/d94UZV7StaKMwTNnvjGFgY7ODrs3muM1PvvsM13necJhcFemo985QgMuge9YP++WwRr3mnAXNKra9F+z7FkACO5v3nIJHETYpKaxaShc+M6E5MOACc8AAoRhIgcWjxUA6qWEw2YBf9hAwnMkwvTIkJ3smawOnNMKwIi//JnQMzJy4dTPZB2PjCHSLESxp8NlVtZqKkEwYxOK8HzngbfCEl+jmtknf0095cdNUPE4+raZHO2H4wlBWQuAiOoY7N+//4H0/rcZgeBYbojdpNRF0QhDOQAizQXpHBfAGTvNEgXAlkNsIf5chJOjuxE44prjBohz6OAB2bh02zHfaL5xq1e2wnBtIh1IHUn3yrXruj8ixzTiWDHa/QkKx3HgdpxCaYne66atWY1RAQj2GYgTOBIcA7Wx6Lirq9uhO4yv2NWOHKsNgHB3BXYOtCH5YDTHuMTm5sCBfa4iJou2Bl/CHy4c+uSzL22clQqwKspKrL1uNDVbX1XJkhfC06k658hQCgDca/c+5NowpT+D5lXIbjnEeDlhQ/rLeTKn1wsgoBnXrl0zGyfAgP2CGzeuu/379htxN7sSze8O3fPBJU5oNFJeVPQZW5FIxK7krJUVM+MTuy60MOEU9u3fZ8exc7YS46ZU44nFDgfzQf9Qt0d9+5VXXjEty+W02ZMaNs5BQMBbdAkLSI0KKqsuWC86BBVWGp/JzooOAMFeAaIBqoP6xOd3mIRMQCY+nYdqLB2XJeTv/eqPLiVDN4zphq9p3bOaWVljoJEiwBhpue7ScqVbX1TiZlgdym+09abLbdjrZnXmf7LSmdWBdYBKsvTv04vL3NSg1GbHddWo3qcXlrjJvi6lneuyq3e4lJy8GEhsvu5ba4AILRTsCsLv+z3DYoH+5TgOs7S+X2D5h/ALg0xoFc75NoAMltkQVsYbK33iQEy5WAeuJGivLZYW7RW4EogbvwmH4zvjmvG5kFMIYRaW60G/mQs3mmSMp7lQpXsmAAH8EDvxAeRIF8BF/g2nwCVDzBsVxepE0SgvIAeXwXzD4cd8CY7yGseBhyL5Gnk120cpe0j3Qc/1BAjKFcb+wjIm1jeEWcyPeHF/NbhfavnUgn/i+OANAMLhfxW6oxpL5jDWfKzv7t84QNAEGKrV1dXZE2tlkJfByXc2dL744gsDAlZhWFJjM9EiUAEsME5jkhAHUOE9mginTp0yIzmzoRC7P3T1nHEO/Wc+dxlFpS6nfo+bGuh1afmFbkbyxtScXJdRWilAGNdAmbXwWVW1bqytxWVV17mJ3k6XrA2kZE2o7Jp6F5X/yM3LLi1PoFZSIbAZNA6kYN9RAU2p51A2Yf+GCcCTPlhtDuJxmmjhZFtOWoH4MXH5kBafQMhD/fkdJvf90n9QOXiHe1ga90s70R8QtcuA5OltKvwR0nBDuJBHLEv99n6+fvMAYUBz46ZdsITo46mTx437ASRyNA94FsgYEe7Lc0hjIlypNh/37d0TBxXLdAX/rDdArGBVlpwU84uPSTo01sL4W3ICT2jAOEAwgWDTkD8jJmJFgx8aBbBfNBjaBHAXiIyMVRcghPdwFqAuSAxw8D5wHgDFsWPHXJbkzNE7zcY5jHfcERBUiAsod9HbN1xqfpE4Cxm2SXwwp46aFVik6PCsyd5ul7Wt1jiHie4Ol1FW5aaGdZyHxB4ZRWVusr/bTUs0lSFwACQm+3tMTJUpUMHPi5k2X+8FgsZzrQAi5Ln5WmvjlzgRNOhPREw8EX1x3hCc1M7Gerve1AiVuI0+zUPmFJwHc4gjH/I1J7jZD/HZaq1yv4sAsfFH0PqUMA4QDGBkf6z+AQkGKYMSoOAYDVY4kUgkzv6iJsZAYhWEOIkjLkiD3wxo/JAjg8iEYzAjp0YcBHHXmspNDUknWZfIMPDZwExK0dWSoLjETBB29ihmtBGq2aSwAyaWmpvW0eHKBxET+xkp2RJFsKEZHTHxFOmzkEsrLDZOw5Zv69O2j5VrINZrDRBh1ftYhd+K/K0W0JC1+UH7BoAIoPGtwDEP+j6ESfx+v/Ar5b+RAOJh9U58n/j9QW2x1HCkQVhc6Af7sUp/Hjevx42/WLXiAAGXEFxgr0JDMmD47mWoftAmhvEDfl4EEApKejRsSMd/Z0M5lpNAQTvXRvCDHxNJFN8HiHWOSRFjZ954IawCxd4pA4UNCcb89WDzyb+L5bXJHqENedK+ADYiBwCb7/gBvoDwWgzeTdZ8G7a4DNulAsR6VWKjAASLTcoSzsFa2B7MDfaWmBPQIyQW7DMxLxZztDsfpB2kCT0LjrT4BLoW/Nk0x488luPIB7cwvQelQV2Yy9RhuS7Ui/xYjAe6u1idlpN2HCAQCW0RmuU03eqGpWNxPOn8LYBY3fZeq9TpVk88ZC08OeIGRntdfmaRS5rVpFYhUkTcmOAtnd2uUBpcKdqH47hxNADZ+8D4Cy0xGxfa0IcgQBBZP7V09LvS/Cxp/03axjnElTmNWqg9xaGzaf4wt54AwTiH0PNB04gP2mYshNA+w6HZNIEIWvU5q9OhDxw4YHs07e0dJt5GGpEXM6bjHgcuFoLoXr9+zb5zyGejNKQ47w2woA3ZQyVvToDmiQSENuOuCDShaGMW0bQNZWMviIuDsK0IwETZiEc4RO2ohXN5EXUIInjCQmtxfKde5EeZORgS4KJc5MM4CUpDLA4Jh4QH4ES6wxP7DkCM9tCK2A7W5Bri0PdoZnEHBnlRB+rEB6UQXF+fbsyTwlAAlqgkNmjdsQBH2+udd97xZzFtAYS114b5swUQG6YrVrQgASBm56bd9Y5L7ldnfuJe3fuWK8/Yrjslml1dZIdLE0H7j7/4g3v98B63vTBHxmKcTjupSc+qdC6u6ouaZ4Y0/Sory11qepb7N//vh+7vP6cj7icGDEQsjiZ7jrSqAJjamm1GkB5WofUECAgsN8pB5LBb4MpP7psuKSk162oIIkepc5kPN7F16pj/cinQ7N2zx2whOJ6+pLTELt+5eOmiEUIuCqqvr3e//c1vXJFE4VxmBhHGD0LbLmIOoHD675tvvmXqrl988bmOty+QPcuoKej09vZYHyBahyB3dnjVWO65wY4Co726ujoTx/M+TSJwjPqee+559+GHH1ocCDMADbBFlS631AFcbTo6n71dgCVTH57YYxQoL8CFOyWwuYHjOab8SOfX771nYLIjEhG49Vi4oqJiS+ec9pJzpQXIhUUA3FHt/17SQafkgSo67ceFR6T9yScfG5hga8LR+Jy2jYr6UGxL4bzqZof1bQHEw6bN2r7fAoi1be+1yi0AhPgI1zfa467cPed2Vux3ydMZWsUl2YUxU2IHPjp/xe2prXJFIg5sVrPCYw+PVSTiDlaXECJWoNxdkZSc6v54ptkdaqhw2ele3MIqF66DsBgRQngwgHyYW0+A4KoAuAKIGnufI1KBztYKl5OmMbxEq4t7qHkPkf/p22+bVtczzz7rPnj/fds/rdWK/7KuBpgV98UtcSjVHDx40Azn0MjErqJS149iiAfRvXb1mqvXKhtNS8KdOvWpa2m55fMSYSafVnEdHG9fqBvlIiLKEF6MOuH20P4skj0MgMM1qS3NTUbo9+/fLw3P7e4Pf/iDcUGndcxHgbgKuAKA7sSJk7rP+rzuI2lyxSLOadLOBBRYxY8MezMB+ipVedD3aM4dP37C+vPLL790x9UGv/3tb3Un93YZ/e1xP//5z+xoEriGgwcPKY1hu2OHBQJ3laCOPilO5Ie6sY5xc/XqFdMyJW0AgkuvADXMGwAJHGk9NkBAzBhUPEG3IANkYOIfWBvYFtugVhg2ljGKQ2vJthA0OWzfwIr17T9sXiuAxYm/JT01GpvbODa1k8VGxfcw4gFjXxQeVi4Y6JnGlMqXok4l7Qc6y0uGfSrztzSjSFd5h/onqaOtvKoY35fjKJ/4RBsE1jYIHpQGhGE19yBmREgGolOua4ib+CSvULasOuNO5UqekIyUeoq1nspEJEmz8QexCX2vokLkstNcWV66XcsZj7/1xVpAQ8X6lraancOGxMuqU5Lm2X8FsYubWG2mChjgHAgfd/wgITnaH/Bg8o9PCjykSs51qGGBQRiCoghCf1p/4fkAt54AAfG8KjBAlIaFO8SRS5hY6Yfb5RCz3JX9Far3EDNWxtwOx6VhEF7ed3d3+dW8jCQbd+40q2lW5YAqYjziAEaslKlvpYADjUzurcGu67wIN9wC7QVA3JI6f6G0M7FnYaXeLiNQDDnhdLgQjfQqVR4u0+qWISmiJziSQ4cOG7ih+EP5+CAyG5fdFkalxSorIi9EPnzIi3JhJsDBgoBknYCHaw5Y8XP/DaK2D3R3dtW2KhNhcbMdnA4Go3Ag/f19Arta43jgLmgTAK+oqFD0b864B4ANbdXTp7+ydsRynPu09+7bp/rcMJEU4rFPP/3UAwSri0SCHog+xB1/PgzEMPDCRgiEK8jEYGnRdkK+Rzg6g0rROIRH+jne2eYmZfeQG+HsEwbyrPPqq5WMdjUwBF7+SjdJgx3wgGhODWEQJ2Onim0GLkY8VdkJGcah9YRtBDYROdsbmA33DH/TiFI+ANDUsG5LUxkwpBu9fdPSz9t1QOH9hCMdD2JKQ+Ww2cVbARFlgOCnSSUXIz7iUE5ACkO+8R7ZaKieuZFdbqT5qsssp6zavFccazcRfgMz0o05r5WlMIAlIKu0JjH+G9NVn4N9Lru2waUVe62y1QSIyelZ19w15i60jbj+YR2Ili3ilEL/+GZIUt9WXPnMlZz9yPXvPu4u73tdEzfFZWeo7KpLe9+UKymQnFar1x0lmW5XleS+aff2Q6jzk/SkX6dmtTBRnxrxVXsxT/Rr0WrSnn4usSk6qT6PCnAztACQtp/GOY40mchYgduwVNpwF+laVLmxCY01gXi25Myp9BHH2Hi9fQBlJdx6AgRtE2gK7Uj9aErahEVIKFtoJ/9+HhCJgyOdD0VEAcZDhw6JiyizuNy7DkDQO7Q3tCssaImHyIf8+ZBnUMzhNxwZbR16Foph5SUN6Jv1uzzVX5QTx54CF27Z3e76DY1knJAnABL6jvDkRRrkC72kbISnTDzxB0TgDAAlOJZs/SYcoAE4BJfYDqRNfPKE++RJGYgHICcqAfAOuk19yfenuqPbOAhekCkvQRwigc6or7LBQQTCkBEVAeFIAIRjg4d3gAzvEVeB+L5T/f0NbJSYHcStGxr8Uo8VEQQcUrJyTJU1raBI9hHDRuw5cmNOjZdRVinDuC7rkCSh9djdW2YMlyXr6/HOu5YO6q7J6WpA2UOMd8pI7+QrbrxbbBGNrXfkBdGfFtuWlqczd7QaGWkSeyggGRURz9q2w6UXFFuc2Umx7DK+43gP4jFJU8XeAgDTAhY6HiKekp3n8ncfMGIPyEwqPyzCo3dvixvJdsXHnzfjPVRwx2T3kV5SbnYcM1FdwFS1ndFuthypSmdS9dOocKkKO6sOg5uZnRIR0HfyxVo8szpi7b+aADE6Mes+ujTo3j8/4EYnZBEsWpWRKr377BRtpM643KlR9/3f/ju3s+OM6y6qdf/Pn/5bbQymuooire7GZl1rr+xgBCileanu6V157pUDhS43U5P7CXdT4qg6Rzq1atc91FmFbmBs0JVmF4uwQYRihEsUJQCGut6ISlKSFlZjzW586CPn0k+61k5k7iMGBGwScoMfK0Jk24AEc6tOK9TpL6+46DfX3NTrR91wLvcwp5kFekV5qc3HlWjuQIQN8FYgQYgQdAGiBe0IxBBiRR6BmK1UfitQ5K0kYi0QvzCI38jSWP1//fXXJtfDKprORDaH7A52AyAAAIIlNcfyRiIRAxMGAYCBzA90Y8Ppj3/8o3vxxRctfoYQOCqAwGIa47iSp1913Z/8RvYLBS5VRm7R202yghbC6XfhgRNuTMZ0A+e/tKM5cut1sJZsHAARCHS67ByGLp9xOVqtj3fpkno9OZYjOS1Dx210W/Wyt9d7AzqstLUi5wgOM8YTMMElTGrFP3jpG9liFFsczm/KEmcx3n5Hxnc6BkQcAOlifJclIg2HAqAhxoITANSGLp+VtfdZAdNLArNuew9A9H7xocsUwA1e/Nrl7txn6WPLkVu32/Ie72gz7gL7DfLFqA+jwIJDJxSvysB1+MYlA8y1AIjxyVn32fUh19QVFeusw/Em5lzv4LTbs11aGgKPjDndO35TdfnmY3ej4pCbful1rcQQf3CyKathNbkIIRzEbnEPu6p0HtMTwEFMazMZ4p6SdC/YqcYuOj3mBieHbCExNjPuMlMz3Pi0rKDTtEjIkGaSmqRnos/GYnFGoctM4YbDBA5iVhvP0zqbLDlHbej3Cyyw/iB6AnRwiFvYqEzT6nUuqqtNR3RmWaEWFOKwPYFl89OvPi3CY/7ZAojHbMAnKHpczZXVPpbUEHfEQrA4DBSIPJwCKI/MCu4CS2lkbgAC7wkLqwV4IGNDdhfYItgYOBDYvMBBwCGMtFzTKvyQOAlNIA1uO2tJnAAiFlbvEGdW7GNalUOIAQ3OYZoeHTKCPi1iC8GFoJvxHWyRwIPfcBOsyOE6sLbG8npam11p0krIrq3XhNR9xDqSY1RnPxngkK8mJJyG+Hov5hEosHpHZAVQwZEAKhwDwqRkzwFQG7xw2rihvN0H9ZQxn9qKeg1c+Mriw42Qd4a4CDgbf4xI1EAta1tElEBiJ+XL2VTUjzLkNu5TG+je5ytnVY88l1mz+hwEIqb+4XHXM8RZV5A2qcyJmKUk+b0FdbBLCZwNwkKxtrDH5mgPvWcMIQrJy0rV5qpWtxJ5QOhshah3nFOEKIbfYbVIHP+h6f0Kk+Mr1tPNaG+AD5g3PoNuuvTgk7Xa9ZW1vxJ4uNFpqTsmefFhRoo2ESVqIt6UQAVBRFqyzi6b1XhWHYsEEKkCGX21uUI7eUcu4TtD3n8nTnB8D/7mF48i6JqPGoI/9nMLIB67CZ+YBOIAwaTFkpr9AjZ5GCQQdog/XAKbJIiJIPw4gAJQAAAQJ4UNHURRpAVbSVhWNgE80uTPURsQSkAC2T9nMDH4ObnVREwQdgENVtLsLcwhahHhtIP3FM4IuVbvdiqsygbHgJuW+IbZB5DALTBzED2lwMZC+Bc4k/UP9JkYJ1NHfgA2lIHjOvjOWU/JEvek6mDBKZWFsiGKSlKe5pjAqs+0VPEQTSG+YlXJngNiJkDL9k4kSgPgABdAEG4FkKOs1J39BkCBPY0ZcROADocZzij8wLkvTXSVIfEV7bqaIib6u1O69xPKF/1u5BpZqn+PNsrQfAl9CJGfUH9wwByEy+SXInCUDbGIHRMu0JvVxqppQoiAQcNStNrlpEz6Gr1+QAC5LGkQloPvcA11EY0b36fmsQ5/IPJwDtBhSfet/DMCOhXd/CgSdYKzSNXmsm8vfL0jPm/ZhOYdnyBiIgRtjbuH6JvPxviDrJ/9jJUq36OImBgX4RMWFPy+n6Osie8Zr4nx+E0Y/AgX6jb/nfzm+yQxLfJMTD98D2FIExd+h7SDX/gd3t/PP4QL6RNu4Xf8cCGtxPcL/UN6FuER/8QBArWn4EIjUggyoXH5HhoCv5A5/onvQhr44UI4+64/thELIfUvFcAPRAtPnFjaEFe+m0vw9x4LGohpS3axcLFIiq6O8xmFaPNPhQVsiOc3sUnDE3jzV0gf36+O/TtPFixN8pNbWG7zpNyWvk9TFMLymU+fyLQtg1VlkPPffXvrh/lbW2myzmkArjpAqL17dbz36IjAVQ4ggAOwjVMV11QMpWHRLyUE+hQijnZNgbQ6IACAA8252csoAABAAElEQVQPQPAdgkgYRCOcaooqJo6jt7kbgnhRxePmr2HlaSCkBHZsrzGZugVexz9q9SXlnkj4lxRBgRgzpimmPGizjeLCdGOeJ87bxy3fcgDCtw0b1VO2oEDNcu/evVYEypUIrpSRMntA8/eH4IdkA02kbdu22QKXxQvpEB8NIuYSC1/yIj0kH7Nw8pqPYY8kAEp4T7rE4z3x+GAMx94QUpfgR5iwiCYuv0k/lIt80VJiYeWN1eZsgc17DPp4sjfDwpvyEpc0SJ+8cYl5BZVnwvOe/WDSJX3yCnEs4iP8iQPElh3EI7TeKkZhEOB4MlgZJKvJQfh80N5gJe81Yzy6zhMxxCJMRv6xKkZ8lLh48GqxgGhwfiJ5ggPR8eIVLuwhDZxNOKXJxEC0xT/ywZhoXgzj0wttQthEF/zxW/guMdxG+U55qf29tdgopfPlWMl2XA5AILJGxRR9fTSDkE5A8EijWgS/u6c77ldQUKjvI7oHZMxhMIakgzGIMdy1a1fdW2/90ObNGdlVDEr8W6jwLHzQRkKFs6mpycYk+66trXdM3ZO900uyoUDMniqul6tKWTx7Yp1itgpdUmPldF2M4YLkpEtEP0d7s1gmV+lmO/Ztb91qkQhee58i9FxM1SFLbxy0ljtVeAIAPaoTEhkObGQ+IMXhkqPDh4/Yvdmcos3lWNhqoB7Loamc9ss9FrQBi0zIBX3GkeXcnQEgYmDI3TyAyKO6LYB41JZb5XiB6PFcC4CQPEz5SDd7rEUiroiIvz/PBTGQraJEsHkiKmLAM5ABrdY7dzVoJ1x93XZ7z6RhAlJmNle5FKi8HDVdTUzFZUJExUW06QRTDHRKZGAEp4E/YqaWFnTCs2XtWWJ54I9jVRi4GQ84fkLgRxuh7sh3e6fw8wTYk2HAjDA+7vqSZspJ+6xvKaxZF/0DoVnJdloOQHAV6C9/+UtbgGDdDBG9GLMCRqsLe4dh2StgA4DdAteQQlDhFlhAlevyp2FZUyMVeOmll2xPlDtrGLM8sTNIlyIMXC32FnTCD37wA3f6q9NmH4ChHJbPlSKuX8uqGK1ORKr7RHQhtlhhE480AK66OpRxho34c7EWdS0tKXUvKu9vvvlaHEuNGe2Rb5XK+MXnn7tIXZ3DruLFl140GwhuT+Q+FZSDsK7esSNiqqzfe/11A6e//Mt/b+DW0Nhg/dXTLatu5UX9ibtr506BR73VAa6hQYD3kZSDjp84rmNIDlo5F+3oJXhuAcQSGmk9gqw1QMxJo2Yiqms0Rz53M2kvu7ZOqVfKYYCTow30DBFxVkMQ7MqKMleuFREiprtSSiBMqVZtgAHnz0DcAY1LslCFKHNDHVeVFkmxAavO3t4+7WkNCgDgKpJN/FQqS1LC3GlrN/Ya4MAQqbS02MrR1HzLbpaDcAEonqXmsELddMcejvwpB0QXcABQADFEABAOjmBAfFWhC35Md95SXfs/lA1wiNtNrH0RHpojC4H1AgjsqCD6EF+IOgSaq20BCpRjWNGzN8o+KUZu/QP9pjBTVVllIh8slQEIxsLzzz9vHAaEd0YLFEQ/Qalmm0RN7TJ24zgNwAXwYJxA2NHmxAiP+6npL/oKTgabA9pmTPunRbKo5r5sLKvhFBobdxrXAdEmvVLNgc9OfWZtzTjkOBC4DOrDyp4VPuUjD8AMjmVQ+7x2Ba4M8XpkYoC1OAQfS+wCgSUAc/3aNVe7Xbc5an4hTgomCXAsHLExJg5j+3YZ2UmhiPS4khVwelT3WAABEWMVySewpDQGlYLnYdOXzWCuEKURvB+WzxJhCPkWc2gwafnqw6tjjAdUQOTx5vDDKX0caS3qyF+aRogsYMFMQ0gbwWyAQ5Qe5kwrR/FNsynkSSTYOZWFumFnYV5MKOqcGM7ePOCPymd15Ul6yovN6VCftQYI4yDmxlUvWZbO5YkVp7/8XkTYsLRVr9qO/QKsWykjZ7vQ/+j9G6ch7oH+5zuTAVsSCDq9xYSH8LCfwSRHjROOBAIP10E6DHyuFOV2OVtZyR/Hio949H7QsqK5AyHDn/LprT3DxGZcUk64E25+C7r3CrguTkUx0RzlDX0cVuyJv0Ph8Ev0D/XhfZhzIexKPdcTIBgDEGMAHnCn7vQrexLI6CkbfvQ7/nwnLAAS5Pb444f4h+8Qbd6RHr/5DgARRgmYPyDEPdakw7jFkRccAcCCsRtjPSw4yJ/FEmJQ7ihHDGVll4gsXwshygXnw7lHHJ/h7ynX+UsqA46wEG4rg36HJ/VEfEQ5yZd8AA/Cs1/H/p6VR+/xpx6EYc5RVus7jXnmCmOd/IweW67L/xMHCBIicQpCglQwfF/YKWTDJgjvMRPH0aikwQqAy4HUoqai6q2fqz0oiIiMNF2JWTuLjZUGEidP4iDEaPpAmKN3WqTmWuVVVRkIYvEgvqiMpkvjCM0m1GO5Qc4IhuKag2Lg9AAYMFwDmFJ19ejIzUsWPrumzoOWBoPJ7lQHs25Wff3GuOIqDppMaBVlSN0W2wl5mhaSDUhZb493tbu8nQdMrXVUth2ozzLYIPB2/IfSsONEKL+IP++wug53cQMKZteh1Qj+E7LlKDr2vAcaVYF8cDwZLLT1au5BWGZr+MfXT70U67I1zHpds6Jb6U/1rJ3eyaSGu+EYBAg+75j0yNX5Tjv5z6xNeER+xOEcIBY6nE0EoVpJsDAiw/hdoc6hvBBACBkLSNLnGcAaf9xK5WeJrfMf6kj/UU/qtVnrFgcIBhlqrhAhUBRiz2YMltR0MASKytLRAAhH4FJpNpSQ+6E1QBo833zzTbOEhtCjYjpw/guzA8iujjiMvzKKyzToJROWSipEHw4jXcdXjMkSmlX0pNRPIcppumOalTkGbalSI4Wgon7KJUIzOhfIbpdTWGwMUEXlTmostFUw2SCUmgqq+EOWAmaRzdWlZpCncln6Uj+d7O81rmJO9UrV6ZAAAwCDqirlxyCv6MgzBiID57/SvNbk1so3Kgvq0udeN7VYwCdZYhisrTPLtgmYtFlGnaTSCjiN3rquZ75UWrlatcjNCfAwFqS8TH4AiSNDio8+ZwDC2PYE9MkFiHWev+uWfQAIxn93d4+7Jpk2ezVwWnBGnD/ECa1dEjEw7+CwSjQHIThwQYj5wFRECxxlfejgAZujzM2VclsAsVItufnTuceSGmLPeeicFIgcDUtq2DHAgN8ff/yxgQMAgiU16l3I7vzJgFdNFsgO+70Aker6Tn/s8vccMsKOlTNEmiMmIMYQX7iCIVk02xEbiJg02IsOPW0WylMCgIrXfuQ6fvu2GZ4BEkVHnrWzjiDMmaXbjKAj7il9/nWzj2CypSCikB4/4io4E6yt+8+cchzTgfEdIJK366B9n9YZS7niBrJrtDmk+63z9xy286KwcQDAKGOyAKP3sz8IxNrEATXq/Kh2Awju0KZ+GPZx7AZGcsPXL9hNd9hxAHx9X39iwAdRgIMAnAAIrK+xzEYMN3Dx9LoDRHy1uvnH9YaqAdyshpstqAJA8JsVZlhphgKzmubj/QUaCscBcNY3Cg9I2MpbaSLe8GKOlVvtU44tgAi9sfWMcxAMQCyp4RjgIhikcA2sYhAn8ZuND+RmWFGzYcR7AASAQMUM4EDex4mInKUUbW1GSOy6P/6Ny5dRHOcVcVAeox6CblbM4hgADOTxiHRSkDNiRCdrYs5MMotlGc8BDN7KWZyMuBvOVsJRrhQR4hlpEhQcPKHzla6ZqCi9qMTEVExIOyBQwMQhgZzvZCClFT1sPuIdfmfvkBGgiPzQ1Qta7efa6h5REWdBURY4h8EL2uwSYUf8BaeSv/+oEf4hWTynF5Va+naWkiYx1tvZVdvdpMCHc6OyKqtN1IRxHG2DdTXHhGCtTd0Gznzuik+8sK4cxBZA2JBa8T+s+IOYIQCEqfBqHojmyyb7Xkd4GF+c1jdxbpLf+mkcBE/S0mknceDxfhrTBJQLwERaIT3/5sF/NzpAIMWgPR8mW4c+EW4p3FUAa+ZAENnhF/qN5/0ccWizpeZ1v3Q2on8cICC0N27cMHERxJ/GARxorKamJrOk3r17t4EFDQEY0AFGoGOiJ77TWAAGxDXa2iQQGDH5esG+o0YMIfgzAiD2KDjMbkZnHiHf5zymyYEeA45kgQYAQhqER3SDNTLhcNMivhZG4iY4DMQ4dlyFNnWYFgsdHAQcC2c4IbLi/COOtqAMM2LvEQelaGUPmAFa7En401qVktoBsRbOxFGKb9bb8geoNCrcrEADrgXCn6Sw/tgQbYgrnWkd6YH4iTYD+BCV8Z66ASp+v6Nf3M1nrvSZ1+L50o44nvQFbf0k7UFY5b6Df+hW+hOAEH/gxqb8WVYAAQ46xAhO10GJuCmdczU57Yk+R6njCDoxpTGhd8W5UgrQtNBPbUxqrOjlmM7V4iTeNKUBgHDoIp+luvUECNqG/HHMGdrDGxX63/hzqQ0SDVRRCcMc4cN3nP/uZJCmOS/ahKQj1D7WzHHQID8cB5MCOGgXYRcBLeP0CNKEHkIHw1xMzAsDOxQrcBwz1NBQf095ST9weRZok/2JAwREPTgaB0dDhCffgz9+oTMswII/9i7Waay8lZAisGl7b7qEC3kgf7VNYnWlxaezSYPpoFEf4pKVxdFrVkiIbewb4fks5kiHgaA8fBw/mCxtK5v3Jyppf6tuli5p+PaIv7fs9Ic0bNrG/lLdWFkoH98tiEL577F04u2hskll1DSY4vF8GMrDINsCCHpn8zvGAf0JQIi+uyGdhHuuJarLg9AEE2et/q/WCbn1ldL8U3VbeybdtfYxC49HusKMjrN57Vxhboo72ShuV35jQojPro24cT31094DFId3SFe/UpcF6QBG0luKW0+AQA21ubnJNumxb8BmBCO3EtkWIL5mYfr73//OPfvscya9QJqBFAPJBRv9KMlgi8M847dpv+k985+wPVINBTQAAQh3S3OzGc+hEQSQTKIQo5bi0h5UY7nvAZVaAIk5yGGkLNQoGxbR5HP9+jW7bwItKKQtgA1xkLjQ1+SFZGYzujhA0PBUdsttjBYIwLlWAGGES3+EU7YyZbFq90HQHCJG+m/+DBFGCStbLqbhiG9WqltuaS2QCBBaithqH5CY0mGJvEvTkh/NXo5Kp1WjIvJjOk2XtYm1vTynxSkAMHAZeVlSE9b+9IT8+oZ1N4nC0S/B5WTKzkSfNPktdXqvJ0D0SskFQzX2OCHALbIxYEMe4zHsFl599TX3q1/90vZFIchciAMh51Y1bGsyZMB2+9ZtAwOuHu3q6o6Lw7EtiEQi7q64hKeeOqn2TXKXL1+2k6YBDmx4PvvsMwuDNTThubb0qZNPucNHjhhHcerUKcW5JBuGF0xSgup2s6yeGxt3StJy08CnUjYZABXW3BwTQl0o/2akr1sAEWbSBnuuNUAgroDAdAxMuba+SVck0UWZ7nbgqG8uBWI1OjyOnNU3VK/CcrR3o1a628vW93C9DdZ1DyxOIkDQmAAtoiGA2RBBBJ6+tz2KWErECY6vdIFsDA0g4DhIg/4jCUNyBSCMxdMXOIoMAQ9xQv8R9H5uPQECovzer99zR48cNSUZQALr5lShJiKc733vdfeLX7xjx1lQl1u6KQ17B8TfrVKqQeMLUTkLXqySe3p6XUQEmlV/i6yXkRpw/eb33/i+2e4YQMgqu0N3THMjHNeNVsvorL6+wcLTD3AXb7zxAzNM49gO7nd+TkZuABdAQNq1tbXGScDNcH0n2pxtd1plnX1EYJVvFtWJEpj7tf1G898CiI3WI7HyrDVAAARfXB92V+6Mm5iCVSsr1f07pIDQNe5ytVLNz051t7snDEgQgeTpMqEjkWy3p8Yb/2zQptxQxUoECNF0Xc4059r7J92wuAj2C9Tk1v7poubsG0yLJeA6WIABbg0w4JknDmNbMde6OgdY941ob08IAdhIgmL3Ug+N+ePVcwXwNaU6ODFdG7YK/zC3ngDBNZunZfnMaj4SqTPRULuAgUP2mBOIbsJ+AVqWqNizd5Cl/UdssfhuRpgSHyFKCjetIerhGtJLly4LfJPdM888Y1wGR3sQDxEWaWCkxyGS7D+w8icN2iPYbHA3NbYoNTXVArA2SwOxEvEIw4fzkKp11hNgwflMlAnjts3IQcTVXFdaxIS8jg6lgdE68KsiWeGqARMbisbHhY7gXSLS0rGkdb+NHtJNTCMx7YdNhI38nnrheIY2WM1NaghPhzgHHKIlfneKm2gQEPAdwsJq9WbnuIk89goUCMelQHAYT6KzsaXxxzlOLM3TUvz5VIvVlbCxHlM7+fbAz26Es1V8uE5yfg9idk77CeLM2nRdKwSe8CbWU0KIiXLUrmpy168b/aZF/YtydB3kJJyCrvNVvBMNugBLWXHj3w31CyCPOCkdpFEaiJvGJmfECaa5HeV+H2KjAwRzHeUY6AQ0gfkcAAu6wIf5ED4hjBpvsW65xw9LaAg5aaCNyQkBHBeDIx81tblwvlc87Zg//UO+PHnH/iLZkh5+OL5TBzgPGwekG/vEktlUjzgHgWEciEfl6BwqzO9gMc3v0Dl8D5bXoaMg4HwnPoiJhTWsH53Ld9JB6wAgwtHAAAefoDmAqixnipAO6YU0ec9KAcc7ykc+lCN0eDgDhTITD0fe5Ev4zeaoG45naNfVBAgmCmb8yHMxziLPyakZrap0L4ZmDu8x39coUKkk/9amJ2caoWES+ouy8s9PEE8kqYM/qE9aILF39H18Uqma+JMWNWZSMSa4PIj0uNidc6CQ9Ya0mMmmNSYPjkCAijIhrcn0B3EEiVFe/Dm2g7FAenagn8KbZoye6Qo7KqtlHMchcFCglVPaKaMTOvJBmmmjUkPOlh1MflaeAFEq3yoAYTxw+DJPzciWJTrkMnUHSbY+HBcCyRmUX3Qi6mqKq1VHNGHmAYJ2nBT4oqXEFa/BBUJFlfkeo2HhtYmTABKAWQ/dYkff+D0iAACCRPnIS1/FmXgtJjgPfj/MBYJMOivhIPjWp6oQfU/6PJmX5MFYwK1UfitR5q00fAvEAYIOu3LlinUkpv5oNWFJjYoXHUyn0oEQYDoW9ouO5Ux02LH6+nqTuzEJScvYPD2R3yFH5PgNdv7JgzBoBaA+C8t49epVd/ToUbPkxigPlo/8ODALhzEelxVxZzaAgLwRjQY+lAWwomyUm/d8J3+AijSC1bev8ub4SxvheK4FQHAE9522u7oTos/ak36G1d5eU21PSBXtTVtzFhMGW9z1gNUvclgOwevRIXy0Ob8htAADRBoizwVDubmyTo+dqRTYcp7cEVEkkQKH9VVVVuhIZ1mci1j3aVwxxjhDiTEFm86lQ4QvKSkyq2LKiAMECMORyT6NHvsNEDTU7XB3dHosIGRnPbHyY3mttq2t2eauXL9pdSqUrHjXzkY3plvkeqN90gzSYWwZ+cY50BujU1FXpvumGV/TAoCJmQk3Ni3xW1qO6472uixdKcrFQnbuzsSIy8vIE/el+aLb6IqyCiQy4niXeYAgnY3otgBiI/bK+pQpDhBkzymGwZKaO6jZFIJYQ2D5/cknn9iKnM0ZVvpwCGza4PCDOKDaBXFh8NfV1RnB5ggP0gVUMMaDaENECA9otGjziO8AEps9AA6DFOJB/oAUeZE2q2jiAEwABkZ5gBnySAgIQAUnwkmH+/fvt9VsJBIxYmEF3SR/1hogACHalvPyOXyM9sfRlxBfjjeGuEKEKRsrc8JB+Dk5M0+cIU9W79MCG4CbMMQJK0QIJ/JbfnOLHMtjiDbgBK1kzJA2N87BsUTV98QJXC3kFMAxjkXxGDOUDz/KC2BBgEmLJw71xmyBFecdhfzgkro1Pjh1ljEGiBCHwwEBIwNlEXo4hCAmIMGe8T43oVNvuTo0LVkANt7vSrJ0N/rUiKvN3mZhWbn3jAlIUzNd93iPq87ZJoCQ7Qxq3lY/DxDkZwWyJ983gIu1GfWGY6O8K+G2OIiVaMX1SSMOEEwKiDfEmMnG4IAtDGINJjWEGxERH1b+vMcPcGCVj54x6TBpmcSACkAAYedOaojGhQsXLG2IPGmTH5tOpMFFHfiTN/HJE+KAvJD3nMXOKhbVMQCAdPft22fpwDmwuiUeZQBYACTKgngKwraZHHXA8aROtEPoC77jhygNcFypiQzxNpGRnrHsjdKSPh+In2i+6Cw6/IECK4jKGegdP/iNDB0/S1Nh+eXTkKcc70gL5xfzpC5/5cNeh0+Ft5TJ+9svyqYvIZyVSn5IaPCzlwSUI6SVAm8rA2H4DpEmFf22cnoRldXN8rZX9/whLbgGUvU1E1jpWtIUEX7SAwR8/Xx/+VKqHQQmVq6E1PwYlQfV3GhOdaEJsDtaqeItFyDCmGdO26JF4x0bBVtYaDCgIYRdAQuE4Eec8B36RTzeI0mAftAH4RPCWn8pHjQC+wsWmMQJtIJ+Cgui8H1hX2607lvp8sQBgoZgRc7qHkJPg9DI+KM2BmFmRc6Ki46AMBEGx+9AtHgCBKEh+U0awY8O4x0dETqKNCB2YTDQweQTOpAnfnArDDa4BvImPqBAB4c0SZ+0+FAu/Mmf52Zy1A3Hk7rSjrQP7RjaeuUBQpuasuz1FrmsqufbjG9qRpchOTa0Fb17ikj5KOk9YRUYeTd6+mxwI2OPE2TIjv5DjLOkVUMavCdccAAEeWEARuITehfk8PLx+emp7nWZyoNRaHJ8heO9CmOBApH2Xt5mA80g0pqQ3B7tIBzyfNLBnsPim+/Wn5VqgeUABPMcYEA0zZMFKOAATYKI8/z0008ksj5ui0vmALQJGsBik0Ujm8+XL1123PnAYhbRM8Z2eTqMk0UtY5Z0oEHMJYzfiIfYm3fEoRxT2n8K3xFtsodKHOjKd8XFAYJGDC5MdhqL70YEYt8Jg1/wD3Ee9kxMc2HYB71LDBvKcb8OWqxMIe3EdDbDd+qK47kWAEFuEFk0Zq7eHZcG06QryBGVlqYN7yD2Edk7lOZL5CMCe7N9PK55QzwMsngSGPuIfbXZplGD3xfXddyKnlYXvSetOmnV1Er1Eg7jjqyFr9zVXgIZiUJj/LVD7yoLdQS8vDr7p0xLB+LNxixaO3nKr64i05UVoByhfareSdfcxbHwHnzwAyew38iRSmi6qrJrW5bZd4ALF1ujph46KtuO/Srr9tJ0hzoocVbbAZb+BIDVzunR0mfO3G+OPUqKywEIiP3HH33kKiV9ACS4dwRjN9qrokJKLqJTH338kaurq3OvvfY9U2P9VMZr586ddSdOnLRyTwsskFxEFIZLhbqk3sqFQqTX3Nzs9kgCgciafU0uJ+K+kjaprOKQNgwITCgzwNSnfTU00QArrvI8fvy47CvmT514lPbYTHHiAAFSblZiupkafKllXWuAoFysqEekl98vlcsJcRKsqBEVQcR5Zovwo0bJYr93SGdkKfyMfuhhYeEKiJclQlsocMmUppMUoVz34JSBB2mwqodDKZTKZrGOiiDuqNQzh6ISC+iYCFQ0SQe7C/LCjehoiegEqp6s9r22Dvsb2ALkZWk/Q2kMKj5pUGZ+ExOuBIJPvqRJngAM9eyR7QCqouAwQFio/NDMUrBVdeQH4FOujTrfKF/gvleiMZYDEFhSc5o013ti+AbHzE1sUzq77MCBg7bix5gN8fULsmZGSeGixNYoukDwSyWiZu7AMbB3CuFvk63D4cOHzcIamwquGGUPkyMwvpHNRa3CYTAHmBxSuOamJltonJQFNWBFe8A9cNc1UhTK9F1xWwCxQXt6PQBCtMsIJkSM74kuEE4ILQ6QeJAjHEHjaSYEpm6884ARyzPhPV95H/KC4OMSs+Q9zrh9vSBM4nv/9t6/9ytTyGstuIcAEAAc7WBtoYwTwQK/lVzBJ7YCaUPwaCtrQ/Lmn/9hT8Qr6wUQiFE5H4k9zUhdnUQ6qaa1RvE40mKfVvEDEgexr4ARGuJj04xUnWqk4ELcvHzd/ywAuXnjpnEiEHT2NyORiBsXoDRKYQYOg31NRNJff33auJMKcQ+EQwGH+pMeHAfxUdU/efKkiavI87vitgBig/Y0ExkHa41Iwu9B6JIhqZWu1h6Ez8+LFS3zJf6JE5slhv8uB6NbIdDAGSq/wyPDupDey8NplyAjR+SbCBor1WYQ/34dNQEB5PKhfBFTbqdjhY4lMf7rCRArVc+tdFamBVbNkvphxbNjv3X8NfcncFQ3S1eOwDbtCRkU3c+htsjdERZOaonBmT/xY2kR5lv3SYfAepI/eUKIucJUy1kZZ0kTRZteD3OJeVGORGfvtLllSzGWZUo/fg+2X6YlBr/vd9KhPfxH1xcqnwmpg2ZoEq8GQExJfjMwEnVnm25JJl8lK94MqaxKpVXgBMGIr5pUJVRTUSKgHB2d3fp0uYb6HWZLENoDNVaM3VinciSyV/PUJrfKj3Fby21/GVVpiW7dU5tDDCFOqJnyHZXZ4eFRyYrzTSXWDOdUFgzXMDhDFZZ4XGLfrvxTJXuqkHiBzUr8kV3zJJ32ji4RvwwjxKRD+dlshFCjqkt+qPeySsURBlC2MsmTcHzXH/WnAuhBe5i/fvCd/C2MpXD/PxoOFo+EuiVOaWm5berdyMGx4cjNldqwCPeJ48e0qi23Nrl/ast/g0rxjaZmAwPsTTjxtKZa8vmhEbU1QOXvFqcvllKfpZRgOSKmpaS3FWbtWiDOQaDixeTiw3c/eSbteyBITASIBQ7tIRzhYQuJwzvCEo7BxZMVESwa6fGOlRGDjzsRBi985XLqdtsdCBAWbnrjop9w1wNyg9kp6dHrfog5GRxxjzMTdKT5qi7b2WnXgnIXQxKTXc/J/h67Y4E7IrhNLlX3TXCEdpKAhDshCMc9viTCPdfRtlsus6LabpHjTghuirO7H2JEfVZ1SxaRAWxmVQ/CACxcMjTe0aYLkGQdq8uKACIuEuJmOPIavnHR7oLI0SVEU5KF+rssRFVETMKx5txLAQgYtVF+Vj7lRbvNqGzJssYdu9uiOun6VV2i5FSXufxil8nVpsqf9oQArpSa65Ta9tzNVvfBucvuz549ateiIt8lDxx68WZhLcLPiZm1NdVWjpvNt6Sm3KMD0WrN0C2sQCHiEGGcJ8ZebFEpogcos4pta2uXbLdcBLzTNh9ZyVZUeDVngKdL6WLIhpU8dhGMMTYQ7SJ4rXzhpliBd3XpRE+Vn43HMS06sJaukjYedhEBIAAFxiHh8vJzXaHEC4zbnt5+Xfk5ZkCIJTllzVT9CGvjVGlFZZgH4aTcE7Lr4B1pD0izhTAV5WWuQOVZCkFVEjGA0H4NY1bjqpeNUPUphoK2F6g2q9IBcrQH6a+k8/NaCyI59oMoM30F9vE99B/PpdRnKWXbAoiltNLGDBMHCIg58jcIPsQfQo7hGpMOmwIGMAOG96wesTGAOLE5xCCHkAQAQK5HONTGsFfgHenjv2fPHpP9zer4gqFLZ3R5jy7oEVE14itCzHWcRjT13VbjmlHcwgZ4cD0n909zxWdW9Q6Xp2tCh3WbW5LOf8+uqdMFRTftWlOaOrOq1m6S4zIhborjnmjuiOYmN0Ajt2GPG2u/YzfMdf/xPbtmNENXn461tbhp3XedJeAgrN16p3xnxkatHNN6Ut6JLlnm5uZZGQCZoatn7Za5nB2NblBl4lKhgr1HdCNdp8pc4YZ1lSngk5wqIqW652xv0D3U3ZaWKmqXJc3CIZToDHwROW7ZA6C4cCi9tFIbwlrFFpa7TMldVwMg2Ige0wq2q3/IVRTptj0Io/qLYy6QUZvRmIgGHAXEEmINwYSoQgDQLmEc+JW4XxwAKKisYlFtxEcr8wy1Ad8hVBBYiKAtOkSgeMc4wZEuxBhOgVUveakYeiocBDwGPuGeZgCMMqFxkqL+gYMgLd4jysH5caizihSX7wAJeXBGD+WEYOJPfdkjUEEtHmXFj7Qpuz/2Q0aAKiMOq3HS5N3DHOUnPdJfGJ46LuZnqyJKsCB9axNluND/QWUgjs///gBAf2xEgAj1Tazfg+pu/aY2e1CYxLT4HvJYLM7Cdwt/h7Tu5x/eh+fDwj3sfUjnYU/S4UOdQr0Wpr3wd0gzDhAESLyTml17AILJj3Uz9hGclQ44ABoQf4xV0CFGNQx1McKwwcN57oTBH/1iVkU4NoY4cgMdZDc5bgDBXdF5uo507E6LVv068VCTelrqaFNDrJy10tM1oqzYubaUlXh2Tb1xCna3s4gLVIPrPdN09/OIVu4FB04YgZ3SVZ9R3RGdqpvquDIUMU96YYkRXbWSgKHCVv6IcLgSdPjaBeNkxjtalZaMcARKnstoMW4luzqiO6dvGJDAKURV3vTiUgvDPdR935yyG/Ty6ve68Z524wDw51Y9wGzo8hkDwTRxHOO65zqvYZ8A6rYRnhlxSeMCK8Asg+tMxQFl1URcdm29qifOquOOGWmtJkDQP4wBO6uI1WPsN/60l1FmfP3/ewYa8e438Cx+wp8QDq8wgRNe35OOaKmVg/fKgr/6MMgpkv7goxd8cMHPfsR+J75PDENYn+R8fIsnf59ySGX+afkkvE/Md2He87Hu/UZRFwIE6YS2IJ2QFvtP0xob0zryIzU9W+A1b18EYAO8MxqLaQJWuIDgQnqAXhD58Q7RHvN5aIgN2gJT11yMQ9mIAEH7sOhkcRQ4QeoJPVqsDrxDZZbFLgvZpTjiYGtBHBYKiY53qLqSX+CMWUARjvIE96A0QhiehKMvKDsLm4WO+lJXuFzKvxTAThxDIT36knwoK+mEvS0Wc6TNZj+O34SlLGH84X8PQGBJDfFm5UUFAudAg1BAjFbIgAaE+JMpIEID0VC8Q3eYozWIg1U0HATpABjEr6urM2CZ1Up88MJpv8LWCpkrQOEi/HWcWjFqtcZvpiuEeEziIH7DDUD8uc4T8Q7+TALuu54Q4Z1lVafVGdd5jrWL2GslTvwkHY0ASLA6JzxEO4ieIN6IeEjPOAy9ZwXPFagzalzuoIYL4NpSC8e90pqYKSLocxIHZW3b4fq+/sTKnFW13cqH2ClLoDLR3e7SxfVE4UzEkZAWV5fCrVDeOXVMmsRqgAKipDRdSWr1E1eU27DXuKhRAR0S8qRicRA5q8NBMBi23Oq3gKZVHCAAOyZpb0+3u6E5k5uXK5XLHaZOOSOR6u1b59zVS6dc1myay9ZibHvDEVe5rVEiyHHXKxGn7V+Icz3w6t91tfW7pSKcaiK4GzpxgKNnWLA1NDZq7nFlZoq7fOGsuyqx7o0bN12x7mb/Oz/4odseqY8R2HlYXE+AAAQC3THCrwYzDk9zGn+sqClfSXEJKwKjPSYWFGEbligyVXQHOkSYd9/9ldlGwE0CiIEosrglLewioGUQYt5BHDlO6IguB4JwBrCgHCxur1y5LDXXA0YjSf/ChfN2URBlJjw0kzTx50IhwIRTIvxRNRKH6h15kJ69E+1MFq2FcI8KmPJFeyHyjAnSRL2Xdxj8QWupJ3SW79Bc0qCMGAhCsy/qngq+UxbqBy0mrc9kJ8LlSUVFxZ7TV9qUA9oNI0D9aVfy5oQMFv4B9OIAQQZNTU2mHhYqS6OBJhB80JjjMkIFeVIIzmsKhaLiNBKVs87VfCNzKkaD8qHzeDejK/36vv5YnZxsq/JUEUeIvsn4YeW1WmIFpT/+fmoRYgaE3RUtP5xt5Jq8XqIb4rJnIfGQMvDcgVZfpMnGNXsPiVd6WgKWhsqlO6opB2GViH+lPFhxsVcAkJC3WlCvtQ8S0lJIW6FZfaL6of/kR9jwCWVVO9jGuNLiFX/gjELatvEey5+9liTJ10kLQBoUgKUUlLg5fTI1YGhTOpPBwgBKRHxf+Ef7S9/FivtoCWzFum8L+OGAaus8QNCHHe0d7puvPne/ffs/uZ0Nda7h8LPu6RdfETHpcr/6xf/hBntuuRxXJe5y2m0XCJx4/s/deF+bu33+G3fhm8uu6+IZ98qP/1e34/BzrkQE5sqlC+6XulCnp6PdgKFxz173/Msv217P2//lr13m9IDr6+5w/dEp9+Ibf98df+olzWk0pub3OpinzNGVGlesYKElpAmdIX2eECHyCLSC79AZ7BBa77TqfK88W1i23vEKDYi1b9++5SYlCo1EIkbgxrWPg4SitLRMgHpL+0VR99ZbPzTa9M477+hstjqjR4gpUZYoKCh0r732miQeX7tuGeBVCSw4oQEaxhE+b7/9ttqqwr300stmsQ2biX9Tc7OA9boZyu3evcfUaTHAq6urMzVbiPW2bdWuU/QQOvpP/9k/s3q9/fZ/FUBkuhNSkQVgaIvy8grVq0ALg14j5hB2xLGHDh7Sfly7FtKDdqYcKrvQ2Z27dkrdtsWIOBIarkUlHSzD0TxDpReV3J//7Gcm2WnW2XaU5/XXX7e2/vnPfur2CdggO9S1XPuAnFnX1NRsihsAMAuIHu350ScvvPiiLS4YzHGAgHCHAcETYpHo+B3e478wTOK7hXETw8bDKb0QTtmRov33fD+/ExwBQnl8YP8y+PEr+Ac/0gxVCO98rG//DXG+/cb7hPIlhluY5j3viGaR5lNMfD/ve99voegGgiIkgAIybzp+dQEi5Hzfom29eIQWYNz7eeABguHDAgsi8Id3/tYNt5x19Q07XVpJjTv4wvfd55//2l04/75GUYo72viUa+0WN5yd7k6eeN5Fh++49o919aUUCvoHR9wLP/qHbsdLP3JJU6OuTbYCty6ec4VOezgyeR/RSbPbGne5l1991X38wXtuvL/DTeoI8hHtUz/z6g/cnn3HXHGJDhpkcRJz6wkQrLI5SoPVLfTh+LHjtjfarutGWb2zIuaeaPaumpqbRLBPmJi7Q4R137792hO9IgD4nu1zvvvuu7aKLtWq+Oq1qyaG45ieCq2aCccRQqyqL/239s49tqsju+PH+IXBNoRHINiAf2AMBAMJEN4km2xIQ7ZJ2iS72+5qq0jblaqqVfNX1apqpVatKvXv/pXu/lFtqyYbQrJJSoAqgU2gCa9sMOZlsHkZm4fBGNv4gY37/cz1wPUvDmAw+FJmrJ/vvXPnzsw9M/d8Z86cc0bbiq5evdox688++0xMUh4DNOLGYC9fIFVSUiJvw6PdNbOPlJhrldZem5vZB3uMY9zUlW1Pi4sn25bNm+2VV14R088SQ/6tuz9l8hRnnIcj0dnyH1etspctW+Y02XjXQo36MfJjs6GTJ07a9373e3ZMu+Uxs5ggwOK7xzYEZY255eXunQFZlDAwEOS3c+cORzMG7Wx7ytLAKCljbNmyxfmsw6u2pxkzD1wrAbosAzDL4pk2zU6wYk+lUq43DJmaq++M4dg/BTx4cmSkCSi4j0MIf7cAov+ahNjBpADjBNoTgGDx/LgA4vOP3raGKrmK0D7Jk0rnWN7kR23zb35tu3ZuV+I8WzJvoSzFmy1//Gi3J/P29//LDm3eIDcQDdbWk2lFM2faI6tWS7trjMSgjdZaf8ryO1tkSd5tLcOkPps/xn7vtdds17ZPrf7YYa1bSOkkI9eekogppTWzseMna0SfDIBgpgHj9gZyqPqyvkn9YFoimx3XLII9o1kTwFq6UZp+zBDOnj3jgGX16uccsz9wYL8bZWPs9rA0zVingWliYHdae8wAGuSDaAr1aRx/nhBzZjSNSJyRPaIpRHXMCpjpIFqvlxV2vhg6Pp8Qo2OFzeyB2TxrsmjMLVy0UDXNsDox9ZbWFgc0zIwYobOlKeJ2wAZAPHWq1m2JgCsPZiV4QF68eLHzSt0taQqgw37X2RI7A1SIjthDGyBnxjKjrMzNIo5p5tChQQflMcMAPJHesDc278naE7OEKQIFeAkirOECWpYBMBJ8aMxDiu90tEPURLg2g3DqdfTaEBJBgQAQiWiGQa9EHCBghrViKJU7ttru9W/bs9990h559Amtp5XZMa0/bNrw31ax57hN1wfNQnTJjOn2gz/4gb35j39v+7Z+ao2SG6MZVzRNI8XUZFvz/Z9aR3OTHav8WkoPJ+WqJNvatA9FYcks+501a2z7ts12pGKHGGKTTZK4ClHW1NRsyb7Hi/lF2mO88FDOIABPyveSBq5RBwYgEKmi/QUYEGDYxJGGNYLt2790AFBaOsOtLRCPuJsBFSNwAvnyDAyS++TBfZ+XS6R/iKN4FqbMtxidR7YzPBfVBXsZbGmi9QtfXmSDE2m1IaZmTs47kQ6AoCzyjDT8urTF6m4BRYsTXyHepz6kI5AnTJ7yOffacuTJNWm5T31IQ0A0D41YX6Esyo4HaMCzfvAJ10dUTloCMxN/HgDCkSR5/4YCIGBedDxftv9IHXV08yo3FRhHkJYO7oZ0iuvzDDe5R1p+0am7ftD/QRo+TqdGK2I0Nl60wxIJbfqPf7UlC+ZY0aOLbdKcpfpIuyVT/pV98P5GLbyOsOJJE2xaL0CcqDpsG//z5/bJx5tcg80pL7MFa16x1/7ojyWHr7Zf/uJNqz2430bm5VjZ40/YK3/4Exs/cZJ9tfNz+2zj+3ZUsvTFTz9nT313jU0VeMS1o2ifoQSIB71/JO39A0AkrUV66+MZLkc/UvAiJj5gfowaBmuRGsd25y9dsTrtj3yiocPGaB/jhwuyrEneTvPlpO+yHOm1ymle7yDDzjbJ6FGO7/CCirfUY/Kk2tzWbbOK8uy0PMHm50aL8Y88lG1jCzXCwXV3CA5YPUAAwLTjWcnXd3/+P9pUq9QeLplphdLAg/Mj+jgoWffp+jPuXvHkYrcg2y45ce3RGtu6aYOTH696drXNKJ9no8eOc0aFhw4esg0fb5Rl+Tj7zjPf0drGdLeR02mVc/hghe3bs9tSUsFesGiJRCYT3ag63jSDDRCstdB3GZXebJE6Xo9wPvQUCAAx9G3Qbw1uBhAwGQCCaWSfkX6/ud08Ek+qXx9ttePnOp2XUzydMmMoE8M/ca7DeVbFS2ud3GpfbO2yorFaKNT1BAFApzy4HpH771EjIgv60xc6bfaUPDt6WrLUGSNtltxpsz9ECNHMKw4QyNEP7N9v/7tNxp+SYa9YuVJb6s5yg4ImiR3OX2i0OnkkZW+CUmk5FRUVW82RKtv0wTrLaJKluTRiUP+etWSlFc8qt4rffm2bP/3STpyUiqRAev6CcntuzTOyb+m2tWvfUTlSx5YIoqRkmr362qu2dOmybzRLAIhvkOSBjQgAkdCmv9cAwb4NtZo5dElLAmbOdf1FuVieMsK559Z6nBMpHTjVpplEtz2ektab5MHss4C4pEl7NJAGUOnEbXf2MDfLmDYxV+CS6e7djNQAnX9vn7a/OH/vhkfllURIShcxoVmCtg1rgDBmZM/Pa70AH1MntRiKbH3vrp3WKYv6PBl6vvrDH9o//d3fWIPczaxZsshKtPh68UKDNXRl2MynX7KdMlKdWjRJC7FjnQuRai2Szn6s3C61Ntmbb77pFjOxdXpGqp54J2WRNz0MNUCk9wFfP9pT3etaH+lvYAT4Epit+L5DfhEoR3Hp70c67vtnXQb6x2yHOF8frn3weXPtnlfb4Y2A4MvmnGf9L36PZwjUhfvk7eOI92XGyyE+PVA/0vhn/XPUwZ+nPxPP05/H39/Xn7h16zQQka+bnrBInU7Gob32jcuRHwtQcRETjTeYMwgV4TprsxhTFKJOlzecxTIYd++iltKhM5+tfRtYCKNeAAR10Q1t4KL9oTVaZeGM/aT5DtyzQg7qTFo+I7/nMYtsQhqJoK7r3WPNTSLf8ckD9T7P8XH8x4dFIgyNCCwA4mIDR3tAA+XQ0fklKUBnTweOqCRu3brV2QdFuu0FtmTpUquXf6mz8tHU2nLJPt203lovnrMn5j9u85Y9aX/7F39qi1PF9vismdYmdyXQ52Sr/DqNK7FuuXZZ/Nh8zSwLNdOT/YG0YLplY1QtY8t6ae7AiGpqauyNN97Q7GGpm4HGGR+0Smegd0o/REy0D+1JWeTP0S/YEk/wzIo+Qb/iPn3f0UtpaGvOK/futTJpbl3rd2pv0tPnamtPOvsPZtaUQZ7QFfVSNJDwnHtcrruLpEGEGir3eQ5/XxgXdkoURl5o+qDJg2YQKrWomBfJYM3Xk2d4J/o9P8SBqJCy2I/WE/yUumPXcUXp2DcdjaUZM6IFdMqlb6JuukvqtMuWL3cL6dSZ56gzZXAN/Tj3tODIsxyxa8B9Ee8LbaAt6qvlUoX19SOd/1F/6Mo98iB/+ArXX2lvDAYP0Am7EPoJcQEgXLMn6x8NSvANy0dDR6Fx6QR0hsEFiMhi9VDVEffRjJHKG07rcKoHc4ah85HQoehYzveSxB7duHtQXVD5Azj4uNT3naoeHwHpsSTlA2HbRry38jzpeAe3GZHSdOs+VrBogNBZUQEdqzogYkGXG31vPJ4CSrw3aagTTKNAHlDPyLlfxBS0XqKPCzfWhbJMRi2Q8m43QH/cWhDI53bmJb4toQfNynvD3DjClNjPgPfhPkwJw6xT0uuvl7pqi5jKNunVVx86aE/On2tLl6+0f/iXf7bFpVNtwbRpdvlSs7WrHS7IFce4mY9bYbfWf6Bjdp58a8mJpiyO65ov2wG5oYFJwrhQ+1wuhvT66687RgDd4jS6WwBBGfQHDxAwPUdTOowC7w+j3r17t+sH+fIagAPDiDYm5lrmVFrZmwG1zZaWZrcGM1k2BtgL0BcOH65y9hJsPIRfsClTJssO4byAuN6J06DvBx984DYQApwxNHtCKqWUgUYZzBLGOEr9BtuwCnmXmCkGzAZD5XPmaPOhOvdNlpZOl5roWbcGOFZqp7xLRUWFe7dUqsRtVIR9AxsQoRbLTHGE8kPdFhCh7WfIyr1O60KI/ebNf0y73k208WLOvJ8HCECFAMPG0M4bxxE3Ru+CDQN9BDVdVHJJh/oqNhXQmb5HHqjGYrfBd1WkGSbGeHyzfEv4I+MbRh129qzZLv1i2YtgV3HqVF0ACIidtOCZCkd+AER/MwgsLfmw7jRQBowV0QY+fnCI57yhtrS6zgzzb5fXU7g/HxV66u3tnS5dBApieMqDeJgoHzr5waCJa1Y+dM7x48b2go1GVW7UJ0eOWThybHOggoUp2lE42UOlr02gCMC4zq6XJM656BaD5a15d2iANSgqhTAgvK/CgCl7pOT6fAi3G7quyg+YZP35w/P1u25MOpD8mtvl30feebPk7kUkugYQ0BxmgI8zPlSYJfr3ixYt0jtctkMH9tpp6eJ/8unn1ijaLywush/9+Ef2y40fW1bLBRul2UGu8rwikeDZ3JH245/+mZ2uOWot8vElh+bOPXyPwLVgwiTNRuqdTjtlAEjYA7z44ovuhy4/8T4MNkD4ESptFQcIBjvE+f7LEZcPO3bskHfgqc6auk39AtuAkqklDqgZ4eIgtEh2BzBBdoRDTMbsKEd9lrqnSlJuVobrEtJhaHZO+v/YOWBX8cknn7jvaaryxDL7+efXOEeRPAujZBe5ItEa8OY3f958N9NjgyE8/14SKE90thHVskGY6UbwDXKpcey47BAEMBjaMYDavmO7M4ajb1+82OhmCGVKzwgfAFuodj6l/PfJUI861Ch+vgDlK9l9zJ1b7r4HDO0AxjNnTtsxzWZK9G4ACZsnQcvp00udBTnAxXaojZqxVFcfEU2mulkWMxQACuM37EQAJwCD8pkpZGtABsCyDgbIUD7gMUX9o2JvhYCxPgCE/zCSdIR5EDjyiwOEG3mrI9NBBgsg/LtTFusI6cGptCqSW75u/aXRNx4xwd76p6fx16SDwYs79L6jv3P9KB5PAhfh5bvX7/Y9ixiNp1faPV36+33v3PyqXUZltU11NnbEGCvMlWt31Ze8bjUw+9h35qCVPDTFCnIRO1wHCPLBCAyDLD+a5rhKrg7Qhz8mtxl73nnbNladsAlFxfbsqmW28oU1YkJtVvXlZ1b5xVbr0D4OhbKUnr38KSuXCOrwwSrbrxH4RPkV68mW51ox1MllZQ7I33rrLVu/fr1jUIDQyy+/bC+88IJjXPF3uhsA4QcRABH503f7AwgGFocEYDA0fEoxa6ZuAFqdQKBUo+5Dmk3hi+lcg3xYyahuRqnENhoIkC8jb/wLYfwFQ2d2BGOESfPOzCC2bdvqXG5cuHBezZhhT8kVCd8RgW8Lp6WMuAEdDN0QLVHWCInqAAwGTzgdrZebFEbwiHMOHjjgwJ3nC1QultKV+yrtpZdednliIT1abj4QjTEoAAjpSwwMyBOXH+zNkSefdDBw6IXtBgZ1BAY7zEjwyzROg6yaGg0ElK587lyX1wW9LxbaABVl4XqEb4aBEu/G9eGqKmdEVyp6UW9mGoAEs3uM+/BWzOwVLTjicOHBbC6ImFwTJOtfnAlz7kVMdBo+BOI8QAxWzRHzX5b2UocWqNlrmsC2oKiostdzjpYZ2HOaBewu3fegQTrS5Lg0cqqmNNxn72d+gAAslR+5wvgzlT4vO2K2aFBRpl7JBSV3achvuJgcKriXOyRfVf0Y6F5L15ufq5/yy1X92mUr5PbKVrnUgZClAtHAYo9sJbtp6OqRkdRVycCHZdvZy+ckx5evmkyJQ1Sxbt0ryNZsQi4sVGOXl387QasbgTrRl0Uj8uYr8vOlZCNky5CdNoOA8TGC/VwLywf2fqWF5Tx7TK40li5bIVHQSLnUkFz8aJWdOY+TzHxLTSu1cdJagnlgWbthw8d2oeGsLVvxpKywF2t2Nl6uEjTCFVO5rJEjM7NCMZZ8MSzq3tBQJ6ZW7frPsJ4r8t8zy8Y+PEX3VFeI3hsGGyAQ3/ADHDxAcOwPICgba2DERfR53pVAWgIASl6Ih/ZI/o5IhpkB74cLDhg0AUO3zEzWB6LZKd8KKuEc0RzjiGYYI2pESVz7wCyA74u8qAOBesFUASxENLnSHOMcBs8PRjpJDJV6k9cuKRZQ3ooVKx3A8R7MZDG8Iy/3niqH9/Flwcg5p1+Qt6cP53z3/puHdgAps4CpJVNVu2iQRV7Ulx91gE4+D67JhzwoByAljrx4Xw+QiHgx2tsrcdkCzUg++uijABCuByTwH41J4Eij05B0FDouPxqY68EIlATjP9/cbYfPqOOLqbbIrmHE8GE2Ki/TJo/LdQwW5r+/9rIDCuwgYN6ASKY6dVnRcDFpPSd7CewiYNReowkQgFFmaXEbXjQ2X24PZEMBWyJ9tcpslnbUCNlckB9h+oThVpg3TAzarKquTekk+hJgEPAzBGjpW7MxI6O8uAbAqurarU0PtQns3EKm8ls4faSNVN7Z1/mAy6e/f+3dHdbSJdfOmTnWIZl+QdZIAzQ6rmptpEueNAUcI7I0YlUcNR2WES2G0k6dSs9Hl5epTa40e+CZkZly1S1wINCkEROLXEv/4uf/Zht+/Y6VT8h3z53rHm7ff/1ntnLFCjH6s26Bs6dL9ijNjZYltdXUtFny5HrWdkokcFgO+3pUh6takF60aLE9s2K5Y35upCiAoD4smKZSJSq4w47s+cQ6Wxst9ehjWjyt1ztlW8nsZ6UOKw+jNEpvgHnxDvFZhb93O0cYFf2X/MjXMzAYGtc+DKQ8vgWYHIyNfPgW7maAljB56gsgpAfuRe0atS805H0Gy04pvTwPMrz7QOiWnk9/17QP74KSydp33w0A0R+RkhBHpyRw9ADBB0HjEUdn9R3Ep72TeovniklfdQZvgAU/8kWffmxBpuToWgDWSL7hkkYpYv4w8mgmEZU6cbR2Y9Movbld22k2XRGz7HGzCpjPFQ3/WevN0X3eCiAYVyi5u+61Ks+zSu/ESLpJHPljZFco2wrsLBqaVabyI16vrzQa1UGfngwrEIiMGilxm8CDvCgb0AGsKCtXZU58SGsqOt7qcoQg2AFAVka0ppJO164ejdIEDASAIgoApdwv6EUBFFguaXw60rgqoWlwmgAACU9JREFU6z4MpbKy0v76r/7SRl+9ZD9ZPt8xnvWVki0Xz7Q//5OfidOwEK/yu/OsoVFg0XNZ/nLG21ebNluO/PNktGq/AzH3mktav5HPpVefX201EiOwpoBohXBMcuspMrDr7jxju9b/u+WIcaUWPm05E1N2/OJliRSW2rRxowRg1xk1cnja4E4Zj3+ed00HCN93PUD4tK7S4V9iKBDsIBLTFN+siGf6HP2U1E81iYvf96OXeFw8Rx8fj0s/h5nCwGDALv/eBDBjRuLiGb33r69TwAQJ3HNpdE4+iKjEy108913ghtLxDFINZh1cUJ4TRUWprv0HgBzf0nOIl+J1upZIJ+n1Iy/yJPQWoRGm8qLgAQRESJH46OYPpaflmvBtz9NeyJdZmBye2WOTtIsf79ckGVmHQGlKcZGrM1vddmukrx6gHzMiqUY2Nmk7XYm4ujpdKZ0CyR4RdLQ0txAt0UfoLzDliBFrIbujRXutSCVWf90SkXVkoOmUrbWOUbJ6j8RnN3/Lb6aIM3Z/Hj96AOBJzrl3vV7XR7/+mW+WEGKGkgIBIIaS+rdQtmfsfFRM1dMBws8m/JEsHSOF08eCzycWFU6HmAK0GYwcSPUjeEAVcMGmI0JXF9Gnpg58aN5ewHNNLcbrmGxvu8fbG5DkOppPSdvM5RalRzttoMDpK5PO1P01R//zoOCPxPvZhJ/9kp9/1ud9oyPPE/oT9fCenq7cjwNUep6khf7xuqWnSb+mbJ7h2bhoK71OPk1/daR+fo1kIO+dXpd7cR0A4l5Q+Q7KoCMS6HB+MYm49B+djuDj3UXsn88nFhVOAwXuiAL9MTfifLw/jzNg4uKyfJ/GV8Qzd/qrv+fPfZqvpfmFxo5TqWYqquDTsNsazJpFXLSZYNDkw/fj86MM1ix4BvsGtJ1YqKae/juKAwvpCOTFAjdeY4uKip3NAXYHBLSdciX+RcNIFLBaXaOyTL5APgEwJn/ck3NkfYhF8iSHABBJbp3eutFB6eDMIFiD4Nr/SOLPOfprdxL+BQrcYwrAhAn+6BmtZ84cAQiO/Y3w0dtHvZW+jkoquvnYvaCG+bAYKvcvSpsIVdIGqZ6SzxipvOKSBCPAL7/8wh6T0RlM/7zUWCkfZn5Aaqjcx2YCQzj2YoC5sw7EngvsNIdqK1plnKPhxGZAhHOyw0BrqUzqwtXVR9y2pKtWPemsrp02kICjQHkzs8OOCA20muoaZ/hJeQzs2rWgjiorm/6ck7oqu7rNVz29dpYrKIH/AkAksFH6qxKjIj4aFh89IJAu/dw/68HCX4djoMC9oAAMmxA/pp/Tj4mLi5h83XBN8YX2UC6Wvj+2DGx+A/OHeWMpzMgfLS023UFDaN++SjkxLHU7szmFB21lPHt2ZA/AbAJQwVUGQMCGOOXlc+03W7bYnPI5lkpFO8Nh4UwZ1Ono0Ro38kcllK1BW1W+t5j+fe0SV1d3Siqsu21aKuWM1/x+EbyLJg7axOmC29MZUKBsAIetTUeMlKW73oEZCO8BiCxYsDAAhG/4cLwzCsQBgpw8AKQffSk+3l+HY6DAvaCABwPK8ufpxxsBRJMW7jFUwxdSZeVeWe3jB6ndudZg5M8oni1C2R6zqUl7aVQddpbMGJBhIMZsY5aM0TBEw70FBnaFMhY8LitnXHdM1ogeGwqM5JYvX+H2vwZsGNlXVOyRId4MGdW1OvEUFs8RSLQ6pj9PRmmNqh871WF4hnUzsxBcVSAywgYCtzDMVhBvlaRKHDAh7sLAD3A7eOCgA45RowodWDlguRcNc5tlhBnEbRLuXj8WFzHFy44DQfycNOnX8efCeaDAYFLAg4DPM34dP+d+XMTU3z3ESPjdQlyDcRqiKHyD4YcL0RHaX87L7d4KJ/qZJaeFbsSuET9H8sTFCmmwCMYSmjxh0DBkVH9h4KwRMMtAbItxHr7G8Nk0fnzkrI7nEUsBNNQBy2IWl1vkNoZ8cMKXJTcyV1Hh1nvhT+y8ZkC4A8E9zPC84S7/Tm3jCbAAVgAXdSQv8vYiOE+3pB0DQCStRb6lPh4gvIipv2QBEPqjSogbKgqkM3/qQZyfQcB009PQh30/9uccYaSk5cc1ecBoyYMfKtYs+PpnETdhz0H6OBPmPt8SYEE81z5PZgH8uOfjqLPPk/j4tbuI/evh+Vhd/S1ABfkT5VE2gXOfn4tI6L8AEAltmPRqxQHC3/Md11+HY6BAkikA0yXcCCCSXP8HsW4BIO6TVu8PIOJVD2ARp0Y4TwIFPCCk1yUARDpFknsdACK5bdOnZjcDiD6Jw0WgQIIpEAAiwY2TVrUAEGkESeplAIiktkyo10ApEABioBQbuvQBIIaO9gMqOQDEgMgVEieYAgEgEtw4aVULAJFGkKReBoBIasuEeg2UAgEgBkqxoUu/du3a4O576Mh/6yUHgLh1WoWUyaZAAIhkt0+8dmEGEadGgs8DQCS4cULVBkSBABADIteQJg4ziCEl/60XHgDi1mkVUiabAgEgkt0+8doFgIhTI8HnASAS3DihagOiQACIAZFrSBMHgBhS8t964QEgbp1WIWWyKRAAItntE6/du2FP6jg5knseACK5bRNqNjAKBIAYGL2GMnUAiKGk/gDKDgAxAGKFpImmQACIRDdPn8qtW7fOMmpra3vy5Jp2WO/2fX1ShIshpQC7VAEOBQWFztMk3lxDCBS4nynQ3Nxs7MTmtgxll52Ehiy59GZTorg32IRW9a5V67333rMMbfHXM/qh0dooPdrf9a6VFjIeMAVwwodb41HyXY/L4AAQAyZheCBhFGCPh0716dzcnITVrG919Om5zYbuB7fcfWt+51fwHVyff/jhh5axa+fOnlSqJPHb3935a99/OdBIbGRSqBkEm5IEgLj/2jDUuC8F2PCHzXbYFOjbPL72feLeX8Egu7QREBsUPYgAgdQCIN+5c6dlCCV6nnhikduF6d43RSjxRhSgo7Zpt6sAEDeiUrh3P1HgfgIItgZlI6IHLSC1YOvUuro6y5Cuaw+bhI8bP85yc3Ki3ZSgCHMsDu5fdH7tmhMfogT+SkfkikT6o7/l5Y3X87qepr+0Sc/j7tcPgGBRj43OKY3tEUMIFLifKXDpUpMTm+bAa67xiIF8//fgu4PAKqZAM4j/T2sQ/c3YfBxH+E27BqRst1pZWekkFv8Hec4VhyV0on0AAAAASUVORK5CYII=",Ov=({cursor:l,onPaneMouseMove:u,onPaneMouseUp:c,onPaneDoubleClick:f})=>(ue.useEffect(()=>{const r=document.createElement("div");return r.style.position="fixed",r.style.top="0",r.style.right="0",r.style.bottom="0",r.style.left="0",r.style.zIndex="9999",r.style.cursor=l,document.body.appendChild(r),u&&r.addEventListener("mousemove",u),c&&r.addEventListener("mouseup",c),f&&document.body.addEventListener("dblclick",f),()=>{u&&r.removeEventListener("mousemove",u),c&&r.removeEventListener("mouseup",c),f&&document.body.removeEventListener("dblclick",f),document.body.removeChild(r)}},[l,u,c,f]),m.jsx(m.Fragment,{})),wv={position:"absolute",top:0,right:0,bottom:0,left:0},Rv=({orientation:l,offsets:u,setOffsets:c,resizerColor:f,resizerWidth:r,minColumnWidth:o})=>{const h=o||0,[v,y]=ue.useState(null),[A,E]=Wh(),S={position:"absolute",right:l==="horizontal"?void 0:0,bottom:l==="horizontal"?0:void 0,width:l==="horizontal"?7:void 0,height:l==="horizontal"?void 0:7,borderTopWidth:l==="horizontal"?void 0:(7-r)/2,borderRightWidth:l==="horizontal"?(7-r)/2:void 0,borderBottomWidth:l==="horizontal"?void 0:(7-r)/2,borderLeftWidth:l==="horizontal"?(7-r)/2:void 0,borderColor:"transparent",borderStyle:"solid",cursor:l==="horizontal"?"ew-resize":"ns-resize"};return m.jsxs("div",{style:{position:"absolute",top:0,right:0,bottom:0,left:-(7-r)/2,zIndex:100,pointerEvents:"none"},ref:E,children:[!!v&&m.jsx(Ov,{cursor:l==="horizontal"?"ew-resize":"ns-resize",onPaneMouseUp:()=>y(null),onPaneMouseMove:O=>{if(!O.buttons)y(null);else if(v){const X=l==="horizontal"?O.clientX-v.clientX:O.clientY-v.clientY,B=v.offset+X,b=v.index>0?u[v.index-1]:0,p=l==="horizontal"?A.width:A.height,x=Math.min(Math.max(b+h,B),p-h)-u[v.index];for(let R=v.index;R<u.length;++R)u[R]=u[R]+x;c([...u])}}}),u.map((O,X)=>m.jsx("div",{style:{...S,top:l==="horizontal"?0:O,left:l==="horizontal"?O:0,pointerEvents:"initial"},onMouseDown:B=>y({clientX:B.clientX,clientY:B.clientY,offset:O,index:X}),children:m.jsx("div",{style:{...wv,background:f}})},X))]})};async function kf(l){const u=new Image;return l&&(u.src=l,await new Promise((c,f)=>{u.onload=c,u.onerror=c})),u}const fr={backgroundImage:`linear-gradient(45deg, #80808020 25%, transparent 25%), 59 + linear-gradient(-45deg, #80808020 25%, transparent 25%), 60 + linear-gradient(45deg, transparent 75%, #80808020 75%), 61 + linear-gradient(-45deg, transparent 75%, #80808020 75%)`,backgroundSize:"20px 20px",backgroundPosition:"0 0, 0 10px, 10px -10px, -10px 0px",boxShadow:`rgb(0 0 0 / 10%) 0px 1.8px 1.9px, 62 + rgb(0 0 0 / 15%) 0px 6.1px 6.3px, 63 + rgb(0 0 0 / 10%) 0px -2px 4px, 64 + rgb(0 0 0 / 15%) 0px -6.1px 12px, 65 + rgb(0 0 0 / 25%) 0px 6px 12px`},um=({diff:l,noTargetBlank:u,hideDetails:c})=>{const[f,r]=ct.useState(l.diff?"diff":"actual"),[o,h]=ct.useState(!1),[v,y]=ct.useState(null),[A,E]=ct.useState("Expected"),[S,O]=ct.useState(null),[X,B]=ct.useState(null),[b,p]=Wh();ct.useEffect(()=>{(async()=>{var K,J,k,nt;y(await kf((K=l.expected)==null?void 0:K.attachment.path)),E(((J=l.expected)==null?void 0:J.title)||"Expected"),O(await kf((k=l.actual)==null?void 0:k.attachment.path)),B(await kf((nt=l.diff)==null?void 0:nt.attachment.path))})()},[l]);const x=v&&S&&X,R=x?Math.max(v.naturalWidth,S.naturalWidth,200):500,U=x?Math.max(v.naturalHeight,S.naturalHeight,200):500,Z=Math.min(1,(b.width-30)/R),F=Math.min(1,(b.width-50)/R/2),j=R*Z,D=U*Z,N={flex:"none",margin:"0 10px",cursor:"pointer",userSelect:"none"};return m.jsx("div",{"data-testid":"test-result-image-mismatch",style:{display:"flex",flexDirection:"column",alignItems:"center",flex:"auto"},ref:p,children:x&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{"data-testid":"test-result-image-mismatch-tabs",style:{display:"flex",margin:"10px 0 20px"},children:[l.diff&&m.jsx("div",{style:{...N,fontWeight:f==="diff"?600:"initial"},onClick:()=>r("diff"),children:"Diff"}),m.jsx("div",{style:{...N,fontWeight:f==="actual"?600:"initial"},onClick:()=>r("actual"),children:"Actual"}),m.jsx("div",{style:{...N,fontWeight:f==="expected"?600:"initial"},onClick:()=>r("expected"),children:A}),m.jsx("div",{style:{...N,fontWeight:f==="sxs"?600:"initial"},onClick:()=>r("sxs"),children:"Side by side"}),m.jsx("div",{style:{...N,fontWeight:f==="slider"?600:"initial"},onClick:()=>r("slider"),children:"Slider"})]}),m.jsxs("div",{style:{display:"flex",justifyContent:"center",flex:"auto",minHeight:D+60},children:[l.diff&&f==="diff"&&m.jsx(En,{image:X,alt:"Diff",hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z}),l.diff&&f==="actual"&&m.jsx(En,{image:S,alt:"Actual",hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z}),l.diff&&f==="expected"&&m.jsx(En,{image:v,alt:A,hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z}),l.diff&&f==="slider"&&m.jsx(Dv,{expectedImage:v,actualImage:S,hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z,expectedTitle:A}),l.diff&&f==="sxs"&&m.jsxs("div",{style:{display:"flex"},children:[m.jsx(En,{image:v,title:A,hideSize:c,canvasWidth:F*R,canvasHeight:F*U,scale:F}),m.jsx(En,{image:o?X:S,title:o?"Diff":"Actual",onClick:()=>h(!o),hideSize:c,canvasWidth:F*R,canvasHeight:F*U,scale:F})]}),!l.diff&&f==="actual"&&m.jsx(En,{image:S,title:"Actual",hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z}),!l.diff&&f==="expected"&&m.jsx(En,{image:v,title:A,hideSize:c,canvasWidth:j,canvasHeight:D,scale:Z}),!l.diff&&f==="sxs"&&m.jsxs("div",{style:{display:"flex"},children:[m.jsx(En,{image:v,title:A,canvasWidth:F*R,canvasHeight:F*U,scale:F}),m.jsx(En,{image:S,title:"Actual",canvasWidth:F*R,canvasHeight:F*U,scale:F})]})]}),!c&&m.jsxs("div",{style:{alignSelf:"start",lineHeight:"18px",marginLeft:"15px"},children:[m.jsx("div",{children:l.diff&&m.jsx("a",{target:"_blank",href:l.diff.attachment.path,rel:"noreferrer",children:l.diff.attachment.name})}),m.jsx("div",{children:m.jsx("a",{target:u?"":"_blank",href:l.actual.attachment.path,rel:"noreferrer",children:l.actual.attachment.name})}),m.jsx("div",{children:m.jsx("a",{target:u?"":"_blank",href:l.expected.attachment.path,rel:"noreferrer",children:l.expected.attachment.name})})]})]})})},Dv=({expectedImage:l,actualImage:u,canvasWidth:c,canvasHeight:f,scale:r,expectedTitle:o,hideSize:h})=>{const v={position:"absolute",top:0,left:0},[y,A]=ct.useState(c/2),E=l.naturalWidth===u.naturalWidth&&l.naturalHeight===u.naturalHeight;return m.jsxs("div",{style:{flex:"none",display:"flex",alignItems:"center",flexDirection:"column",userSelect:"none"},children:[!h&&m.jsxs("div",{style:{margin:5},children:[!E&&m.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"Expected "}),m.jsx("span",{children:l.naturalWidth}),m.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),m.jsx("span",{children:l.naturalHeight}),!E&&m.jsx("span",{style:{flex:"none",margin:"0 5px 0 15px"},children:"Actual "}),!E&&m.jsx("span",{children:u.naturalWidth}),!E&&m.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),!E&&m.jsx("span",{children:u.naturalHeight})]}),m.jsxs("div",{style:{position:"relative",width:c,height:f,margin:15,...fr},children:[m.jsx(Rv,{orientation:"horizontal",offsets:[y],setOffsets:S=>A(S[0]),resizerColor:"#57606a80",resizerWidth:6}),m.jsx("img",{alt:o,style:{width:l.naturalWidth*r,height:l.naturalHeight*r},draggable:"false",src:l.src}),m.jsx("div",{style:{...v,bottom:0,overflow:"hidden",width:y,...fr},children:m.jsx("img",{alt:"Actual",style:{width:u.naturalWidth*r,height:u.naturalHeight*r},draggable:"false",src:u.src})})]})]})},En=({image:l,title:u,alt:c,hideSize:f,canvasWidth:r,canvasHeight:o,scale:h,onClick:v})=>m.jsxs("div",{style:{flex:"none",display:"flex",alignItems:"center",flexDirection:"column"},children:[!f&&m.jsxs("div",{style:{margin:5},children:[u&&m.jsx("span",{style:{flex:"none",margin:"0 5px"},children:u}),m.jsx("span",{children:l.naturalWidth}),m.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),m.jsx("span",{children:l.naturalHeight})]}),m.jsx("div",{style:{display:"flex",flex:"none",width:r,height:o,margin:15,...fr},children:m.jsx("img",{width:l.naturalWidth*h,height:l.naturalHeight*h,alt:u||c,style:{cursor:v?"pointer":"initial"},draggable:"false",src:l.src,onClick:v})})]});function Mv(l,u){const c=/(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g,f=[];let r,o={},h=!1,v=u==null?void 0:u.fg,y=u==null?void 0:u.bg;for(;(r=c.exec(l))!==null;){const[,,A,,E]=r;if(A){const S=+A;switch(S){case 0:o={};break;case 1:o["font-weight"]="bold";break;case 2:o.opacity="0.8";break;case 3:o["font-style"]="italic";break;case 4:o["text-decoration"]="underline";break;case 7:h=!0;break;case 8:o.display="none";break;case 9:o["text-decoration"]="line-through";break;case 22:delete o["font-weight"],delete o["font-style"],delete o.opacity,delete o["text-decoration"];break;case 23:delete o["font-weight"],delete o["font-style"],delete o.opacity;break;case 24:delete o["text-decoration"];break;case 27:h=!1;break;case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:v=U2[S-30];break;case 39:v=u==null?void 0:u.fg;break;case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:y=U2[S-40];break;case 49:y=u==null?void 0:u.bg;break;case 53:o["text-decoration"]="overline";break;case 90:case 91:case 92:case 93:case 94:case 95:case 96:case 97:v=Q2[S-90];break;case 100:case 101:case 102:case 103:case 104:case 105:case 106:case 107:y=Q2[S-100];break}}else if(E){const S={...o},O=h?y:v;O!==void 0&&(S.color=O);const X=h?v:y;X!==void 0&&(S["background-color"]=X),f.push(`<span style="${Hv(S)}">${jv(E)}</span>`)}}return f.join("")}const U2={0:"var(--vscode-terminal-ansiBlack)",1:"var(--vscode-terminal-ansiRed)",2:"var(--vscode-terminal-ansiGreen)",3:"var(--vscode-terminal-ansiYellow)",4:"var(--vscode-terminal-ansiBlue)",5:"var(--vscode-terminal-ansiMagenta)",6:"var(--vscode-terminal-ansiCyan)",7:"var(--vscode-terminal-ansiWhite)"},Q2={0:"var(--vscode-terminal-ansiBrightBlack)",1:"var(--vscode-terminal-ansiBrightRed)",2:"var(--vscode-terminal-ansiBrightGreen)",3:"var(--vscode-terminal-ansiBrightYellow)",4:"var(--vscode-terminal-ansiBrightBlue)",5:"var(--vscode-terminal-ansiBrightMagenta)",6:"var(--vscode-terminal-ansiBrightCyan)",7:"var(--vscode-terminal-ansiBrightWhite)"};function jv(l){return l.replace(/[&"<>]/g,u=>({"&":"&amp;",'"':"&quot;","<":"&lt;",">":"&gt;"})[u])}function Hv(l){return Object.entries(l).map(([u,c])=>`${u}: ${c}`).join("; ")}const wr=({code:l,children:u,testId:c})=>{const f=ct.useMemo(()=>Uv(l),[l]);return m.jsxs("div",{className:"test-error-container test-error-text","data-testid":c,children:[u,m.jsx("div",{className:"test-error-view",dangerouslySetInnerHTML:{__html:f||""}})]})},Nv=({prompt:l})=>{const[u,c]=ct.useState(!1);return m.jsx("button",{className:"button",style:{minWidth:100},onClick:async()=>{await navigator.clipboard.writeText(l),c(!0),setTimeout(()=>{c(!1)},3e3)},children:u?"Copied":"Copy prompt"})},Bv=({diff:l})=>m.jsx("div",{"data-testid":"test-screenshot-error-view",className:"test-error-view",children:m.jsx(um,{diff:l,hideDetails:!0},"image-diff")});function Uv(l){return Mv(l||"",{bg:"var(--color-canvas-subtle)",fg:"var(--color-fg-default)"})}const Qv=` 66 + # Instructions 67 + 68 + - Following Playwright test failed. 69 + - Explain why, be concise, respect Playwright best practices. 70 + - Provide a snippet of code with the fix, if possible. 71 + `.trimStart();async function zv({testInfo:l,metadata:u,errorContext:c,errors:f,buildCodeFrame:r,stdout:o,stderr:h}){var S;const v=new Set(f.filter(O=>O.message&&!O.message.includes(` 72 + `)).map(O=>O.message));for(const O of f)for(const X of v.keys())(S=O.message)!=null&&S.includes(X)&&v.delete(X);const y=f.filter(O=>!(!O.message||!O.message.includes(` 73 + `)&&!v.has(O.message)));if(!y.length)return;const A=[Qv,"# Test info","",l];o&&A.push("","# Stdout","","```",Jf(o),"```"),h&&A.push("","# Stderr","","```",Jf(h),"```"),A.push("","# Error details");for(const O of y)A.push("","```",Jf(O.message||""),"```");c&&A.push(c);const E=await r(y[y.length-1]);return E&&A.push("","# Test source","","```ts",E,"```"),u!=null&&u.gitDiff&&A.push("","# Local changes","","```diff",u.gitDiff,"```"),A.join(` 74 + `)}const Yv=new RegExp("([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))","g");function Jf(l){return l.replace(Yv,"")}function Lv(l,u){var f;const c=new Map;for(const r of l){const o=r.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);if(!o)continue;const[,h,v,y=""]=o,A=h+y;let E=c.get(A);E||(E={name:A,anchors:[`attachment-${h}`]},c.set(A,E)),E.anchors.push(`attachment-${u.attachments.indexOf(r)}`),v==="actual"&&(E.actual={attachment:r}),v==="expected"&&(E.expected={attachment:r,title:"Expected"}),v==="previous"&&(E.expected={attachment:r,title:"Previous"}),v==="diff"&&(E.diff={attachment:r})}for(const[r,o]of c)!o.actual||!o.expected?c.delete(r):(l.delete(o.actual.attachment),l.delete(o.expected.attachment),l.delete((f=o.diff)==null?void 0:f.attachment));return[...c.values()]}const Gv=({test:l,result:u,testRunMetadata:c,options:f})=>{const{screenshots:r,videos:o,traces:h,otherAttachments:v,diffs:y,errors:A,otherAttachmentAnchors:E,screenshotAnchors:S,errorContext:O}=ct.useMemo(()=>{const B=u.attachments.filter(N=>!N.name.startsWith("_")),b=new Set(B.filter(N=>N.contentType.startsWith("image/"))),p=[...b].map(N=>`attachment-${B.indexOf(N)}`),x=B.filter(N=>N.contentType.startsWith("video/")),R=B.filter(N=>N.name==="trace"),U=B.find(N=>N.name==="error-context"),Z=new Set(B);[...b,...x,...R].forEach(N=>Z.delete(N));const F=[...Z].map(N=>`attachment-${B.indexOf(N)}`),j=Lv(b,u),D=u.errors.map(N=>N.message);return{screenshots:[...b],videos:x,traces:R,otherAttachments:Z,diffs:j,errors:D,otherAttachmentAnchors:F,screenshotAnchors:p,errorContext:U}},[u]),X=P5(async()=>{if(f!=null&&f.noCopyPrompt)return;const B=u.attachments.find(R=>R.name==="stdout"),b=u.attachments.find(R=>R.name==="stderr"),p=B!=null&&B.body&&B.contentType==="text/plain"?B.body:void 0,x=b!=null&&b.body&&b.contentType==="text/plain"?b.body:void 0;return await zv({testInfo:[`- Name: ${l.path.join(" >> ")} >> ${l.title}`,`- Location: ${l.location.file}:${l.location.line}:${l.location.column}`].join(` 75 + `),metadata:c,errorContext:O!=null&&O.path?await fetch(O.path).then(R=>R.text()):O==null?void 0:O.body,errors:u.errors,buildCodeFrame:async R=>R.codeframe,stdout:p,stderr:x})},[l,O,c,u],void 0);return m.jsxs("div",{className:"test-result",children:[!!A.length&&m.jsxs(ke,{header:"Errors",children:[X&&m.jsx("div",{style:{position:"absolute",right:"16px",padding:"10px",zIndex:1},children:m.jsx(Nv,{prompt:X})}),A.map((B,b)=>{const p=Xv(B,y);return m.jsxs(m.Fragment,{children:[m.jsx(wr,{code:B},"test-result-error-message-"+b),p&&m.jsx(Bv,{diff:p})]})})]}),!!u.steps.length&&m.jsx(ke,{header:"Test Steps",children:u.steps.map((B,b)=>m.jsx(cm,{step:B,result:u,test:l,depth:0},`step-${b}`))}),y.map((B,b)=>m.jsx(Si,{id:B.anchors,children:m.jsx(ke,{dataTestId:"test-results-image-diff",header:`Image mismatch: ${B.name}`,revealOnAnchorId:B.anchors,children:m.jsx(um,{diff:B})})},`diff-${b}`)),!!r.length&&m.jsx(ke,{header:"Screenshots",revealOnAnchorId:S,children:r.map((B,b)=>m.jsxs(Si,{id:`attachment-${u.attachments.indexOf(B)}`,children:[m.jsx("a",{href:Ve(B.path),children:m.jsx("img",{className:"screenshot",src:Ve(B.path)})}),m.jsx(nc,{attachment:B,result:u})]},`screenshot-${b}`))}),!!h.length&&m.jsx(Si,{id:"attachment-trace",children:m.jsx(ke,{header:"Traces",revealOnAnchorId:"attachment-trace",children:m.jsxs("div",{children:[m.jsx("a",{href:Ve(nm(h)),children:m.jsx("img",{className:"screenshot",src:Cv,style:{width:192,height:117,marginLeft:20}})}),h.map((B,b)=>m.jsx(nc,{attachment:B,result:u,linkName:h.length===1?"trace":`trace-${b+1}`},`trace-${b}`))]})})}),!!o.length&&m.jsx(Si,{id:"attachment-video",children:m.jsx(ke,{header:"Videos",revealOnAnchorId:"attachment-video",children:o.map(B=>m.jsxs("div",{children:[m.jsx("video",{controls:!0,children:m.jsx("source",{src:Ve(B.path),type:B.contentType})}),m.jsx(nc,{attachment:B,result:u})]},B.path))})}),!!v.size&&m.jsx(ke,{header:"Attachments",revealOnAnchorId:E,dataTestId:"attachments",children:[...v].map((B,b)=>m.jsx(Si,{id:`attachment-${u.attachments.indexOf(B)}`,children:m.jsx(nc,{attachment:B,result:u,openInNewTab:B.contentType.startsWith("text/html")})},`attachment-link-${b}`))})]})};function Xv(l,u){const c=l.split(` 76 + `)[0];if(!(!c.includes("toHaveScreenshot")&&!c.includes("toMatchSnapshot")))return u.find(f=>l.includes(f.name))}const cm=({test:l,step:u,result:c,depth:f})=>{const r=se();return m.jsx(Tv,{title:m.jsxs("div",{"aria-label":u.title,className:"step-title-container",children:[hc(u.error||u.duration===-1?"failed":u.skipped?"skipped":"passed"),m.jsxs("span",{className:"step-title-text",children:[m.jsx("span",{children:u.title}),u.count>1&&m.jsxs(m.Fragment,{children:[" ✕ ",m.jsx("span",{className:"test-result-counter",children:u.count})]}),u.location&&m.jsxs("span",{className:"test-result-path",children:["— ",u.location.file,":",u.location.line]})]}),m.jsx("span",{className:"step-spacer"}),u.attachments.length>0&&m.jsx("a",{className:"step-attachment-link",title:"reveal attachment",href:Ve(Cn({test:l,result:c,anchor:`attachment-${u.attachments[0]}`},r)),onClick:o=>{o.stopPropagation()},children:Ih()}),m.jsx("span",{className:"step-duration",children:Ol(u.duration)})]}),loadChildren:u.steps.length||u.snippet?()=>{const o=u.snippet?[m.jsx(wr,{testId:"test-snippet",code:u.snippet},"line")]:[],h=u.steps.map((v,y)=>m.jsx(cm,{step:v,depth:f+1,result:c,test:l},y));return o.concat(h)}:void 0,depth:f})},Vv=({projectNames:l,test:u,testRunMetadata:c,run:f,next:r,prev:o,options:h})=>{const[v,y]=ct.useState(f),A=se(),E=u.annotations.filter(S=>!S.type.startsWith("_"))??[];return m.jsxs(m.Fragment,{children:[m.jsx(Or,{title:u.title,leftSuperHeader:m.jsx("div",{className:"test-case-path",children:u.path.join(" › ")}),rightSuperHeader:m.jsxs(m.Fragment,{children:[m.jsx("div",{className:Ze(!o&&"hidden"),children:m.jsx(Tn,{href:Cn({test:o},A),children:"« previous"})}),m.jsx("div",{style:{width:10}}),m.jsx("div",{className:Ze(!r&&"hidden"),children:m.jsx(Tn,{href:Cn({test:r},A),children:"next »"})})]})}),m.jsxs("div",{className:"hbox",style:{lineHeight:"24px"},children:[m.jsx("div",{className:"test-case-location",children:m.jsxs(Sr,{value:`${u.location.file}:${u.location.line}`,children:[u.location.file,":",u.location.line]})}),m.jsx("div",{style:{flex:"auto"}}),m.jsx(tm,{test:u,trailingSeparator:!0}),m.jsx("div",{className:"test-case-duration",children:Ol(u.duration)})]}),m.jsx($h,{style:{marginLeft:"6px"},projectNames:l,activeProjectName:u.projectName,otherLabels:u.tags}),u.results.length===0&&E.length!==0&&m.jsx(ke,{header:"Annotations",dataTestId:"test-case-annotations",children:E.map((S,O)=>m.jsx(z2,{annotation:S},O))}),m.jsx(Sv,{tabs:u.results.map((S,O)=>({id:String(O),title:m.jsxs("div",{style:{display:"flex",alignItems:"center"},children:[hc(S.status)," ",Zv(O),u.results.length>1&&m.jsx("span",{className:"test-case-run-duration",children:Ol(S.duration)})]}),render:()=>{const X=S.annotations.filter(B=>!B.type.startsWith("_"));return m.jsxs(m.Fragment,{children:[!!X.length&&m.jsx(ke,{header:"Annotations",dataTestId:"test-case-annotations",children:X.map((B,b)=>m.jsx(z2,{annotation:B},b))}),m.jsx(Gv,{test:u,result:S,testRunMetadata:c,options:h})]})}}))||[],selectedTab:String(v),setSelectedTab:S=>y(+S)})]})};function z2({annotation:{type:l,description:u}}){return m.jsxs("div",{className:"test-case-annotation",children:[m.jsx("span",{style:{fontWeight:"bold"},children:l}),u&&m.jsxs(Sr,{value:u,children:[": ",Di(u)]})]})}function Zv(l){return l?`Retry #${l}`:"Run"}const sm=({file:l,projectNames:u,isFileExpanded:c,setFileExpanded:f,footer:r})=>{const o=se();return m.jsx(im,{expanded:c?c(l.fileId):void 0,noInsets:!0,setExpanded:f?(h=>f(l.fileId,h)):void 0,header:m.jsx("span",{className:"chip-header-allow-selection",children:l.fileName}),footer:r,children:l.tests.map(h=>m.jsxs("div",{className:Ze("test-file-test","test-file-test-outcome-"+h.outcome),children:[m.jsxs("div",{className:"hbox",style:{alignItems:"flex-start"},children:[m.jsxs("div",{className:"hbox",children:[m.jsx("span",{className:"test-file-test-status-icon",children:hc(h.outcome)}),m.jsxs("span",{children:[m.jsx(Tn,{href:Cn({test:h},o),title:[...h.path,h.title].join(" › "),children:m.jsx("span",{className:"test-file-title",children:[...h.path,h.title].join(" › ")})}),m.jsx($h,{style:{marginLeft:"6px"},projectNames:u,activeProjectName:h.projectName,otherLabels:h.tags})]})]}),m.jsx("span",{"data-testid":"test-duration",style:{minWidth:"50px",textAlign:"right"},children:Ol(h.duration)})]}),m.jsx("div",{className:"test-file-details-row",children:m.jsxs("div",{className:"test-file-details-row-items",children:[m.jsx(Tn,{href:Cn({test:h},o),title:[...h.path,h.title].join(" › "),className:"test-file-path-link",children:m.jsxs("span",{className:"test-file-path",children:[h.location.file,":",h.location.line]})}),m.jsx(qv,{test:h}),m.jsx(Iv,{test:h}),m.jsx(tm,{test:h,dim:!0})]})})]},`test-${h.testId}`))})};function qv({test:l}){const u=se();for(const c of l.results)for(const f of c.attachments)if(f.contentType.startsWith("image/")&&f.name.match(/-(expected|actual|diff)/))return m.jsx(Tr,{href:Cn({test:l,result:c,anchor:`attachment-${c.attachments.indexOf(f)}`},u),title:"View images",dim:!0,children:k5()})}function Iv({test:l}){const u=se(),c=l.results.find(f=>f.attachments.some(r=>r.name==="video"));return c?m.jsx(Tr,{href:Cn({test:l,result:c,anchor:"attachment-video"},u),title:"View video",dim:!0,children:J5()}):void 0}class Kv extends ct.Component{constructor(){super(...arguments);yn(this,"state",{error:null,errorInfo:null})}componentDidCatch(c,f){this.setState({error:c,errorInfo:f})}render(){var c,f,r;return this.state.error||this.state.errorInfo?m.jsxs("div",{className:"metadata-view p-3",children:[m.jsx("p",{children:"An error was encountered when trying to render metadata."}),m.jsx("p",{children:m.jsxs("pre",{style:{overflow:"scroll"},children:[(c=this.state.error)==null?void 0:c.message,m.jsx("br",{}),(f=this.state.error)==null?void 0:f.stack,m.jsx("br",{}),(r=this.state.errorInfo)==null?void 0:r.componentStack]})})]}):this.props.children}}const kv=l=>m.jsx(Kv,{children:m.jsx(Jv,{metadata:l.metadata})}),Jv=l=>{const u=l.metadata,c=se().has("show-metadata-other")?Object.entries(l.metadata).filter(([r])=>!fm.has(r)):[];if(u.ci||u.gitCommit||c.length>0)return m.jsxs("div",{className:"metadata-view",children:[u.ci&&!u.gitCommit&&m.jsx(Fv,{info:u.ci}),u.gitCommit&&m.jsx(Wv,{ci:u.ci,commit:u.gitCommit}),c.length>0&&m.jsxs(m.Fragment,{children:[(u.gitCommit||u.ci)&&m.jsx("div",{className:"metadata-separator"}),m.jsx("div",{className:"metadata-section metadata-properties",role:"list",children:c.map(([r,o])=>{const h=typeof o!="object"||o===null||o===void 0?String(o):JSON.stringify(o),v=h.length>1e3?h.slice(0,1e3)+"…":h;return m.jsx("div",{className:"copyable-property",role:"listitem",children:m.jsxs(Sr,{value:h,children:[m.jsx("span",{style:{fontWeight:"bold"},title:r,children:r}),": ",m.jsx("span",{title:v,children:Di(v)})]})},r)})})]})]})},Fv=({info:l})=>{const u=l.prTitle||`Commit ${l.commitHash}`,c=l.prHref||l.commitHref;return m.jsx("div",{className:"metadata-section",role:"list",children:m.jsx("div",{role:"listitem",children:m.jsx("a",{href:Ve(c),target:"_blank",rel:"noopener noreferrer",title:u,children:u})})})},Wv=({ci:l,commit:u})=>{const c=(l==null?void 0:l.prTitle)||u.subject,f=(l==null?void 0:l.prHref)||(l==null?void 0:l.commitHref),r=` <${u.author.email}>`,o=`${u.author.name}${r}`,h=Intl.DateTimeFormat(void 0,{dateStyle:"medium"}).format(u.committer.time),v=Intl.DateTimeFormat(void 0,{dateStyle:"full",timeStyle:"long"}).format(u.committer.time);return m.jsxs("div",{className:"metadata-section",role:"list",children:[m.jsxs("div",{role:"listitem",children:[f&&m.jsx("a",{href:Ve(f),target:"_blank",rel:"noopener noreferrer",title:c,children:c}),!f&&m.jsx("span",{title:c,children:c})]}),m.jsxs("div",{role:"listitem",className:"hbox",children:[m.jsx("span",{className:"mr-1",children:o}),m.jsxs("span",{title:v,children:[" on ",h]})]})]})},fm=new Set(["ci","gitCommit","gitDiff","actualWorkers"]),_v=l=>{const u=Object.entries(l).filter(([c])=>!fm.has(c));return!l.ci&&!l.gitCommit&&!u.length},Pv=({files:l,expandedFiles:u,setExpandedFiles:c,projectNames:f})=>{const r=ct.useMemo(()=>{const o=[];let h=0;for(const v of l)h+=v.tests.length,o.push({file:v,defaultExpanded:h<200});return o},[l]);return m.jsx(m.Fragment,{children:r.length>0?r.map(({file:o,defaultExpanded:h})=>m.jsx(sm,{file:o,projectNames:f,isFileExpanded:v=>{const y=u.get(v);return y===void 0?h:!!y},setFileExpanded:(v,y)=>{const A=new Map(u);A.set(v,y),c(A)}},`file-${o.fileId}`)):m.jsx("div",{className:"chip-header test-file-no-files",children:"No tests found"})})},Y2=({report:l,filteredStats:u,metadataVisible:c,toggleMetadataVisible:f})=>{if(!l)return null;const r=l.projectNames.length===1&&!!l.projectNames[0],o=!r&&!u,h=!_v(l.metadata)&&m.jsxs("div",{className:Ze("metadata-toggle",!o&&"metadata-toggle-second-line"),role:"button",onClick:f,title:c?"Hide metadata":"Show metadata",children:[c?Ni():Cl(),"Metadata"]}),v=m.jsxs("div",{className:"test-file-header-info",children:[r&&m.jsxs("div",{"data-testid":"project-name",children:["Project: ",l.projectNames[0]]}),u&&m.jsxs("div",{"data-testid":"filtered-tests-count",children:["Filtered: ",u.total," ",!!u.total&&"("+Ol(u.duration)+")"]}),o&&h]}),y=m.jsxs(m.Fragment,{children:[m.jsx("div",{"data-testid":"overall-time",style:{marginRight:"10px"},children:l?new Date(l.startTime).toLocaleString():""}),m.jsxs("div",{"data-testid":"overall-duration",children:["Total time: ",Ol(l.duration??0)]})]});return m.jsxs(m.Fragment,{children:[m.jsx(Or,{title:l.options.title,leftSuperHeader:v,rightSuperHeader:y}),!o&&h,c&&m.jsx(kv,{metadata:l.metadata}),!!l.errors.length&&m.jsx(ke,{header:"Errors",dataTestId:"report-errors",children:l.errors.map((A,E)=>m.jsx(wr,{code:A},"test-report-error-message-"+E))})]})},rm=l=>{const u=Math.round(l/1e3),c=Math.floor(u/60),f=u%60;return c===0?`${f}s`:`${c}m ${f}s`},$v=({entries:l})=>{const f=Math.max(...l.map(D=>D.label.length))*10,o={top:20,right:20,bottom:40,left:Math.min(800*.5,Math.max(50,f))},h=800-o.left-o.right,v=Math.min(...l.map(D=>D.startTime)),y=Math.max(...l.map(D=>D.startTime+D.duration));let A,E;const S=y-v;S<60*1e3?(A=10*1e3,E=!0):S<300*1e3?(A=30*1e3,E=!0):S<1800*1e3?(A=300*1e3,E=!1):(A=600*1e3,E=!1);const O=Math.ceil(v/A)*A,X=(D,N)=>{const K=new Date(D).toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",second:E?"2-digit":void 0});if(N)return K;if(K.endsWith(" AM")||K.endsWith(" PM"))return K.slice(0,-3)},b=(y-v)*1.1,p=Math.ceil(b/A)*A,x=h/p,R=20,U=8,Z=l.length*(R+U),F=[];for(let D=O;D<=v+p;D+=A){const N=D-v;F.push({x:N*x,label:X(D,D===O)})}const j=Z+o.top+o.bottom;return m.jsx("svg",{viewBox:`0 0 800 ${j}`,preserveAspectRatio:"xMidYMid meet",style:{width:"100%",height:"auto"},role:"img",children:m.jsxs("g",{transform:`translate(${o.left}, ${o.top})`,role:"presentation",children:[F.map(({x:D,label:N},K)=>m.jsxs("g",{"aria-hidden":"true",children:[m.jsx("line",{x1:D,y1:0,x2:D,y2:Z,stroke:"var(--color-border-muted)",strokeWidth:"1"}),m.jsx("text",{x:D,y:Z+20,textAnchor:"middle",dominantBaseline:"middle",fontSize:"12",fill:"var(--color-fg-muted)",children:N})]},K)),l.map((D,N)=>{const K=D.startTime-v,J=D.duration*x,k=K*x,nt=N*(R+U),P=["var(--color-scale-blue-2)","var(--color-scale-blue-3)","var(--color-scale-blue-4)"],st=P[N%P.length];return m.jsxs("g",{role:"listitem","aria-label":D.tooltip,children:[m.jsx("rect",{className:"gantt-bar",x:k,y:nt,width:J,height:R,fill:st,rx:"2",tabIndex:0,children:m.jsx("title",{children:D.tooltip})}),m.jsx("text",{x:k+J+6,y:nt+R/2,dominantBaseline:"middle",fontSize:"12",fill:"var(--color-fg-muted)","aria-hidden":"true",children:rm(D.duration)}),m.jsx("text",{x:-10,y:nt+R/2,textAnchor:"end",dominantBaseline:"middle",fontSize:"12",fill:"var(--color-fg-muted)","aria-hidden":"true",children:D.label})]},N)}),m.jsx("line",{x1:0,y1:0,x2:0,y2:Z,stroke:"var(--color-fg-muted)",strokeWidth:"1","aria-hidden":"true"}),m.jsx("line",{x1:0,y1:Z,x2:h,y2:Z,stroke:"var(--color-fg-muted)",strokeWidth:"1","aria-hidden":"true"})]})})};function ty({report:l,tests:u}){return m.jsxs(m.Fragment,{children:[m.jsx(ny,{report:l}),m.jsx(ey,{report:l,tests:u})]})}function ey({report:l,tests:u}){const[c,f]=ue.useState(50);return m.jsx(sm,{file:{fileId:"slowest",fileName:"Slowest Tests",tests:u.slice(0,c),stats:null},projectNames:l.json().projectNames,footer:c<u.length?m.jsxs("button",{className:"link-badge fullwidth-link",style:{padding:"8px 5px"},onClick:()=>f(r=>r+50),children:[Ni(),"Show 50 more"]}):void 0})}function ny({report:l}){const u=l.json().machines;if(u.length===0)return null;const c=u.map(f=>{const r=f.tag.join(" "),o=new Date(f.startTime).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"short"});let h=`${r} started at ${o}, runs ${rm(f.duration)}`;return f.shardIndex&&(h+=` (shard ${f.shardIndex})`),{label:r,tooltip:h,startTime:f.startTime,duration:f.duration,shardIndex:f.shardIndex??1}}).sort((f,r)=>f.label.localeCompare(r.label)||f.shardIndex-r.shardIndex);return m.jsx(ke,{header:"Timeline",children:m.jsx($v,{entries:c})})}const ay=l=>!l.has("testId")&&!l.has("speedboard"),ly=l=>l.has("testId"),iy=l=>l.has("speedboard")&&!l.has("testId"),uy=({report:l})=>{var Z,F;const u=se(),[c,f]=ct.useState(new Map),[r,o]=ct.useState(u.get("q")||""),[h,v]=ct.useState(!1),y=u.has("speedboard"),[A]=_h("mergeFiles",!1),E=u.get("testId"),S=((Z=u.get("q"))==null?void 0:Z.toString())||"",O=S?"&q="+S:"",X=(F=l==null?void 0:l.json())==null?void 0:F.options.title,B=ct.useMemo(()=>{const j=new Map;for(const D of(l==null?void 0:l.json().files)||[])for(const N of D.tests)j.set(N.testId,D.fileId);return j},[l]),b=ct.useMemo(()=>rc.parse(r),[r]),p=ct.useMemo(()=>b.empty()?void 0:sy((l==null?void 0:l.json().files)||[],b),[l,b]),x=ct.useMemo(()=>y?oy(l,b):A?ry(l,b):fy(l,b),[l,b,A,y]),{prev:R,next:U}=ct.useMemo(()=>{const j=x.tests.findIndex(K=>K.testId===E),D=j>0?x.tests[j-1]:void 0,N=j<x.tests.length-1?x.tests[j+1]:void 0;return{prev:D,next:N}},[E,x]);return ct.useEffect(()=>{const j=D=>{if(D.target instanceof HTMLInputElement||D.target instanceof HTMLTextAreaElement||D.shiftKey||D.ctrlKey||D.metaKey||D.altKey)return;const N=new URLSearchParams(u);switch(D.key){case"a":D.preventDefault(),ca("#?");break;case"p":D.preventDefault(),N.delete("testId"),N.delete("speedboard"),ca(Na(N,"s:passed",!1));break;case"f":D.preventDefault(),N.delete("testId"),N.delete("speedboard"),ca(Na(N,"s:failed",!1));break;case"ArrowLeft":R&&(D.preventDefault(),N.delete("testId"),ca(Cn({test:R},N)+O));break;case"ArrowRight":U&&(D.preventDefault(),N.delete("testId"),ca(Cn({test:U},N)+O));break}};return document.addEventListener("keydown",j),()=>document.removeEventListener("keydown",j)},[R,U,O,S,u]),ct.useEffect(()=>{X?document.title=X:document.title="Playwright Test Report"},[X]),m.jsx("div",{className:"htmlreport vbox px-4 pb-4",children:m.jsxs("main",{children:[l&&m.jsx(Ev,{stats:l.json().stats,filterText:r,setFilterText:o}),m.jsxs(Kf,{predicate:ay,children:[m.jsx(Y2,{report:l==null?void 0:l.json(),filteredStats:p,metadataVisible:h,toggleMetadataVisible:()=>v(j=>!j)}),m.jsx(Pv,{files:x.files,expandedFiles:c,setExpandedFiles:f,projectNames:(l==null?void 0:l.json().projectNames)||[]})]}),m.jsxs(Kf,{predicate:iy,children:[m.jsx(Y2,{report:l==null?void 0:l.json(),filteredStats:p,metadataVisible:h,toggleMetadataVisible:()=>v(j=>!j)}),l&&m.jsx(ty,{report:l,tests:x.tests})]}),m.jsx(Kf,{predicate:ly,children:l&&m.jsx(cy,{report:l,next:U,prev:R,testId:E,testIdToFileIdMap:B})})]})})},cy=({report:l,testIdToFileIdMap:u,next:c,prev:f,testId:r})=>{const[o,h]=ct.useState("loading"),v=+(se().get("run")||"0");if(ct.useEffect(()=>{(async()=>{if(!r||typeof o=="object"&&r===o.testId)return;const S=u.get(r);if(!S){h("not-found");return}const O=await l.entry(`${S}.json`);h((O==null?void 0:O.tests.find(X=>X.testId===r))||"not-found")})()},[o,l,r,u]),o==="loading")return m.jsx("div",{className:"test-case-column"});if(o==="not-found")return m.jsxs("div",{className:"test-case-column",children:[m.jsx(Or,{title:"Test not found"}),m.jsxs("div",{className:"test-case-location",children:["Test ID: ",r]})]});const{projectNames:y,metadata:A,options:E}=l.json();return m.jsx("div",{className:"test-case-column",children:m.jsx(Vv,{projectNames:y,testRunMetadata:A,options:E,next:c,prev:f,test:o,run:v})})};function sy(l,u){const c={total:0,duration:0};for(const f of l){const r=f.tests.filter(o=>u.matches(o));c.total+=r.length;for(const o of r)c.duration+=o.duration}return c}function fy(l,u){const c={files:[],tests:[]};for(const f of(l==null?void 0:l.json().files)||[]){const r=f.tests.filter(o=>u.matches(o));r.length&&c.files.push({...f,tests:r}),c.tests.push(...r)}return c}function ry(l,u){const c=[],f=new Map;for(const o of(l==null?void 0:l.json().files)||[]){const h=o.tests.filter(v=>u.matches(v));for(const v of h){const y=v.path[0]??"<anonymous>";let A=f.get(y);A||(A={fileId:y,fileName:y,tests:[],stats:{total:0,expected:0,unexpected:0,flaky:0,skipped:0,ok:!0}},f.set(y,A),c.push(A));const E={...v,path:v.path.slice(1)};A.tests.push(E)}}c.sort((o,h)=>o.fileName.localeCompare(h.fileName));const r={files:c,tests:[]};for(const o of c)r.tests.push(...o.tests);return r}function oy(l,u){const f=((l==null?void 0:l.json().files)||[]).flatMap(r=>r.tests).filter(r=>u.matches(r));return f.sort((r,o)=>o.duration-r.duration),{files:[],tests:f}}const dy="data:image/svg+xml,%3csvg%20width='400'%20height='400'%20viewBox='0%200%20400%20400'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M136.444%20221.556C123.558%20225.213%20115.104%20231.625%20109.535%20238.032C114.869%20233.364%20122.014%20229.08%20131.652%20226.348C141.51%20223.554%20149.92%20223.574%20156.869%20224.915V219.481C150.941%20218.939%20144.145%20219.371%20136.444%20221.556ZM108.946%20175.876L61.0895%20188.484C61.0895%20188.484%2061.9617%20189.716%2063.5767%20191.36L104.153%20180.668C104.153%20180.668%20103.578%20188.077%2098.5847%20194.705C108.03%20187.559%20108.946%20175.876%20108.946%20175.876ZM149.005%20288.347C81.6582%20306.486%2046.0272%20228.438%2035.2396%20187.928C30.2556%20169.229%2028.0799%20155.067%2027.5%20145.928C27.4377%20144.979%2027.4665%20144.179%2027.5336%20143.446C24.04%20143.657%2022.3674%20145.473%2022.7077%20150.721C23.2876%20159.855%2025.4633%20174.016%2030.4473%20192.721C41.2301%20233.225%2076.8659%20311.273%20144.213%20293.134C158.872%20289.185%20169.885%20281.992%20178.152%20272.81C170.532%20279.692%20160.995%20285.112%20149.005%20288.347ZM161.661%20128.11V132.903H188.077C187.535%20131.206%20186.989%20129.677%20186.447%20128.11H161.661Z'%20fill='%232D4552'/%3e%3cpath%20d='M193.981%20167.584C205.861%20170.958%20212.144%20179.287%20215.465%20186.658L228.711%20190.42C228.711%20190.42%20226.904%20164.623%20203.57%20157.995C181.741%20151.793%20168.308%20170.124%20166.674%20172.496C173.024%20167.972%20182.297%20164.268%20193.981%20167.584ZM299.422%20186.777C277.573%20180.547%20264.145%20198.916%20262.535%20201.255C268.89%20196.736%20278.158%20193.031%20289.837%20196.362C301.698%20199.741%20307.976%20208.06%20311.307%20215.436L324.572%20219.212C324.572%20219.212%20322.736%20193.41%20299.422%20186.777ZM286.262%20254.795L176.072%20223.99C176.072%20223.99%20177.265%20230.038%20181.842%20237.869L274.617%20263.805C282.255%20259.386%20286.262%20254.795%20286.262%20254.795ZM209.867%20321.102C122.618%20297.71%20133.166%20186.543%20147.284%20133.865C153.097%20112.156%20159.073%2096.0203%20164.029%2085.204C161.072%2084.5953%20158.623%2086.1529%20156.203%2091.0746C150.941%20101.747%20144.212%20119.124%20137.7%20143.45C123.586%20196.127%20113.038%20307.29%20200.283%20330.682C241.406%20341.699%20273.442%20324.955%20297.323%20298.659C274.655%20319.19%20245.714%20330.701%20209.867%20321.102Z'%20fill='%232D4552'/%3e%3cpath%20d='M161.661%20262.296V239.863L99.3324%20257.537C99.3324%20257.537%20103.938%20230.777%20136.444%20221.556C146.302%20218.762%20154.713%20218.781%20161.661%20220.123V128.11H192.869C189.471%20117.61%20186.184%20109.526%20183.423%20103.909C178.856%2094.612%20174.174%20100.775%20163.545%20109.665C156.059%20115.919%20137.139%20129.261%20108.668%20136.933C80.1966%20144.61%2057.179%20142.574%2047.5752%20140.911C33.9601%20138.562%2026.8387%20135.572%2027.5049%20145.928C28.0847%20155.062%2030.2605%20169.224%2035.2445%20187.928C46.0272%20228.433%2081.663%20306.481%20149.01%20288.342C166.602%20283.602%20179.019%20274.233%20187.626%20262.291H161.661V262.296ZM61.0848%20188.484L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6614%20203.743%2061.0848%20188.484%2061.0848%20188.484Z'%20fill='%23E2574C'/%3e%3cpath%20d='M341.786%20129.174C329.345%20131.355%20299.498%20134.072%20262.612%20124.185C225.716%20114.304%20201.236%2097.0224%20191.537%2088.8994C177.788%2077.3834%20171.74%2069.3802%20165.788%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.098C297.093%20344.47%20343.53%20242.92%20357.644%20190.238C364.157%20165.917%20367.013%20147.5%20367.799%20135.625C368.695%20122.173%20359.455%20126.078%20341.786%20129.174ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756ZM223.42%20268.713C182.403%20256.698%20176.077%20223.99%20176.077%20223.99L286.262%20254.796C286.262%20254.791%20264.021%20280.578%20223.42%20268.713ZM262.377%20201.495C262.377%20201.495%20276.107%20180.126%20299.422%20186.773C322.736%20193.411%20324.572%20219.208%20324.572%20219.208L262.377%20201.495Z'%20fill='%232EAD33'/%3e%3cpath%20d='M139.88%20246.04L99.3324%20257.532C99.3324%20257.532%20103.737%20232.44%20133.607%20222.496L110.647%20136.33L108.663%20136.933C80.1918%20144.611%2057.1742%20142.574%2047.5704%20140.911C33.9554%20138.563%2026.834%20135.572%2027.5001%20145.929C28.08%20155.063%2030.2557%20169.224%2035.2397%20187.929C46.0225%20228.433%2081.6583%20306.481%20149.005%20288.342L150.989%20287.719L139.88%20246.04ZM61.0848%20188.485L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6615%20203.743%2061.0848%20188.485%2061.0848%20188.485Z'%20fill='%23D65348'/%3e%3cpath%20d='M225.27%20269.163L223.415%20268.712C182.398%20256.698%20176.072%20223.99%20176.072%20223.99L232.89%20239.872L262.971%20124.281L262.607%20124.185C225.711%20114.304%20201.232%2097.0224%20191.532%2088.8994C177.783%2077.3834%20171.735%2069.3802%20165.783%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.097L211.655%20321.5L225.27%20269.163ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756Z'%20fill='%231D8D22'/%3e%3cpath%20d='M141.946%20245.451L131.072%20248.537C133.641%20263.019%20138.169%20276.917%20145.276%20289.195C146.513%20288.922%20147.74%20288.687%20149%20288.342C152.302%20287.451%20155.364%20286.348%20158.312%20285.145C150.371%20273.361%20145.118%20259.789%20141.946%20245.451ZM137.7%20143.451C132.112%20164.307%20127.113%20194.326%20128.489%20224.436C130.952%20223.367%20133.554%20222.371%20136.444%20221.551L138.457%20221.101C136.003%20188.939%20141.308%20156.165%20147.284%20133.866C148.799%20128.225%20150.318%20122.978%20151.832%20118.085C149.393%20119.637%20146.767%20121.228%20143.776%20122.867C141.759%20129.093%20139.722%20135.898%20137.7%20143.451Z'%20fill='%23C04B41'/%3e%3c/svg%3e",Ff=N5,Rr=document.createElement("link");Rr.rel="shortcut icon";Rr.href=dy;document.head.appendChild(Rr);const hy=()=>{const[l,u]=ct.useState();return ct.useEffect(()=>{const c=new my;c.load().then(()=>{var f;(f=document.getElementById("playwrightReportBase64"))==null||f.remove(),u(c)})},[]),m.jsx(cv,{children:m.jsx(uy,{report:l})})};window.onload=()=>{gv(),X5.createRoot(document.querySelector("#root")).render(m.jsx(hy,{}))};class my{constructor(){yn(this,"_entries",new Map);yn(this,"_json")}async load(){const u=document.getElementById("playwrightReportBase64").textContent,c=new Ff.ZipReader(new Ff.Data64URIReader(u),{useWebWorkers:!1});for(const f of await c.getEntries())this._entries.set(f.filename,f);this._json=await this.entry("report.json")}json(){return this._json}async entry(u){const c=this._entries.get(u),f=new Ff.TextWriter;return await c.getData(f),JSON.parse(await f.getData())}} 77 + </script> 78 + <style type='text/css'>:root{--color-canvas-default-transparent: rgba(255,255,255,0);--color-marketing-icon-primary: #218bff;--color-marketing-icon-secondary: #54aeff;--color-diff-blob-addition-num-text: #24292f;--color-diff-blob-addition-fg: #24292f;--color-diff-blob-addition-num-bg: #CCFFD8;--color-diff-blob-addition-line-bg: #E6FFEC;--color-diff-blob-addition-word-bg: #ABF2BC;--color-diff-blob-deletion-num-text: #24292f;--color-diff-blob-deletion-fg: #24292f;--color-diff-blob-deletion-num-bg: #FFD7D5;--color-diff-blob-deletion-line-bg: #FFEBE9;--color-diff-blob-deletion-word-bg: rgba(255,129,130,.4);--color-diff-blob-hunk-num-bg: rgba(84,174,255,.4);--color-diff-blob-expander-icon: #57606a;--color-diff-blob-selected-line-highlight-mix-blend-mode: multiply;--color-diffstat-deletion-border: rgba(27,31,36,.15);--color-diffstat-addition-border: rgba(27,31,36,.15);--color-diffstat-addition-bg: #2da44e;--color-search-keyword-hl: #fff8c5;--color-prettylights-syntax-comment: #6e7781;--color-prettylights-syntax-constant: #0550ae;--color-prettylights-syntax-entity: #8250df;--color-prettylights-syntax-storage-modifier-import: #24292f;--color-prettylights-syntax-entity-tag: #116329;--color-prettylights-syntax-keyword: #cf222e;--color-prettylights-syntax-string: #0a3069;--color-prettylights-syntax-variable: #953800;--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;--color-prettylights-syntax-invalid-illegal-bg: #82071e;--color-prettylights-syntax-carriage-return-text: #f6f8fa;--color-prettylights-syntax-carriage-return-bg: #cf222e;--color-prettylights-syntax-string-regexp: #116329;--color-prettylights-syntax-markup-list: #3b2300;--color-prettylights-syntax-markup-heading: #0550ae;--color-prettylights-syntax-markup-italic: #24292f;--color-prettylights-syntax-markup-bold: #24292f;--color-prettylights-syntax-markup-deleted-text: #82071e;--color-prettylights-syntax-markup-deleted-bg: #FFEBE9;--color-prettylights-syntax-markup-inserted-text: #116329;--color-prettylights-syntax-markup-inserted-bg: #dafbe1;--color-prettylights-syntax-markup-changed-text: #953800;--color-prettylights-syntax-markup-changed-bg: #ffd8b5;--color-prettylights-syntax-markup-ignored-text: #eaeef2;--color-prettylights-syntax-markup-ignored-bg: #0550ae;--color-prettylights-syntax-meta-diff-range: #8250df;--color-prettylights-syntax-brackethighlighter-angle: #57606a;--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;--color-prettylights-syntax-constant-other-reference-link: #0a3069;--color-codemirror-text: #24292f;--color-codemirror-bg: #ffffff;--color-codemirror-gutters-bg: #ffffff;--color-codemirror-guttermarker-text: #ffffff;--color-codemirror-guttermarker-subtle-text: #6e7781;--color-codemirror-linenumber-text: #57606a;--color-codemirror-cursor: #24292f;--color-codemirror-selection-bg: rgba(84,174,255,.4);--color-codemirror-activeline-bg: rgba(234,238,242,.5);--color-codemirror-matchingbracket-text: #24292f;--color-codemirror-lines-bg: #ffffff;--color-codemirror-syntax-comment: #24292f;--color-codemirror-syntax-constant: #0550ae;--color-codemirror-syntax-entity: #8250df;--color-codemirror-syntax-keyword: #cf222e;--color-codemirror-syntax-storage: #cf222e;--color-codemirror-syntax-string: #0a3069;--color-codemirror-syntax-support: #0550ae;--color-codemirror-syntax-variable: #953800;--color-checks-bg: #24292f;--color-checks-run-border-width: 0px;--color-checks-container-border-width: 0px;--color-checks-text-primary: #f6f8fa;--color-checks-text-secondary: #8c959f;--color-checks-text-link: #54aeff;--color-checks-btn-icon: #afb8c1;--color-checks-btn-hover-icon: #f6f8fa;--color-checks-btn-hover-bg: rgba(255,255,255,.125);--color-checks-input-text: #eaeef2;--color-checks-input-placeholder-text: #8c959f;--color-checks-input-focus-text: #8c959f;--color-checks-input-bg: #32383f;--color-checks-input-shadow: none;--color-checks-donut-error: #fa4549;--color-checks-donut-pending: #bf8700;--color-checks-donut-success: #2da44e;--color-checks-donut-neutral: #afb8c1;--color-checks-dropdown-text: #afb8c1;--color-checks-dropdown-bg: #32383f;--color-checks-dropdown-border: #424a53;--color-checks-dropdown-shadow: rgba(27,31,36,.3);--color-checks-dropdown-hover-text: #f6f8fa;--color-checks-dropdown-hover-bg: #424a53;--color-checks-dropdown-btn-hover-text: #f6f8fa;--color-checks-dropdown-btn-hover-bg: #32383f;--color-checks-scrollbar-thumb-bg: #57606a;--color-checks-header-label-text: #d0d7de;--color-checks-header-label-open-text: #f6f8fa;--color-checks-header-border: #32383f;--color-checks-header-icon: #8c959f;--color-checks-line-text: #d0d7de;--color-checks-line-num-text: rgba(140,149,159,.75);--color-checks-line-timestamp-text: #8c959f;--color-checks-line-hover-bg: #32383f;--color-checks-line-selected-bg: rgba(33,139,255,.15);--color-checks-line-selected-num-text: #54aeff;--color-checks-line-dt-fm-text: #24292f;--color-checks-line-dt-fm-bg: #9a6700;--color-checks-gate-bg: rgba(125,78,0,.15);--color-checks-gate-text: #d0d7de;--color-checks-gate-waiting-text: #afb8c1;--color-checks-step-header-open-bg: #32383f;--color-checks-step-error-text: #ff8182;--color-checks-step-warning-text: #d4a72c;--color-checks-logline-text: #8c959f;--color-checks-logline-num-text: rgba(140,149,159,.75);--color-checks-logline-debug-text: #c297ff;--color-checks-logline-error-text: #d0d7de;--color-checks-logline-error-num-text: #ff8182;--color-checks-logline-error-bg: rgba(164,14,38,.15);--color-checks-logline-warning-text: #d0d7de;--color-checks-logline-warning-num-text: #d4a72c;--color-checks-logline-warning-bg: rgba(125,78,0,.15);--color-checks-logline-command-text: #54aeff;--color-checks-logline-section-text: #4ac26b;--color-checks-ansi-black: #24292f;--color-checks-ansi-black-bright: #32383f;--color-checks-ansi-white: #d0d7de;--color-checks-ansi-white-bright: #d0d7de;--color-checks-ansi-gray: #8c959f;--color-checks-ansi-red: #ff8182;--color-checks-ansi-red-bright: #ffaba8;--color-checks-ansi-green: #4ac26b;--color-checks-ansi-green-bright: #6fdd8b;--color-checks-ansi-yellow: #d4a72c;--color-checks-ansi-yellow-bright: #eac54f;--color-checks-ansi-blue: #54aeff;--color-checks-ansi-blue-bright: #80ccff;--color-checks-ansi-magenta: #c297ff;--color-checks-ansi-magenta-bright: #d8b9ff;--color-checks-ansi-cyan: #76e3ea;--color-checks-ansi-cyan-bright: #b3f0ff;--color-project-header-bg: #24292f;--color-project-sidebar-bg: #ffffff;--color-project-gradient-in: #ffffff;--color-project-gradient-out: rgba(255,255,255,0);--color-mktg-success: rgba(36,146,67,1);--color-mktg-info: rgba(19,119,234,1);--color-mktg-bg-shade-gradient-top: rgba(27,31,36,.065);--color-mktg-bg-shade-gradient-bottom: rgba(27,31,36,0);--color-mktg-btn-bg-top: hsla(228,82%,66%,1);--color-mktg-btn-bg-bottom: #4969ed;--color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);--color-mktg-btn-bg-overlay-bottom: #3355e0;--color-mktg-btn-text: #ffffff;--color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);--color-mktg-btn-primary-bg-bottom: #2ea44f;--color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);--color-mktg-btn-primary-bg-overlay-bottom: #22863a;--color-mktg-btn-primary-text: #ffffff;--color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);--color-mktg-btn-enterprise-bg-bottom: #6f57ff;--color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);--color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;--color-mktg-btn-enterprise-text: #ffffff;--color-mktg-btn-outline-text: #4969ed;--color-mktg-btn-outline-border: rgba(73,105,237,.3);--color-mktg-btn-outline-hover-text: #3355e0;--color-mktg-btn-outline-hover-border: rgba(51,85,224,.5);--color-mktg-btn-outline-focus-border: #4969ed;--color-mktg-btn-outline-focus-border-inset: rgba(73,105,237,.5);--color-mktg-btn-dark-text: #ffffff;--color-mktg-btn-dark-border: rgba(255,255,255,.3);--color-mktg-btn-dark-hover-text: #ffffff;--color-mktg-btn-dark-hover-border: rgba(255,255,255,.5);--color-mktg-btn-dark-focus-border: #ffffff;--color-mktg-btn-dark-focus-border-inset: rgba(255,255,255,.5);--color-avatar-bg: #ffffff;--color-avatar-border: rgba(27,31,36,.15);--color-avatar-stack-fade: #afb8c1;--color-avatar-stack-fade-more: #d0d7de;--color-avatar-child-shadow: -2px -2px 0 rgba(255,255,255,.8);--color-topic-tag-border: rgba(0,0,0,0);--color-select-menu-backdrop-border: rgba(0,0,0,0);--color-select-menu-tap-highlight: rgba(175,184,193,.5);--color-select-menu-tap-focus-bg: #b6e3ff;--color-overlay-shadow: 0 1px 3px rgba(27,31,36,.12), 0 8px 24px rgba(66,74,83,.12);--color-header-text: rgba(255,255,255,.7);--color-header-bg: #24292f;--color-header-logo: #ffffff;--color-header-search-bg: #24292f;--color-header-search-border: #57606a;--color-sidenav-selected-bg: #ffffff;--color-menu-bg-active: rgba(0,0,0,0);--color-control-transparent-bg-hover: #818b981a;--color-input-disabled-bg: rgba(175,184,193,.2);--color-timeline-badge-bg: #eaeef2;--color-ansi-black: #24292f;--color-ansi-black-bright: #57606a;--color-ansi-white: #6e7781;--color-ansi-white-bright: #8c959f;--color-ansi-gray: #6e7781;--color-ansi-red: #cf222e;--color-ansi-red-bright: #a40e26;--color-ansi-green: #116329;--color-ansi-green-bright: #1a7f37;--color-ansi-yellow: #4d2d00;--color-ansi-yellow-bright: #633c01;--color-ansi-blue: #0969da;--color-ansi-blue-bright: #218bff;--color-ansi-magenta: #8250df;--color-ansi-magenta-bright: #a475f9;--color-ansi-cyan: #1b7c83;--color-ansi-cyan-bright: #3192aa;--color-btn-text: #24292f;--color-btn-bg: #f6f8fa;--color-btn-border: rgba(27,31,36,.15);--color-btn-shadow: 0 1px 0 rgba(27,31,36,.04);--color-btn-inset-shadow: inset 0 1px 0 rgba(255,255,255,.25);--color-btn-hover-bg: #f3f4f6;--color-btn-hover-border: rgba(27,31,36,.15);--color-btn-active-bg: hsla(220,14%,93%,1);--color-btn-active-border: rgba(27,31,36,.15);--color-btn-selected-bg: hsla(220,14%,94%,1);--color-btn-focus-bg: #f6f8fa;--color-btn-focus-border: rgba(27,31,36,.15);--color-btn-focus-shadow: 0 0 0 3px rgba(9,105,218,.3);--color-btn-shadow-active: inset 0 .15em .3em rgba(27,31,36,.15);--color-btn-shadow-input-focus: 0 0 0 .2em rgba(9,105,218,.3);--color-btn-counter-bg: rgba(27,31,36,.08);--color-btn-primary-text: #ffffff;--color-btn-primary-bg: #2da44e;--color-btn-primary-border: rgba(27,31,36,.15);--color-btn-primary-shadow: 0 1px 0 rgba(27,31,36,.1);--color-btn-primary-inset-shadow: inset 0 1px 0 rgba(255,255,255,.03);--color-btn-primary-hover-bg: #2c974b;--color-btn-primary-hover-border: rgba(27,31,36,.15);--color-btn-primary-selected-bg: hsla(137,55%,36%,1);--color-btn-primary-selected-shadow: inset 0 1px 0 rgba(0,45,17,.2);--color-btn-primary-disabled-text: rgba(255,255,255,.8);--color-btn-primary-disabled-bg: #94d3a2;--color-btn-primary-disabled-border: rgba(27,31,36,.15);--color-btn-primary-focus-bg: #2da44e;--color-btn-primary-focus-border: rgba(27,31,36,.15);--color-btn-primary-focus-shadow: 0 0 0 3px rgba(45,164,78,.4);--color-btn-primary-icon: rgba(255,255,255,.8);--color-btn-primary-counter-bg: rgba(255,255,255,.2);--color-btn-outline-text: #0969da;--color-btn-outline-hover-text: #ffffff;--color-btn-outline-hover-bg: #0969da;--color-btn-outline-hover-border: rgba(27,31,36,.15);--color-btn-outline-hover-shadow: 0 1px 0 rgba(27,31,36,.1);--color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,.03);--color-btn-outline-hover-counter-bg: rgba(255,255,255,.2);--color-btn-outline-selected-text: #ffffff;--color-btn-outline-selected-bg: hsla(212,92%,42%,1);--color-btn-outline-selected-border: rgba(27,31,36,.15);--color-btn-outline-selected-shadow: inset 0 1px 0 rgba(0,33,85,.2);--color-btn-outline-disabled-text: rgba(9,105,218,.5);--color-btn-outline-disabled-bg: #f6f8fa;--color-btn-outline-disabled-counter-bg: rgba(9,105,218,.05);--color-btn-outline-focus-border: rgba(27,31,36,.15);--color-btn-outline-focus-shadow: 0 0 0 3px rgba(5,80,174,.4);--color-btn-outline-counter-bg: rgba(9,105,218,.1);--color-btn-danger-text: #cf222e;--color-btn-danger-hover-text: #ffffff;--color-btn-danger-hover-bg: #a40e26;--color-btn-danger-hover-border: rgba(27,31,36,.15);--color-btn-danger-hover-shadow: 0 1px 0 rgba(27,31,36,.1);--color-btn-danger-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,.03);--color-btn-danger-hover-counter-bg: rgba(255,255,255,.2);--color-btn-danger-selected-text: #ffffff;--color-btn-danger-selected-bg: hsla(356,72%,44%,1);--color-btn-danger-selected-border: rgba(27,31,36,.15);--color-btn-danger-selected-shadow: inset 0 1px 0 rgba(76,0,20,.2);--color-btn-danger-disabled-text: rgba(207,34,46,.5);--color-btn-danger-disabled-bg: #f6f8fa;--color-btn-danger-disabled-counter-bg: rgba(207,34,46,.05);--color-btn-danger-focus-border: rgba(27,31,36,.15);--color-btn-danger-focus-shadow: 0 0 0 3px rgba(164,14,38,.4);--color-btn-danger-counter-bg: rgba(207,34,46,.1);--color-btn-danger-icon: #cf222e;--color-btn-danger-hover-icon: #ffffff;--color-underlinenav-icon: #6e7781;--color-underlinenav-border-hover: rgba(175,184,193,.2);--color-fg-default: #24292f;--color-fg-muted: #57606a;--color-fg-subtle: #6e7781;--color-fg-on-emphasis: #ffffff;--color-canvas-default: #ffffff;--color-canvas-overlay: #ffffff;--color-canvas-inset: #f6f8fa;--color-canvas-subtle: #f6f8fa;--color-border-default: #d0d7de;--color-border-muted: hsla(210,18%,87%,1);--color-border-subtle: rgba(27,31,36,.15);--color-shadow-small: 0 1px 0 rgba(27,31,36,.04);--color-shadow-medium: 0 3px 6px rgba(140,149,159,.15);--color-shadow-large: 0 8px 24px rgba(140,149,159,.2);--color-shadow-extra-large: 0 12px 28px rgba(140,149,159,.3);--color-neutral-emphasis-plus: #24292f;--color-neutral-emphasis: #6e7781;--color-neutral-muted: rgba(175,184,193,.2);--color-neutral-subtle: rgba(234,238,242,.5);--color-accent-fg: #0969da;--color-accent-emphasis: #0969da;--color-accent-muted: rgba(84,174,255,.4);--color-accent-subtle: #ddf4ff;--color-success-fg: #1a7f37;--color-success-emphasis: #2da44e;--color-success-muted: rgba(74,194,107,.4);--color-success-subtle: #dafbe1;--color-attention-fg: #9a6700;--color-attention-emphasis: #bf8700;--color-attention-muted: rgba(212,167,44,.4);--color-attention-subtle: #fff8c5;--color-severe-fg: #bc4c00;--color-severe-emphasis: #bc4c00;--color-severe-muted: rgba(251,143,68,.4);--color-severe-subtle: #fff1e5;--color-danger-fg: #cf222e;--color-danger-emphasis: #cf222e;--color-danger-muted: rgba(255,129,130,.4);--color-danger-subtle: #FFEBE9;--color-done-fg: #8250df;--color-done-emphasis: #8250df;--color-done-muted: rgba(194,151,255,.4);--color-done-subtle: #fbefff;--color-sponsors-fg: #bf3989;--color-sponsors-emphasis: #bf3989;--color-sponsors-muted: rgba(255,128,200,.4);--color-sponsors-subtle: #ffeff7;--color-primer-canvas-backdrop: rgba(27,31,36,.5);--color-primer-canvas-sticky: rgba(255,255,255,.95);--color-primer-border-active: #FD8C73;--color-primer-border-contrast: rgba(27,31,36,.1);--color-primer-shadow-highlight: inset 0 1px 0 rgba(255,255,255,.25);--color-primer-shadow-inset: inset 0 1px 0 rgba(208,215,222,.2);--color-primer-shadow-focus: 0 0 0 3px rgba(9,105,218,.3);--color-scale-black: #1b1f24;--color-scale-white: #ffffff;--color-scale-gray-0: #f6f8fa;--color-scale-gray-1: #eaeef2;--color-scale-gray-2: #d0d7de;--color-scale-gray-3: #afb8c1;--color-scale-gray-4: #8c959f;--color-scale-gray-5: #6e7781;--color-scale-gray-6: #57606a;--color-scale-gray-7: #424a53;--color-scale-gray-8: #32383f;--color-scale-gray-9: #24292f;--color-scale-blue-0: #ddf4ff;--color-scale-blue-1: #b6e3ff;--color-scale-blue-2: #80ccff;--color-scale-blue-3: #54aeff;--color-scale-blue-4: #218bff;--color-scale-blue-5: #0969da;--color-scale-blue-6: #0550ae;--color-scale-blue-7: #033d8b;--color-scale-blue-8: #0a3069;--color-scale-blue-9: #002155;--color-scale-green-0: #dafbe1;--color-scale-green-1: #aceebb;--color-scale-green-2: #6fdd8b;--color-scale-green-3: #4ac26b;--color-scale-green-4: #2da44e;--color-scale-green-5: #1a7f37;--color-scale-green-6: #116329;--color-scale-green-7: #044f1e;--color-scale-green-8: #003d16;--color-scale-green-9: #002d11;--color-scale-yellow-0: #fff8c5;--color-scale-yellow-1: #fae17d;--color-scale-yellow-2: #eac54f;--color-scale-yellow-3: #d4a72c;--color-scale-yellow-4: #bf8700;--color-scale-yellow-5: #9a6700;--color-scale-yellow-6: #7d4e00;--color-scale-yellow-7: #633c01;--color-scale-yellow-8: #4d2d00;--color-scale-yellow-9: #3b2300;--color-scale-orange-0: #fff1e5;--color-scale-orange-1: #ffd8b5;--color-scale-orange-2: #ffb77c;--color-scale-orange-3: #fb8f44;--color-scale-orange-4: #e16f24;--color-scale-orange-5: #bc4c00;--color-scale-orange-6: #953800;--color-scale-orange-7: #762c00;--color-scale-orange-8: #5c2200;--color-scale-orange-9: #471700;--color-scale-red-0: #FFEBE9;--color-scale-red-1: #ffcecb;--color-scale-red-2: #ffaba8;--color-scale-red-3: #ff8182;--color-scale-red-4: #fa4549;--color-scale-red-5: #cf222e;--color-scale-red-6: #a40e26;--color-scale-red-7: #82071e;--color-scale-red-8: #660018;--color-scale-red-9: #4c0014;--color-scale-purple-0: #fbefff;--color-scale-purple-1: #ecd8ff;--color-scale-purple-2: #d8b9ff;--color-scale-purple-3: #c297ff;--color-scale-purple-4: #a475f9;--color-scale-purple-5: #8250df;--color-scale-purple-6: #6639ba;--color-scale-purple-7: #512a97;--color-scale-purple-8: #3e1f79;--color-scale-purple-9: #2e1461;--color-scale-pink-0: #ffeff7;--color-scale-pink-1: #ffd3eb;--color-scale-pink-2: #ffadda;--color-scale-pink-3: #ff80c8;--color-scale-pink-4: #e85aad;--color-scale-pink-5: #bf3989;--color-scale-pink-6: #99286e;--color-scale-pink-7: #772057;--color-scale-pink-8: #611347;--color-scale-pink-9: #4d0336;--color-scale-coral-0: #FFF0EB;--color-scale-coral-1: #FFD6CC;--color-scale-coral-2: #FFB4A1;--color-scale-coral-3: #FD8C73;--color-scale-coral-4: #EC6547;--color-scale-coral-5: #C4432B;--color-scale-coral-6: #9E2F1C;--color-scale-coral-7: #801F0F;--color-scale-coral-8: #691105;--color-scale-coral-9: #510901 }:root.dark-mode{color-scheme:dark;--color-canvas-default-transparent: rgba(13,17,23,0);--color-marketing-icon-primary: #79c0ff;--color-marketing-icon-secondary: #1f6feb;--color-diff-blob-addition-num-text: #c9d1d9;--color-diff-blob-addition-fg: #c9d1d9;--color-diff-blob-addition-num-bg: rgba(63,185,80,.3);--color-diff-blob-addition-line-bg: rgba(46,160,67,.15);--color-diff-blob-addition-word-bg: rgba(46,160,67,.4);--color-diff-blob-deletion-num-text: #c9d1d9;--color-diff-blob-deletion-fg: #c9d1d9;--color-diff-blob-deletion-num-bg: rgba(248,81,73,.3);--color-diff-blob-deletion-line-bg: rgba(248,81,73,.15);--color-diff-blob-deletion-word-bg: rgba(248,81,73,.4);--color-diff-blob-hunk-num-bg: rgba(56,139,253,.4);--color-diff-blob-expander-icon: #8b949e;--color-diff-blob-selected-line-highlight-mix-blend-mode: screen;--color-diffstat-deletion-border: rgba(240,246,252,.1);--color-diffstat-addition-border: rgba(240,246,252,.1);--color-diffstat-addition-bg: #3fb950;--color-search-keyword-hl: rgba(210,153,34,.4);--color-prettylights-syntax-comment: #8b949e;--color-prettylights-syntax-constant: #79c0ff;--color-prettylights-syntax-entity: #d2a8ff;--color-prettylights-syntax-storage-modifier-import: #c9d1d9;--color-prettylights-syntax-entity-tag: #7ee787;--color-prettylights-syntax-keyword: #ff7b72;--color-prettylights-syntax-string: #a5d6ff;--color-prettylights-syntax-variable: #ffa657;--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;--color-prettylights-syntax-invalid-illegal-bg: #8e1519;--color-prettylights-syntax-carriage-return-text: #f0f6fc;--color-prettylights-syntax-carriage-return-bg: #b62324;--color-prettylights-syntax-string-regexp: #7ee787;--color-prettylights-syntax-markup-list: #f2cc60;--color-prettylights-syntax-markup-heading: #1f6feb;--color-prettylights-syntax-markup-italic: #c9d1d9;--color-prettylights-syntax-markup-bold: #c9d1d9;--color-prettylights-syntax-markup-deleted-text: #ffdcd7;--color-prettylights-syntax-markup-deleted-bg: #67060c;--color-prettylights-syntax-markup-inserted-text: #aff5b4;--color-prettylights-syntax-markup-inserted-bg: #033a16;--color-prettylights-syntax-markup-changed-text: #ffdfb6;--color-prettylights-syntax-markup-changed-bg: #5a1e02;--color-prettylights-syntax-markup-ignored-text: #c9d1d9;--color-prettylights-syntax-markup-ignored-bg: #1158c7;--color-prettylights-syntax-meta-diff-range: #d2a8ff;--color-prettylights-syntax-brackethighlighter-angle: #8b949e;--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;--color-codemirror-text: #c9d1d9;--color-codemirror-bg: #0d1117;--color-codemirror-gutters-bg: #0d1117;--color-codemirror-guttermarker-text: #0d1117;--color-codemirror-guttermarker-subtle-text: #484f58;--color-codemirror-linenumber-text: #8b949e;--color-codemirror-cursor: #c9d1d9;--color-codemirror-selection-bg: rgba(56,139,253,.4);--color-codemirror-activeline-bg: rgba(110,118,129,.1);--color-codemirror-matchingbracket-text: #c9d1d9;--color-codemirror-lines-bg: #0d1117;--color-codemirror-syntax-comment: #8b949e;--color-codemirror-syntax-constant: #79c0ff;--color-codemirror-syntax-entity: #d2a8ff;--color-codemirror-syntax-keyword: #ff7b72;--color-codemirror-syntax-storage: #ff7b72;--color-codemirror-syntax-string: #a5d6ff;--color-codemirror-syntax-support: #79c0ff;--color-codemirror-syntax-variable: #ffa657;--color-checks-bg: #010409;--color-checks-run-border-width: 1px;--color-checks-container-border-width: 1px;--color-checks-text-primary: #c9d1d9;--color-checks-text-secondary: #8b949e;--color-checks-text-link: #58a6ff;--color-checks-btn-icon: #8b949e;--color-checks-btn-hover-icon: #c9d1d9;--color-checks-btn-hover-bg: rgba(110,118,129,.1);--color-checks-input-text: #8b949e;--color-checks-input-placeholder-text: #484f58;--color-checks-input-focus-text: #c9d1d9;--color-checks-input-bg: #161b22;--color-checks-input-shadow: none;--color-checks-donut-error: #f85149;--color-checks-donut-pending: #d29922;--color-checks-donut-success: #2ea043;--color-checks-donut-neutral: #8b949e;--color-checks-dropdown-text: #c9d1d9;--color-checks-dropdown-bg: #161b22;--color-checks-dropdown-border: #30363d;--color-checks-dropdown-shadow: rgba(1,4,9,.3);--color-checks-dropdown-hover-text: #c9d1d9;--color-checks-dropdown-hover-bg: rgba(110,118,129,.1);--color-checks-dropdown-btn-hover-text: #c9d1d9;--color-checks-dropdown-btn-hover-bg: rgba(110,118,129,.1);--color-checks-scrollbar-thumb-bg: rgba(110,118,129,.4);--color-checks-header-label-text: #8b949e;--color-checks-header-label-open-text: #c9d1d9;--color-checks-header-border: #21262d;--color-checks-header-icon: #8b949e;--color-checks-line-text: #8b949e;--color-checks-line-num-text: #484f58;--color-checks-line-timestamp-text: #484f58;--color-checks-line-hover-bg: rgba(110,118,129,.1);--color-checks-line-selected-bg: rgba(56,139,253,.15);--color-checks-line-selected-num-text: #58a6ff;--color-checks-line-dt-fm-text: #f0f6fc;--color-checks-line-dt-fm-bg: #9e6a03;--color-checks-gate-bg: rgba(187,128,9,.15);--color-checks-gate-text: #8b949e;--color-checks-gate-waiting-text: #d29922;--color-checks-step-header-open-bg: #161b22;--color-checks-step-error-text: #f85149;--color-checks-step-warning-text: #d29922;--color-checks-logline-text: #8b949e;--color-checks-logline-num-text: #484f58;--color-checks-logline-debug-text: #a371f7;--color-checks-logline-error-text: #8b949e;--color-checks-logline-error-num-text: #484f58;--color-checks-logline-error-bg: rgba(248,81,73,.15);--color-checks-logline-warning-text: #8b949e;--color-checks-logline-warning-num-text: #d29922;--color-checks-logline-warning-bg: rgba(187,128,9,.15);--color-checks-logline-command-text: #58a6ff;--color-checks-logline-section-text: #3fb950;--color-checks-ansi-black: #0d1117;--color-checks-ansi-black-bright: #161b22;--color-checks-ansi-white: #b1bac4;--color-checks-ansi-white-bright: #b1bac4;--color-checks-ansi-gray: #6e7681;--color-checks-ansi-red: #ff7b72;--color-checks-ansi-red-bright: #ffa198;--color-checks-ansi-green: #3fb950;--color-checks-ansi-green-bright: #56d364;--color-checks-ansi-yellow: #d29922;--color-checks-ansi-yellow-bright: #e3b341;--color-checks-ansi-blue: #58a6ff;--color-checks-ansi-blue-bright: #79c0ff;--color-checks-ansi-magenta: #bc8cff;--color-checks-ansi-magenta-bright: #d2a8ff;--color-checks-ansi-cyan: #76e3ea;--color-checks-ansi-cyan-bright: #b3f0ff;--color-project-header-bg: #0d1117;--color-project-sidebar-bg: #161b22;--color-project-gradient-in: #161b22;--color-project-gradient-out: rgba(22,27,34,0);--color-mktg-success: rgba(41,147,61,1);--color-mktg-info: rgba(42,123,243,1);--color-mktg-bg-shade-gradient-top: rgba(1,4,9,.065);--color-mktg-bg-shade-gradient-bottom: rgba(1,4,9,0);--color-mktg-btn-bg-top: hsla(228,82%,66%,1);--color-mktg-btn-bg-bottom: #4969ed;--color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);--color-mktg-btn-bg-overlay-bottom: #3355e0;--color-mktg-btn-text: #f0f6fc;--color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);--color-mktg-btn-primary-bg-bottom: #2ea44f;--color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);--color-mktg-btn-primary-bg-overlay-bottom: #22863a;--color-mktg-btn-primary-text: #f0f6fc;--color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);--color-mktg-btn-enterprise-bg-bottom: #6f57ff;--color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);--color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;--color-mktg-btn-enterprise-text: #f0f6fc;--color-mktg-btn-outline-text: #f0f6fc;--color-mktg-btn-outline-border: rgba(240,246,252,.3);--color-mktg-btn-outline-hover-text: #f0f6fc;--color-mktg-btn-outline-hover-border: rgba(240,246,252,.5);--color-mktg-btn-outline-focus-border: #f0f6fc;--color-mktg-btn-outline-focus-border-inset: rgba(240,246,252,.5);--color-mktg-btn-dark-text: #f0f6fc;--color-mktg-btn-dark-border: rgba(240,246,252,.3);--color-mktg-btn-dark-hover-text: #f0f6fc;--color-mktg-btn-dark-hover-border: rgba(240,246,252,.5);--color-mktg-btn-dark-focus-border: #f0f6fc;--color-mktg-btn-dark-focus-border-inset: rgba(240,246,252,.5);--color-avatar-bg: rgba(240,246,252,.1);--color-avatar-border: rgba(240,246,252,.1);--color-avatar-stack-fade: #30363d;--color-avatar-stack-fade-more: #21262d;--color-avatar-child-shadow: -2px -2px 0 #0d1117;--color-topic-tag-border: rgba(0,0,0,0);--color-select-menu-backdrop-border: #484f58;--color-select-menu-tap-highlight: rgba(48,54,61,.5);--color-select-menu-tap-focus-bg: #0c2d6b;--color-overlay-shadow: 0 0 0 1px #30363d, 0 16px 32px rgba(1,4,9,.85);--color-header-text: rgba(240,246,252,.7);--color-header-bg: #161b22;--color-header-logo: #f0f6fc;--color-header-search-bg: #0d1117;--color-header-search-border: #30363d;--color-sidenav-selected-bg: #21262d;--color-menu-bg-active: #161b22;--color-control-transparent-bg-hover: #656c7633;--color-input-disabled-bg: rgba(110,118,129,0);--color-timeline-badge-bg: #21262d;--color-ansi-black: #484f58;--color-ansi-black-bright: #6e7681;--color-ansi-white: #b1bac4;--color-ansi-white-bright: #f0f6fc;--color-ansi-gray: #6e7681;--color-ansi-red: #ff7b72;--color-ansi-red-bright: #ffa198;--color-ansi-green: #3fb950;--color-ansi-green-bright: #56d364;--color-ansi-yellow: #d29922;--color-ansi-yellow-bright: #e3b341;--color-ansi-blue: #58a6ff;--color-ansi-blue-bright: #79c0ff;--color-ansi-magenta: #bc8cff;--color-ansi-magenta-bright: #d2a8ff;--color-ansi-cyan: #39c5cf;--color-ansi-cyan-bright: #56d4dd;--color-btn-text: #c9d1d9;--color-btn-bg: #21262d;--color-btn-border: rgba(240,246,252,.1);--color-btn-shadow: 0 0 transparent;--color-btn-inset-shadow: 0 0 transparent;--color-btn-hover-bg: #30363d;--color-btn-hover-border: #8b949e;--color-btn-active-bg: hsla(212,12%,18%,1);--color-btn-active-border: #6e7681;--color-btn-selected-bg: #161b22;--color-btn-focus-bg: #21262d;--color-btn-focus-border: #8b949e;--color-btn-focus-shadow: 0 0 0 3px rgba(139,148,158,.3);--color-btn-shadow-active: inset 0 .15em .3em rgba(1,4,9,.15);--color-btn-shadow-input-focus: 0 0 0 .2em rgba(31,111,235,.3);--color-btn-counter-bg: #30363d;--color-btn-primary-text: #ffffff;--color-btn-primary-bg: #238636;--color-btn-primary-border: rgba(240,246,252,.1);--color-btn-primary-shadow: 0 0 transparent;--color-btn-primary-inset-shadow: 0 0 transparent;--color-btn-primary-hover-bg: #2ea043;--color-btn-primary-hover-border: rgba(240,246,252,.1);--color-btn-primary-selected-bg: #238636;--color-btn-primary-selected-shadow: 0 0 transparent;--color-btn-primary-disabled-text: rgba(240,246,252,.5);--color-btn-primary-disabled-bg: rgba(35,134,54,.6);--color-btn-primary-disabled-border: rgba(240,246,252,.1);--color-btn-primary-focus-bg: #238636;--color-btn-primary-focus-border: rgba(240,246,252,.1);--color-btn-primary-focus-shadow: 0 0 0 3px rgba(46,164,79,.4);--color-btn-primary-icon: #f0f6fc;--color-btn-primary-counter-bg: rgba(240,246,252,.2);--color-btn-outline-text: #58a6ff;--color-btn-outline-hover-text: #58a6ff;--color-btn-outline-hover-bg: #30363d;--color-btn-outline-hover-border: rgba(240,246,252,.1);--color-btn-outline-hover-shadow: 0 1px 0 rgba(1,4,9,.1);--color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(240,246,252,.03);--color-btn-outline-hover-counter-bg: rgba(240,246,252,.2);--color-btn-outline-selected-text: #f0f6fc;--color-btn-outline-selected-bg: #0d419d;--color-btn-outline-selected-border: rgba(240,246,252,.1);--color-btn-outline-selected-shadow: 0 0 transparent;--color-btn-outline-disabled-text: rgba(88,166,255,.5);--color-btn-outline-disabled-bg: #0d1117;--color-btn-outline-disabled-counter-bg: rgba(31,111,235,.05);--color-btn-outline-focus-border: rgba(240,246,252,.1);--color-btn-outline-focus-shadow: 0 0 0 3px rgba(17,88,199,.4);--color-btn-outline-counter-bg: rgba(31,111,235,.1);--color-btn-danger-text: #f85149;--color-btn-danger-hover-text: #f0f6fc;--color-btn-danger-hover-bg: #da3633;--color-btn-danger-hover-border: #f85149;--color-btn-danger-hover-shadow: 0 0 transparent;--color-btn-danger-hover-inset-shadow: 0 0 transparent;--color-btn-danger-hover-icon: #f0f6fc;--color-btn-danger-hover-counter-bg: rgba(255,255,255,.2);--color-btn-danger-selected-text: #ffffff;--color-btn-danger-selected-bg: #b62324;--color-btn-danger-selected-border: #ff7b72;--color-btn-danger-selected-shadow: 0 0 transparent;--color-btn-danger-disabled-text: rgba(248,81,73,.5);--color-btn-danger-disabled-bg: #0d1117;--color-btn-danger-disabled-counter-bg: rgba(218,54,51,.05);--color-btn-danger-focus-border: #f85149;--color-btn-danger-focus-shadow: 0 0 0 3px rgba(248,81,73,.4);--color-btn-danger-counter-bg: rgba(218,54,51,.1);--color-btn-danger-icon: #f85149;--color-underlinenav-icon: #484f58;--color-underlinenav-border-hover: rgba(110,118,129,.4);--color-fg-default: #c9d1d9;--color-fg-muted: #8b949e;--color-fg-subtle: #484f58;--color-fg-on-emphasis: #f0f6fc;--color-canvas-default: #0d1117;--color-canvas-overlay: #161b22;--color-canvas-inset: #010409;--color-canvas-subtle: #161b22;--color-border-default: #30363d;--color-border-muted: #21262d;--color-border-subtle: rgba(240,246,252,.1);--color-shadow-small: 0 0 transparent;--color-shadow-medium: 0 3px 6px #010409;--color-shadow-large: 0 8px 24px #010409;--color-shadow-extra-large: 0 12px 48px #010409;--color-neutral-emphasis-plus: #6e7681;--color-neutral-emphasis: #6e7681;--color-neutral-muted: rgba(110,118,129,.4);--color-neutral-subtle: rgba(110,118,129,.1);--color-accent-fg: #58a6ff;--color-accent-emphasis: #1f6feb;--color-accent-muted: rgba(56,139,253,.4);--color-accent-subtle: rgba(56,139,253,.15);--color-success-fg: #3fb950;--color-success-emphasis: #238636;--color-success-muted: rgba(46,160,67,.4);--color-success-subtle: rgba(46,160,67,.15);--color-attention-fg: #d29922;--color-attention-emphasis: #9e6a03;--color-attention-muted: rgba(187,128,9,.4);--color-attention-subtle: rgba(187,128,9,.15);--color-severe-fg: #db6d28;--color-severe-emphasis: #bd561d;--color-severe-muted: rgba(219,109,40,.4);--color-severe-subtle: rgba(219,109,40,.15);--color-danger-fg: #f85149;--color-danger-emphasis: #da3633;--color-danger-muted: rgba(248,81,73,.4);--color-danger-subtle: rgba(248,81,73,.15);--color-done-fg: #a371f7;--color-done-emphasis: #8957e5;--color-done-muted: rgba(163,113,247,.4);--color-done-subtle: rgba(163,113,247,.15);--color-sponsors-fg: #db61a2;--color-sponsors-emphasis: #bf4b8a;--color-sponsors-muted: rgba(219,97,162,.4);--color-sponsors-subtle: rgba(219,97,162,.15);--color-primer-canvas-backdrop: rgba(1,4,9,.8);--color-primer-canvas-sticky: rgba(13,17,23,.95);--color-primer-border-active: #F78166;--color-primer-border-contrast: rgba(240,246,252,.2);--color-primer-shadow-highlight: 0 0 transparent;--color-primer-shadow-inset: 0 0 transparent;--color-primer-shadow-focus: 0 0 0 3px #0c2d6b;--color-scale-black: #010409;--color-scale-white: #f0f6fc;--color-scale-gray-0: #f0f6fc;--color-scale-gray-1: #c9d1d9;--color-scale-gray-2: #b1bac4;--color-scale-gray-3: #8b949e;--color-scale-gray-4: #6e7681;--color-scale-gray-5: #484f58;--color-scale-gray-6: #30363d;--color-scale-gray-7: #21262d;--color-scale-gray-8: #161b22;--color-scale-gray-9: #0d1117;--color-scale-blue-0: #cae8ff;--color-scale-blue-1: #a5d6ff;--color-scale-blue-2: #79c0ff;--color-scale-blue-3: #58a6ff;--color-scale-blue-4: #388bfd;--color-scale-blue-5: #1f6feb;--color-scale-blue-6: #1158c7;--color-scale-blue-7: #0d419d;--color-scale-blue-8: #0c2d6b;--color-scale-blue-9: #051d4d;--color-scale-green-0: #aff5b4;--color-scale-green-1: #7ee787;--color-scale-green-2: #56d364;--color-scale-green-3: #3fb950;--color-scale-green-4: #2ea043;--color-scale-green-5: #238636;--color-scale-green-6: #196c2e;--color-scale-green-7: #0f5323;--color-scale-green-8: #033a16;--color-scale-green-9: #04260f;--color-scale-yellow-0: #f8e3a1;--color-scale-yellow-1: #f2cc60;--color-scale-yellow-2: #e3b341;--color-scale-yellow-3: #d29922;--color-scale-yellow-4: #bb8009;--color-scale-yellow-5: #9e6a03;--color-scale-yellow-6: #845306;--color-scale-yellow-7: #693e00;--color-scale-yellow-8: #4b2900;--color-scale-yellow-9: #341a00;--color-scale-orange-0: #ffdfb6;--color-scale-orange-1: #ffc680;--color-scale-orange-2: #ffa657;--color-scale-orange-3: #f0883e;--color-scale-orange-4: #db6d28;--color-scale-orange-5: #bd561d;--color-scale-orange-6: #9b4215;--color-scale-orange-7: #762d0a;--color-scale-orange-8: #5a1e02;--color-scale-orange-9: #3d1300;--color-scale-red-0: #ffdcd7;--color-scale-red-1: #ffc1ba;--color-scale-red-2: #ffa198;--color-scale-red-3: #ff7b72;--color-scale-red-4: #f85149;--color-scale-red-5: #da3633;--color-scale-red-6: #b62324;--color-scale-red-7: #8e1519;--color-scale-red-8: #67060c;--color-scale-red-9: #490202;--color-scale-purple-0: #eddeff;--color-scale-purple-1: #e2c5ff;--color-scale-purple-2: #d2a8ff;--color-scale-purple-3: #bc8cff;--color-scale-purple-4: #a371f7;--color-scale-purple-5: #8957e5;--color-scale-purple-6: #6e40c9;--color-scale-purple-7: #553098;--color-scale-purple-8: #3c1e70;--color-scale-purple-9: #271052;--color-scale-pink-0: #ffdaec;--color-scale-pink-1: #ffbedd;--color-scale-pink-2: #ff9bce;--color-scale-pink-3: #f778ba;--color-scale-pink-4: #db61a2;--color-scale-pink-5: #bf4b8a;--color-scale-pink-6: #9e3670;--color-scale-pink-7: #7d2457;--color-scale-pink-8: #5e103e;--color-scale-pink-9: #42062a;--color-scale-coral-0: #FFDDD2;--color-scale-coral-1: #FFC2B2;--color-scale-coral-2: #FFA28B;--color-scale-coral-3: #F78166;--color-scale-coral-4: #EA6045;--color-scale-coral-5: #CF462D;--color-scale-coral-6: #AC3220;--color-scale-coral-7: #872012;--color-scale-coral-8: #640D04;--color-scale-coral-9: #460701 }:root{--box-shadow: rgba(0, 0, 0, .133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, .11) 0px .3px .9px 0px;--box-shadow-thick: rgb(0 0 0 / 10%) 0px 1.8px 1.9px, rgb(0 0 0 / 15%) 0px 6.1px 6.3px, rgb(0 0 0 / 10%) 0px -2px 4px, rgb(0 0 0 / 15%) 0px -6.1px 12px, rgb(0 0 0 / 25%) 0px 6px 12px}*{box-sizing:border-box;min-width:0;min-height:0}svg{fill:currentColor}.vbox{display:flex;flex-direction:column;flex:auto;position:relative}.hbox{display:flex;flex:auto;position:relative}.hidden{visibility:hidden}.d-flex{display:flex!important}.d-inline{display:inline!important}.m-1{margin:4px}.m-2{margin:8px}.m-3{margin:16px}.m-4{margin:24px}.m-5{margin:32px}.mx-1{margin:0 4px}.mx-2{margin:0 8px}.mx-3{margin:0 16px}.mx-4{margin:0 24px}.mx-5{margin:0 32px}.my-1{margin:4px 0}.my-2{margin:8px 0}.my-3{margin:16px 0}.my-4{margin:24px 0}.my-5{margin:32px 0}.mt-1{margin-top:4px}.mt-2{margin-top:8px}.mt-3{margin-top:16px}.mt-4{margin-top:24px}.mt-5{margin-top:32px}.mr-1{margin-right:4px}.mr-2{margin-right:8px}.mr-3{margin-right:16px}.mr-4{margin-right:24px}.mr-5{margin-right:32px}.mb-1{margin-bottom:4px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:16px}.mb-4{margin-bottom:24px}.mb-5{margin-bottom:32px}.ml-1{margin-left:4px}.ml-2{margin-left:8px}.ml-3{margin-left:16px}.ml-4{margin-left:24px}.ml-5{margin-left:32px}.p-1{padding:4px}.p-2{padding:8px}.p-3{padding:16px}.p-4{padding:24px}.p-5{padding:32px}.px-1{padding:0 4px}.px-2{padding:0 8px}.px-3{padding:0 16px}.px-4{padding:0 24px}.px-5{padding:0 32px}.py-1{padding:4px 0}.py-2{padding:8px 0}.py-3{padding:16px 0}.py-4{padding:24px 0}.py-5{padding:32px 0}.pt-1{padding-top:4px}.pt-2{padding-top:8px}.pt-3{padding-top:16px}.pt-4{padding-top:24px}.pt-5{padding-top:32px}.pr-1{padding-right:4px}.pr-2{padding-right:8px}.pr-3{padding-right:16px}.pr-4{padding-right:24px}.pr-5{padding-right:32px}.pb-1{padding-bottom:4px}.pb-2{padding-bottom:8px}.pb-3{padding-bottom:16px}.pb-4{padding-bottom:24px}.pb-5{padding-bottom:32px}.pl-1{padding-left:4px}.pl-2{padding-left:8px}.pl-3{padding-left:16px}.pl-4{padding-left:24px}.pl-5{padding-left:32px}.no-wrap{white-space:nowrap!important}.float-left{float:left!important}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}.form-control,.form-select{padding:5px 12px;font-size:14px;line-height:20px;color:var(--color-fg-default);vertical-align:middle;background-color:var(--color-canvas-default);background-repeat:no-repeat;background-position:right 8px center;border:1px solid var(--color-border-default);border-radius:6px;outline:none;box-shadow:var(--color-primer-shadow-inset)}.input-contrast{background-color:var(--color-canvas-inset)}.subnav-search{position:relative;flex:auto;display:flex}.subnav-search-input{flex:auto;padding-left:32px;color:var(--color-fg-muted)}.subnav-search-icon{position:absolute;top:9px;left:8px;display:block;color:var(--color-fg-muted);text-align:center;pointer-events:none}.subnav-search-context+.subnav-search{margin-left:-1px}.subnav-item{flex:none;position:relative;float:left;padding:5px 8px;font-weight:500;line-height:20px;color:var(--color-fg-default);border:1px solid var(--color-border-default);-webkit-user-select:none;user-select:none}.subnav-item:hover{background-color:var(--color-canvas-subtle)}.subnav-item[aria-selected=true]{background:var(--color-control-transparent-bg-hover)}.subnav-item:first-child{border-top-left-radius:6px;border-bottom-left-radius:6px}.subnav-item:last-child{border-top-right-radius:6px;border-bottom-right-radius:6px}.subnav-item+.subnav-item{margin-left:-1px}.subnav-item .octicon,.subnav-item-label{margin-right:8px}.counter{display:inline-block;min-width:20px;padding:0 6px;font-size:12px;font-weight:500;line-height:18px;color:var(--color-fg-default);text-align:center;background-color:var(--color-neutral-muted);border:1px solid transparent;border-radius:2em}.color-icon-success{color:var(--color-success-fg)!important}.color-text-danger{color:var(--color-danger-fg)!important}.color-text-warning{color:var(--color-checks-step-warning-text)!important}.color-fg-muted{color:var(--color-fg-muted)!important}.octicon{display:inline-block;overflow:visible!important;vertical-align:text-bottom;fill:currentColor;margin-right:7px;flex:none}.button{flex:none;height:24px;border:1px solid var(--color-btn-border);outline:none;color:var(--color-btn-text);background:var(--color-btn-bg);padding:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.button:not(:disabled):hover{border-color:var(--color-btn-hover-border);background-color:var(--color-btn-hover-bg)}input[type=checkbox]{outline:var(--color-focus-border);height:24px}dialog{background-color:var(--color-canvas-subtle);border:1px solid var(--color-border-default);border-radius:6px;padding:6px}.subnav-item .octicon.octicon-settings{margin-right:0}.subnav-item .octicon.octicon-clock{margin-right:0;color:var(--color-fg-default)!important}@media only screen and (max-width: 600px){.subnav-item,.form-control{border-radius:0!important}.subnav-item{border:none}.subnav-search-input{border-left:0;border-right:0}}.header-view-status-container{float:right}.header-view{padding:12px 8px 0}.header-view div{flex-shrink:0;flex-wrap:wrap}.header-superheader{color:var(--color-fg-muted)}.header-title{flex:none;font-weight:400;font-size:32px;line-height:1.25}.header-setting-theme{display:grid;margin-left:22px}@media only screen and (max-width: 600px){.header-view{padding:0}.header-view div{flex-shrink:1}.header-view-status-container{float:none;margin:0 0 10px!important;overflow:hidden}.header-view-status-container .subnav-search-input{border-left:none;border-right:none}.header-title,.header-superheader{margin:0 8px}}.copy-icon{flex:none;height:24px;width:24px;border:none;outline:none;color:var(--color-fg-muted);background:transparent;padding:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.copy-icon svg{margin:0}.copy-icon:not(:disabled):hover{background-color:var(--color-border-default)}.copy-button-container{visibility:hidden;display:inline-flex;margin-left:8px;vertical-align:bottom}.copy-value-container:hover .copy-button-container{visibility:visible}.attachment-body{white-space:pre-wrap;background-color:var(--color-canvas-subtle);margin-left:24px;line-height:normal;padding:8px;font-family:monospace;position:relative}.attachment-body .copy-icon{position:absolute;right:5px;top:5px}.attachment-flash{animation:attachmentflash-bg 2s}@keyframes attachmentflash-bg{0%{background:var(--color-attention-subtle)}to{background:transparent}}.link-badge{flex:none;background-color:transparent;border-color:transparent;-webkit-user-select:none;user-select:none}.link-badge-dim span{color:var(--color-fg-muted)}.link-badge:hover{cursor:pointer}.link-badge svg{fill:var(--color-fg-default)}.link-badge-dim svg{fill:var(--color-fg-muted)}.link-badge-dim:hover svg{fill:var(--color-fg-muted)}.fullwidth-link{width:100%;text-align:left}.fullwidth-link:hover{background-color:var(--color-canvas-subtle)}.trace-link{margin-right:3px}.trace-link-separator{color:var(--color-fg-muted);-webkit-user-select:none;user-select:none}.expandable-summary{cursor:pointer;list-style:none;white-space:nowrap;padding-left:4px}.label{display:inline-block;padding:0 8px;font-size:12px;font-weight:500;line-height:18px;border:1px solid transparent;border-radius:2em;background-color:var(--color-scale-gray-4);color:#fff;margin:0 10px;flex:none;font-weight:600;cursor:pointer}.label-anchor{text-decoration:none;color:var(--color-fg-default)}:root.light-mode .label-color-0{background-color:var(--color-scale-blue-0);color:var(--color-scale-blue-6);border:1px solid var(--color-scale-blue-4)}:root.light-mode .label-color-1{background-color:var(--color-scale-yellow-0);color:var(--color-scale-yellow-6);border:1px solid var(--color-scale-yellow-4)}:root.light-mode .label-color-2{background-color:var(--color-scale-purple-0);color:var(--color-scale-purple-6);border:1px solid var(--color-scale-purple-4)}:root.light-mode .label-color-3{background-color:var(--color-scale-pink-0);color:var(--color-scale-pink-6);border:1px solid var(--color-scale-pink-4)}:root.light-mode .label-color-4{background-color:var(--color-scale-coral-0);color:var(--color-scale-coral-6);border:1px solid var(--color-scale-coral-4)}:root.light-mode .label-color-5{background-color:var(--color-scale-orange-0);color:var(--color-scale-orange-6);border:1px solid var(--color-scale-orange-4)}:root.dark-mode .label-color-0{background-color:var(--color-scale-blue-9);color:var(--color-scale-blue-2);border:1px solid var(--color-scale-blue-4)}:root.dark-mode .label-color-1{background-color:var(--color-scale-yellow-9);color:var(--color-scale-yellow-2);border:1px solid var(--color-scale-yellow-4)}:root.dark-mode .label-color-2{background-color:var(--color-scale-purple-9);color:var(--color-scale-purple-2);border:1px solid var(--color-scale-purple-4)}:root.dark-mode .label-color-3{background-color:var(--color-scale-pink-9);color:var(--color-scale-pink-2);border:1px solid var(--color-scale-pink-4)}:root.dark-mode .label-color-4{background-color:var(--color-scale-coral-9);color:var(--color-scale-coral-2);border:1px solid var(--color-scale-coral-4)}:root.dark-mode .label-color-5{background-color:var(--color-scale-orange-9);color:var(--color-scale-orange-2);border:1px solid var(--color-scale-orange-4)}.label-row .label{margin:0}.label-row .label:not(:first-child){margin-left:6px}html,body{width:100%;height:100%;padding:0;margin:0;overscroll-behavior-x:none}body{overflow:auto;max-width:1024px;margin:0 auto;width:100%}.test-file-test:not(:first-child){border-top:1px solid var(--color-border-default)}@media only screen and (max-width: 600px){.htmlreport{padding:0!important}}.tabbed-pane{display:flex;flex:auto;overflow:hidden}.tabbed-pane-tab-strip{display:flex;align-items:center;padding-right:10px;flex:none;width:100%;z-index:2;font-size:14px;line-height:32px;color:var(--color-fg-default);height:48px;min-width:70px;box-shadow:inset 0 -1px 0 var(--color-border-muted)!important}.tabbed-pane-tab-strip:focus{outline:none}.tabbed-pane-tab-element{padding:4px 8px 0;margin-right:4px;cursor:pointer;display:flex;flex:none;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none;border-bottom:2px solid transparent;outline:none;height:100%}.tabbed-pane-tab-label{max-width:250px;white-space:pre;overflow:hidden;text-overflow:ellipsis;display:inline-block;height:30px;padding:0 8px;border-radius:6px}.tabbed-pane-tab-label:hover{background-color:var(--color-control-transparent-bg-hover)}.tabbed-pane-tab-element.selected{border-bottom-color:#666;-webkit-text-stroke:.5px currentColor}.chip-header{border:1px solid var(--color-border-default);border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--color-canvas-subtle);padding:0 8px;border-bottom:none;margin-top:12px;font-weight:600;line-height:38px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.chip-header-allow-selection{-webkit-user-select:text;user-select:text}.chip-header.expanded-false{border:1px solid var(--color-border-default);border-radius:6px}.chip-header.expanded-false,.chip-header.expanded-true{cursor:pointer}.chip-body{border:1px solid var(--color-border-default);border-bottom-left-radius:6px;border-bottom-right-radius:6px;padding:16px;margin-bottom:12px;overflow:hidden}.chip-body-no-insets{padding:0}.chip-footer{border-top:1px solid var(--color-border-default)}@media only screen and (max-width: 600px){.chip-header{border-radius:0;border-right:none;border-left:none}.chip-body{border-radius:0;border-right:none;border-left:none;padding:8px}.chip-body-no-insets{padding:0}}.test-case-column{border-radius:6px;margin-bottom:24px}.test-case-column .tab-element.selected{font-weight:600;border-bottom-color:var(--color-primer-border-active)}.test-case-column .tab-element{border:none;color:var(--color-fg-default);border-bottom:2px solid transparent}.test-case-column .tab-element:hover{color:var(--color-fg-default)}.test-case-location,.test-case-duration{flex:none;align-items:center;padding:0 8px 8px}.selected .test-case-run-duration{-webkit-text-stroke:0}.test-case-run-duration{color:var(--color-fg-muted);padding-left:8px}.header-view .test-case-path{flex:none;flex-shrink:1;align-items:center;padding-right:8px}.test-case-annotation{flex:none;align-items:center;padding:0 8px;line-height:24px;white-space:pre-wrap}@media only screen and (max-width: 600px){.test-case-column{border-radius:0!important;margin:0!important}}.tree-item{display:flex;flex-direction:column;overflow:hidden;min-width:0;line-height:38px}.tree-item-title{cursor:pointer;overflow:hidden;text-overflow:ellipsis;min-width:0;display:flex;align-items:center}.tree-item-body{min-height:18px}.yellow-flash{animation:yellowflash-bg 2s}@keyframes yellowflash-bg{0%{background:var(--color-attention-subtle)}to{background:transparent}}:root{--vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;--vscode-font-weight: normal;--vscode-font-size: 13px;--vscode-editor-font-family: "Droid Sans Mono", "monospace", monospace;--vscode-editor-font-weight: normal;--vscode-editor-font-size: 14px;--vscode-foreground: #616161;--vscode-disabledForeground: rgba(97, 97, 97, .5);--vscode-errorForeground: #a1260d;--vscode-descriptionForeground: #717171;--vscode-icon-foreground: #424242;--vscode-focusBorder: #0090f1;--vscode-textSeparator-foreground: rgba(0, 0, 0, .18);--vscode-textLink-foreground: #006ab1;--vscode-textLink-activeForeground: #006ab1;--vscode-textPreformat-foreground: #a31515;--vscode-textBlockQuote-background: rgba(127, 127, 127, .1);--vscode-textBlockQuote-border: rgba(0, 122, 204, .5);--vscode-textCodeBlock-background: rgba(220, 220, 220, .4);--vscode-widget-shadow: rgba(0, 0, 0, .16);--vscode-input-background: #ffffff;--vscode-input-foreground: #616161;--vscode-inputOption-activeBorder: #007acc;--vscode-inputOption-hoverBackground: rgba(184, 184, 184, .31);--vscode-inputOption-activeBackground: rgba(0, 144, 241, .2);--vscode-inputOption-activeForeground: #000000;--vscode-input-placeholderForeground: #767676;--vscode-inputValidation-infoBackground: #d6ecf2;--vscode-inputValidation-infoBorder: #007acc;--vscode-inputValidation-warningBackground: #f6f5d2;--vscode-inputValidation-warningBorder: #b89500;--vscode-inputValidation-errorBackground: #f2dede;--vscode-inputValidation-errorBorder: #be1100;--vscode-dropdown-background: #ffffff;--vscode-dropdown-border: #cecece;--vscode-checkbox-background: #ffffff;--vscode-checkbox-border: #cecece;--vscode-button-foreground: #ffffff;--vscode-button-separator: rgba(255, 255, 255, .4);--vscode-button-background: #007acc;--vscode-button-hoverBackground: #0062a3;--vscode-button-secondaryForeground: #ffffff;--vscode-button-secondaryBackground: #5f6a79;--vscode-button-secondaryHoverBackground: #4c5561;--vscode-badge-background: #c4c4c4;--vscode-badge-foreground: #333333;--vscode-scrollbar-shadow: #dddddd;--vscode-scrollbarSlider-background: rgba(100, 100, 100, .4);--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, .7);--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, .6);--vscode-progressBar-background: #0e70c0;--vscode-editorError-foreground: #e51400;--vscode-editorWarning-foreground: #bf8803;--vscode-editorInfo-foreground: #1a85ff;--vscode-editorHint-foreground: #6c6c6c;--vscode-sash-hoverBorder: #0090f1;--vscode-editor-background: #ffffff;--vscode-editor-foreground: #000000;--vscode-editorStickyScroll-background: #ffffff;--vscode-editorStickyScrollHover-background: #f0f0f0;--vscode-editorWidget-background: #f3f3f3;--vscode-editorWidget-foreground: #616161;--vscode-editorWidget-border: #c8c8c8;--vscode-quickInput-background: #f3f3f3;--vscode-quickInput-foreground: #616161;--vscode-quickInputTitle-background: rgba(0, 0, 0, .06);--vscode-pickerGroup-foreground: #0066bf;--vscode-pickerGroup-border: #cccedb;--vscode-keybindingLabel-background: rgba(221, 221, 221, .4);--vscode-keybindingLabel-foreground: #555555;--vscode-keybindingLabel-border: rgba(204, 204, 204, .4);--vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, .4);--vscode-editor-selectionBackground: #add6ff;--vscode-editor-inactiveSelectionBackground: #e5ebf1;--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, .5);--vscode-editor-findMatchBackground: #a8ac94;--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, .33);--vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, .3);--vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, .22);--vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, .15);--vscode-editorHoverWidget-background: #f3f3f3;--vscode-editorHoverWidget-foreground: #616161;--vscode-editorHoverWidget-border: #c8c8c8;--vscode-editorHoverWidget-statusBarBackground: #e7e7e7;--vscode-editorLink-activeForeground: #0000ff;--vscode-editorInlayHint-foreground: rgba(51, 51, 51, .8);--vscode-editorInlayHint-background: rgba(196, 196, 196, .3);--vscode-editorInlayHint-typeForeground: rgba(51, 51, 51, .8);--vscode-editorInlayHint-typeBackground: rgba(196, 196, 196, .3);--vscode-editorInlayHint-parameterForeground: rgba(51, 51, 51, .8);--vscode-editorInlayHint-parameterBackground: rgba(196, 196, 196, .3);--vscode-editorLightBulb-foreground: #ddb100;--vscode-editorLightBulbAutoFix-foreground: #007acc;--vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, .4);--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, .3);--vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, .2);--vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, .2);--vscode-diffEditor-diagonalFill: rgba(34, 34, 34, .2);--vscode-list-focusOutline: #0090f1;--vscode-list-focusAndSelectionOutline: #90c2f9;--vscode-list-activeSelectionBackground: #0060c0;--vscode-list-activeSelectionForeground: #ffffff;--vscode-list-activeSelectionIconForeground: #ffffff;--vscode-list-inactiveSelectionBackground: #e4e6f1;--vscode-list-hoverBackground: #e8e8e8;--vscode-list-dropBackground: #d6ebff;--vscode-list-highlightForeground: #0066bf;--vscode-list-focusHighlightForeground: #bbe7ff;--vscode-list-invalidItemForeground: #b89500;--vscode-list-errorForeground: #b01011;--vscode-list-warningForeground: #855f00;--vscode-listFilterWidget-background: #f3f3f3;--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);--vscode-listFilterWidget-noMatchesOutline: #be1100;--vscode-listFilterWidget-shadow: rgba(0, 0, 0, .16);--vscode-list-filterMatchBackground: rgba(234, 92, 0, .33);--vscode-tree-indentGuidesStroke: #a9a9a9;--vscode-tree-tableColumnsBorder: rgba(97, 97, 97, .13);--vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, .04);--vscode-list-deemphasizedForeground: #8e8e90;--vscode-quickInputList-focusForeground: #ffffff;--vscode-quickInputList-focusIconForeground: #ffffff;--vscode-quickInputList-focusBackground: #0060c0;--vscode-menu-foreground: #616161;--vscode-menu-background: #ffffff;--vscode-menu-selectionForeground: #ffffff;--vscode-menu-selectionBackground: #0060c0;--vscode-menu-separatorBackground: #d4d4d4;--vscode-toolbar-hoverBackground: rgba(184, 184, 184, .31);--vscode-toolbar-activeBackground: rgba(166, 166, 166, .31);--vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, .2);--vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, .5);--vscode-breadcrumb-foreground: rgba(97, 97, 97, .8);--vscode-breadcrumb-background: #ffffff;--vscode-breadcrumb-focusForeground: #4e4e4e;--vscode-breadcrumb-activeSelectionForeground: #4e4e4e;--vscode-breadcrumbPicker-background: #f3f3f3;--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, .5);--vscode-merge-currentContentBackground: rgba(64, 200, 174, .2);--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, .5);--vscode-merge-incomingContentBackground: rgba(64, 166, 255, .2);--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, .4);--vscode-merge-commonContentBackground: rgba(96, 96, 96, .16);--vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, .5);--vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, .5);--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, .4);--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, .49);--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, .8);--vscode-minimap-findMatchHighlight: #d18616;--vscode-minimap-selectionOccurrenceHighlight: #c9c9c9;--vscode-minimap-selectionHighlight: #add6ff;--vscode-minimap-errorHighlight: rgba(255, 18, 18, .7);--vscode-minimap-warningHighlight: #bf8803;--vscode-minimap-foregroundOpacity: #000000;--vscode-minimapSlider-background: rgba(100, 100, 100, .2);--vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, .35);--vscode-minimapSlider-activeBackground: rgba(0, 0, 0, .3);--vscode-problemsErrorIcon-foreground: #e51400;--vscode-problemsWarningIcon-foreground: #bf8803;--vscode-problemsInfoIcon-foreground: #1a85ff;--vscode-charts-foreground: #616161;--vscode-charts-lines: rgba(97, 97, 97, .5);--vscode-charts-red: #e51400;--vscode-charts-blue: #1a85ff;--vscode-charts-yellow: #bf8803;--vscode-charts-orange: #d18616;--vscode-charts-green: #388a34;--vscode-charts-purple: #652d90;--vscode-editor-lineHighlightBorder: #eeeeee;--vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, .2);--vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, .33);--vscode-editorCursor-foreground: #000000;--vscode-editorWhitespace-foreground: rgba(51, 51, 51, .2);--vscode-editorIndentGuide-background: #d3d3d3;--vscode-editorIndentGuide-activeBackground: #939393;--vscode-editorLineNumber-foreground: #237893;--vscode-editorActiveLineNumber-foreground: #0b216f;--vscode-editorLineNumber-activeForeground: #0b216f;--vscode-editorRuler-foreground: #d3d3d3;--vscode-editorCodeLens-foreground: #919191;--vscode-editorBracketMatch-background: rgba(0, 100, 0, .1);--vscode-editorBracketMatch-border: #b9b9b9;--vscode-editorOverviewRuler-border: rgba(127, 127, 127, .3);--vscode-editorGutter-background: #ffffff;--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, .47);--vscode-editorGhostText-foreground: rgba(0, 0, 0, .47);--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, .6);--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, .7);--vscode-editorOverviewRuler-warningForeground: #bf8803;--vscode-editorOverviewRuler-infoForeground: #1a85ff;--vscode-editorBracketHighlight-foreground1: #0431fa;--vscode-editorBracketHighlight-foreground2: #319331;--vscode-editorBracketHighlight-foreground3: #7b3814;--vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, .8);--vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0);--vscode-editorUnicodeHighlight-border: #cea33d;--vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, .08);--vscode-symbolIcon-arrayForeground: #616161;--vscode-symbolIcon-booleanForeground: #616161;--vscode-symbolIcon-classForeground: #d67e00;--vscode-symbolIcon-colorForeground: #616161;--vscode-symbolIcon-constantForeground: #616161;--vscode-symbolIcon-constructorForeground: #652d90;--vscode-symbolIcon-enumeratorForeground: #d67e00;--vscode-symbolIcon-enumeratorMemberForeground: #007acc;--vscode-symbolIcon-eventForeground: #d67e00;--vscode-symbolIcon-fieldForeground: #007acc;--vscode-symbolIcon-fileForeground: #616161;--vscode-symbolIcon-folderForeground: #616161;--vscode-symbolIcon-functionForeground: #652d90;--vscode-symbolIcon-interfaceForeground: #007acc;--vscode-symbolIcon-keyForeground: #616161;--vscode-symbolIcon-keywordForeground: #616161;--vscode-symbolIcon-methodForeground: #652d90;--vscode-symbolIcon-moduleForeground: #616161;--vscode-symbolIcon-namespaceForeground: #616161;--vscode-symbolIcon-nullForeground: #616161;--vscode-symbolIcon-numberForeground: #616161;--vscode-symbolIcon-objectForeground: #616161;--vscode-symbolIcon-operatorForeground: #616161;--vscode-symbolIcon-packageForeground: #616161;--vscode-symbolIcon-propertyForeground: #616161;--vscode-symbolIcon-referenceForeground: #616161;--vscode-symbolIcon-snippetForeground: #616161;--vscode-symbolIcon-stringForeground: #616161;--vscode-symbolIcon-structForeground: #616161;--vscode-symbolIcon-textForeground: #616161;--vscode-symbolIcon-typeParameterForeground: #616161;--vscode-symbolIcon-unitForeground: #616161;--vscode-symbolIcon-variableForeground: #007acc;--vscode-editorHoverWidget-highlightForeground: #0066bf;--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;--vscode-editor-foldBackground: rgba(173, 214, 255, .3);--vscode-editorGutter-foldingControlForeground: #424242;--vscode-editor-linkedEditingBackground: rgba(255, 0, 0, .3);--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, .25);--vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, .25);--vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, .8);--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, .8);--vscode-peekViewTitle-background: rgba(26, 133, 255, .1);--vscode-peekViewTitleLabel-foreground: #000000;--vscode-peekViewTitleDescription-foreground: #616161;--vscode-peekView-border: #1a85ff;--vscode-peekViewResult-background: #f3f3f3;--vscode-peekViewResult-lineForeground: #646465;--vscode-peekViewResult-fileForeground: #1e1e1e;--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, .2);--vscode-peekViewResult-selectionForeground: #6c6c6c;--vscode-peekViewEditor-background: #f2f8fc;--vscode-peekViewEditorGutter-background: #f2f8fc;--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, .3);--vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, .87);--vscode-editorMarkerNavigationError-background: #e51400;--vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, .1);--vscode-editorMarkerNavigationWarning-background: #bf8803;--vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, .1);--vscode-editorMarkerNavigationInfo-background: #1a85ff;--vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, .1);--vscode-editorMarkerNavigation-background: #ffffff;--vscode-editorSuggestWidget-background: #f3f3f3;--vscode-editorSuggestWidget-border: #c8c8c8;--vscode-editorSuggestWidget-foreground: #000000;--vscode-editorSuggestWidget-selectedForeground: #ffffff;--vscode-editorSuggestWidget-selectedIconForeground: #ffffff;--vscode-editorSuggestWidget-selectedBackground: #0060c0;--vscode-editorSuggestWidget-highlightForeground: #0066bf;--vscode-editorSuggestWidget-focusHighlightForeground: #bbe7ff;--vscode-editorSuggestWidgetStatus-foreground: rgba(0, 0, 0, .5);--vscode-tab-activeBackground: #ffffff;--vscode-tab-unfocusedActiveBackground: #ffffff;--vscode-tab-inactiveBackground: #ececec;--vscode-tab-unfocusedInactiveBackground: #ececec;--vscode-tab-activeForeground: #333333;--vscode-tab-inactiveForeground: rgba(51, 51, 51, .7);--vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, .7);--vscode-tab-unfocusedInactiveForeground: rgba(51, 51, 51, .35);--vscode-tab-border: #f3f3f3;--vscode-tab-lastPinnedBorder: rgba(97, 97, 97, .19);--vscode-tab-activeModifiedBorder: #33aaee;--vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, .5);--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, .7);--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, .25);--vscode-editorPane-background: #ffffff;--vscode-editorGroupHeader-tabsBackground: #f3f3f3;--vscode-editorGroupHeader-noTabsBackground: #ffffff;--vscode-editorGroup-border: #e7e7e7;--vscode-editorGroup-dropBackground: rgba(38, 119, 203, .18);--vscode-editorGroup-dropIntoPromptForeground: #616161;--vscode-editorGroup-dropIntoPromptBackground: #f3f3f3;--vscode-sideBySideEditor-horizontalBorder: #e7e7e7;--vscode-sideBySideEditor-verticalBorder: #e7e7e7;--vscode-panel-background: #ffffff;--vscode-panel-border: rgba(128, 128, 128, .35);--vscode-panelTitle-activeForeground: #424242;--vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, .75);--vscode-panelTitle-activeBorder: #424242;--vscode-panelInput-border: #dddddd;--vscode-panel-dropBorder: #424242;--vscode-panelSection-dropBackground: rgba(38, 119, 203, .18);--vscode-panelSectionHeader-background: rgba(128, 128, 128, .2);--vscode-panelSection-border: rgba(128, 128, 128, .35);--vscode-banner-background: #004386;--vscode-banner-foreground: #ffffff;--vscode-banner-iconForeground: #1a85ff;--vscode-statusBar-foreground: #ffffff;--vscode-statusBar-noFolderForeground: #ffffff;--vscode-statusBar-background: #007acc;--vscode-statusBar-noFolderBackground: #68217a;--vscode-statusBar-focusBorder: #ffffff;--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, .18);--vscode-statusBarItem-focusBorder: #ffffff;--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, .12);--vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, .2);--vscode-statusBarItem-prominentForeground: #ffffff;--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, .5);--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, .3);--vscode-statusBarItem-errorBackground: #c72e0f;--vscode-statusBarItem-errorForeground: #ffffff;--vscode-statusBarItem-warningBackground: #725102;--vscode-statusBarItem-warningForeground: #ffffff;--vscode-activityBar-background: #2c2c2c;--vscode-activityBar-foreground: #ffffff;--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, .4);--vscode-activityBar-activeBorder: #ffffff;--vscode-activityBar-dropBorder: #ffffff;--vscode-activityBarBadge-background: #007acc;--vscode-activityBarBadge-foreground: #ffffff;--vscode-statusBarItem-remoteBackground: #16825d;--vscode-statusBarItem-remoteForeground: #ffffff;--vscode-extensionBadge-remoteBackground: #007acc;--vscode-extensionBadge-remoteForeground: #ffffff;--vscode-sideBar-background: #f3f3f3;--vscode-sideBarTitle-foreground: #6f6f6f;--vscode-sideBar-dropBackground: rgba(38, 119, 203, .18);--vscode-sideBarSectionHeader-background: rgba(0, 0, 0, 0);--vscode-sideBarSectionHeader-border: rgba(97, 97, 97, .19);--vscode-titleBar-activeForeground: #333333;--vscode-titleBar-inactiveForeground: rgba(51, 51, 51, .6);--vscode-titleBar-activeBackground: #dddddd;--vscode-titleBar-inactiveBackground: rgba(221, 221, 221, .6);--vscode-menubar-selectionForeground: #333333;--vscode-menubar-selectionBackground: rgba(184, 184, 184, .31);--vscode-notifications-foreground: #616161;--vscode-notifications-background: #f3f3f3;--vscode-notificationLink-foreground: #006ab1;--vscode-notificationCenterHeader-background: #e7e7e7;--vscode-notifications-border: #e7e7e7;--vscode-notificationsErrorIcon-foreground: #e51400;--vscode-notificationsWarningIcon-foreground: #bf8803;--vscode-notificationsInfoIcon-foreground: #1a85ff;--vscode-commandCenter-foreground: #333333;--vscode-commandCenter-activeForeground: #333333;--vscode-commandCenter-activeBackground: rgba(184, 184, 184, .31);--vscode-commandCenter-border: rgba(128, 128, 128, .35);--vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, .5);--vscode-editorCommentsWidget-unresolvedBorder: #1a85ff;--vscode-editorCommentsWidget-rangeBackground: rgba(26, 133, 255, .1);--vscode-editorCommentsWidget-rangeBorder: rgba(26, 133, 255, .4);--vscode-editorCommentsWidget-rangeActiveBackground: rgba(26, 133, 255, .1);--vscode-editorCommentsWidget-rangeActiveBorder: rgba(26, 133, 255, .4);--vscode-editorGutter-commentRangeForeground: #d5d8e9;--vscode-debugToolBar-background: #f3f3f3;--vscode-debugIcon-startForeground: #388a34;--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, .45);--vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, .45);--vscode-mergeEditor-change\.background: rgba(155, 185, 85, .2);--vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, .4);--vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, .48);--vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600;--vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, .29);--vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, .8);--vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, .93);--vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03;--vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, .28);--vscode-settings-headerForeground: #444444;--vscode-settings-modifiedItemIndicator: #66afe0;--vscode-settings-headerBorder: rgba(128, 128, 128, .35);--vscode-settings-sashBorder: rgba(128, 128, 128, .35);--vscode-settings-dropdownBackground: #ffffff;--vscode-settings-dropdownBorder: #cecece;--vscode-settings-dropdownListBorder: #c8c8c8;--vscode-settings-checkboxBackground: #ffffff;--vscode-settings-checkboxBorder: #cecece;--vscode-settings-textInputBackground: #ffffff;--vscode-settings-textInputForeground: #616161;--vscode-settings-textInputBorder: #cecece;--vscode-settings-numberInputBackground: #ffffff;--vscode-settings-numberInputForeground: #616161;--vscode-settings-numberInputBorder: #cecece;--vscode-settings-focusedRowBackground: rgba(232, 232, 232, .6);--vscode-settings-rowHoverBackground: rgba(232, 232, 232, .3);--vscode-settings-focusedRowBorder: rgba(0, 0, 0, .12);--vscode-terminal-foreground: #333333;--vscode-terminal-selectionBackground: #add6ff;--vscode-terminal-inactiveSelectionBackground: #e5ebf1;--vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, .25);--vscode-terminalCommandDecoration-successBackground: #2090d3;--vscode-terminalCommandDecoration-errorBackground: #e51400;--vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, .8);--vscode-terminal-border: rgba(128, 128, 128, .35);--vscode-terminal-findMatchBackground: #a8ac94;--vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, .33);--vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, .49);--vscode-terminal-dropBackground: rgba(38, 119, 203, .18);--vscode-testing-iconFailed: #f14c4c;--vscode-testing-iconErrored: #f14c4c;--vscode-testing-iconPassed: #73c991;--vscode-testing-runAction: #73c991;--vscode-testing-iconQueued: #cca700;--vscode-testing-iconUnset: #848484;--vscode-testing-iconSkipped: #848484;--vscode-testing-peekBorder: #e51400;--vscode-testing-peekHeaderBackground: rgba(229, 20, 0, .1);--vscode-testing-message\.error\.decorationForeground: #e51400;--vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, .2);--vscode-testing-message\.info\.decorationForeground: rgba(0, 0, 0, .5);--vscode-welcomePage-tileBackground: #f3f3f3;--vscode-welcomePage-tileHoverBackground: #dbdbdb;--vscode-welcomePage-tileShadow: rgba(0, 0, 0, .16);--vscode-welcomePage-progress\.background: #ffffff;--vscode-welcomePage-progress\.foreground: #006ab1;--vscode-debugExceptionWidget-border: #a31515;--vscode-debugExceptionWidget-background: #f1dfde;--vscode-ports-iconRunningProcessForeground: #369432;--vscode-statusBar-debuggingBackground: #cc6633;--vscode-statusBar-debuggingForeground: #ffffff;--vscode-editor-inlineValuesForeground: rgba(0, 0, 0, .5);--vscode-editor-inlineValuesBackground: rgba(255, 200, 0, .2);--vscode-editorGutter-modifiedBackground: #2090d3;--vscode-editorGutter-addedBackground: #48985d;--vscode-editorGutter-deletedBackground: #e51400;--vscode-minimapGutter-modifiedBackground: #2090d3;--vscode-minimapGutter-addedBackground: #48985d;--vscode-minimapGutter-deletedBackground: #e51400;--vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, .6);--vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, .6);--vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, .6);--vscode-debugIcon-breakpointForeground: #e51400;--vscode-debugIcon-breakpointDisabledForeground: #848484;--vscode-debugIcon-breakpointUnverifiedForeground: #848484;--vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700;--vscode-debugIcon-breakpointStackframeForeground: #89d185;--vscode-notebook-cellBorderColor: #e8e8e8;--vscode-notebook-focusedEditorBorder: #0090f1;--vscode-notebookStatusSuccessIcon-foreground: #388a34;--vscode-notebookStatusErrorIcon-foreground: #a1260d;--vscode-notebookStatusRunningIcon-foreground: #616161;--vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, .35);--vscode-notebook-selectedCellBackground: rgba(200, 221, 241, .31);--vscode-notebook-selectedCellBorder: #e8e8e8;--vscode-notebook-focusedCellBorder: #0090f1;--vscode-notebook-inactiveFocusedCellBorder: #e8e8e8;--vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, .08);--vscode-notebook-cellInsertionIndicator: #0090f1;--vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, .4);--vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, .7);--vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, .6);--vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, .2);--vscode-notebook-cellEditorBackground: #f3f3f3;--vscode-notebook-editorBackground: #ffffff;--vscode-keybindingTable-headerBackground: rgba(97, 97, 97, .04);--vscode-keybindingTable-rowsBackground: rgba(97, 97, 97, .04);--vscode-scm-providerBorder: #c8c8c8;--vscode-searchEditor-textInputBorder: #cecece;--vscode-debugTokenExpression-name: #9b46b0;--vscode-debugTokenExpression-value: rgba(108, 108, 108, .8);--vscode-debugTokenExpression-string: #a31515;--vscode-debugTokenExpression-boolean: #0000ff;--vscode-debugTokenExpression-number: #098658;--vscode-debugTokenExpression-error: #e51400;--vscode-debugView-exceptionLabelForeground: #ffffff;--vscode-debugView-exceptionLabelBackground: #a31515;--vscode-debugView-stateLabelForeground: #616161;--vscode-debugView-stateLabelBackground: rgba(136, 136, 136, .27);--vscode-debugView-valueChangedHighlight: #569cd6;--vscode-debugConsole-infoForeground: #1a85ff;--vscode-debugConsole-warningForeground: #bf8803;--vscode-debugConsole-errorForeground: #a1260d;--vscode-debugConsole-sourceForeground: #616161;--vscode-debugConsoleInputIcon-foreground: #616161;--vscode-debugIcon-pauseForeground: #007acc;--vscode-debugIcon-stopForeground: #a1260d;--vscode-debugIcon-disconnectForeground: #a1260d;--vscode-debugIcon-restartForeground: #388a34;--vscode-debugIcon-stepOverForeground: #007acc;--vscode-debugIcon-stepIntoForeground: #007acc;--vscode-debugIcon-stepOutForeground: #007acc;--vscode-debugIcon-continueForeground: #007acc;--vscode-debugIcon-stepBackForeground: #007acc;--vscode-extensionButton-prominentBackground: #007acc;--vscode-extensionButton-prominentForeground: #ffffff;--vscode-extensionButton-prominentHoverBackground: #0062a3;--vscode-extensionIcon-starForeground: #df6100;--vscode-extensionIcon-verifiedForeground: #006ab1;--vscode-extensionIcon-preReleaseForeground: #1d9271;--vscode-extensionIcon-sponsorForeground: #b51e78;--vscode-terminal-ansiBlack: #000000;--vscode-terminal-ansiRed: #cd3131;--vscode-terminal-ansiGreen: #00bc00;--vscode-terminal-ansiYellow: #949800;--vscode-terminal-ansiBlue: #0451a5;--vscode-terminal-ansiMagenta: #bc05bc;--vscode-terminal-ansiCyan: #0598bc;--vscode-terminal-ansiWhite: #555555;--vscode-terminal-ansiBrightBlack: #666666;--vscode-terminal-ansiBrightRed: #cd3131;--vscode-terminal-ansiBrightGreen: #14ce14;--vscode-terminal-ansiBrightYellow: #b5ba00;--vscode-terminal-ansiBrightBlue: #0451a5;--vscode-terminal-ansiBrightMagenta: #bc05bc;--vscode-terminal-ansiBrightCyan: #0598bc;--vscode-terminal-ansiBrightWhite: #a5a5a5;--vscode-interactive-activeCodeBorder: #1a85ff;--vscode-interactive-inactiveCodeBorder: #e4e6f1;--vscode-gitDecoration-addedResourceForeground: #587c0c;--vscode-gitDecoration-modifiedResourceForeground: #895503;--vscode-gitDecoration-deletedResourceForeground: #ad0707;--vscode-gitDecoration-renamedResourceForeground: #007100;--vscode-gitDecoration-untrackedResourceForeground: #007100;--vscode-gitDecoration-ignoredResourceForeground: #8e8e90;--vscode-gitDecoration-stageModifiedResourceForeground: #895503;--vscode-gitDecoration-stageDeletedResourceForeground: #ad0707;--vscode-gitDecoration-conflictingResourceForeground: #ad0707;--vscode-gitDecoration-submoduleResourceForeground: #1258a7}:root.light-mode{color-scheme:light}:root.dark-mode{color-scheme:dark;--vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;--vscode-font-weight: normal;--vscode-font-size: 13px;--vscode-editor-font-family: "Droid Sans Mono", "monospace", monospace;--vscode-editor-font-weight: normal;--vscode-editor-font-size: 14px;--vscode-foreground: #cccccc;--vscode-disabledForeground: rgba(204, 204, 204, .5);--vscode-errorForeground: #f48771;--vscode-descriptionForeground: rgba(204, 204, 204, .7);--vscode-icon-foreground: #c5c5c5;--vscode-focusBorder: #007fd4;--vscode-textSeparator-foreground: rgba(255, 255, 255, .18);--vscode-textLink-foreground: #3794ff;--vscode-textLink-activeForeground: #3794ff;--vscode-textPreformat-foreground: #d7ba7d;--vscode-textBlockQuote-background: rgba(127, 127, 127, .1);--vscode-textBlockQuote-border: rgba(0, 122, 204, .5);--vscode-textCodeBlock-background: rgba(10, 10, 10, .4);--vscode-widget-shadow: rgba(0, 0, 0, .36);--vscode-input-background: #3c3c3c;--vscode-input-foreground: #cccccc;--vscode-inputOption-activeBorder: #007acc;--vscode-inputOption-hoverBackground: rgba(90, 93, 94, .5);--vscode-inputOption-activeBackground: rgba(0, 127, 212, .4);--vscode-inputOption-activeForeground: #ffffff;--vscode-input-placeholderForeground: #a6a6a6;--vscode-inputValidation-infoBackground: #063b49;--vscode-inputValidation-infoBorder: #007acc;--vscode-inputValidation-warningBackground: #352a05;--vscode-inputValidation-warningBorder: #b89500;--vscode-inputValidation-errorBackground: #5a1d1d;--vscode-inputValidation-errorBorder: #be1100;--vscode-dropdown-background: #3c3c3c;--vscode-dropdown-foreground: #f0f0f0;--vscode-dropdown-border: #3c3c3c;--vscode-checkbox-background: #3c3c3c;--vscode-checkbox-foreground: #f0f0f0;--vscode-checkbox-border: #3c3c3c;--vscode-button-foreground: #ffffff;--vscode-button-separator: rgba(255, 255, 255, .4);--vscode-button-background: #0e639c;--vscode-button-hoverBackground: #1177bb;--vscode-button-secondaryForeground: #ffffff;--vscode-button-secondaryBackground: #3a3d41;--vscode-button-secondaryHoverBackground: #45494e;--vscode-badge-background: #4d4d4d;--vscode-badge-foreground: #ffffff;--vscode-scrollbar-shadow: #000000;--vscode-scrollbarSlider-background: rgba(121, 121, 121, .4);--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, .7);--vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, .4);--vscode-progressBar-background: #0e70c0;--vscode-editorError-foreground: #f14c4c;--vscode-editorWarning-foreground: #cca700;--vscode-editorInfo-foreground: #3794ff;--vscode-editorHint-foreground: rgba(238, 238, 238, .7);--vscode-sash-hoverBorder: #007fd4;--vscode-editor-background: #1e1e1e;--vscode-editor-foreground: #d4d4d4;--vscode-editorStickyScroll-background: #1e1e1e;--vscode-editorStickyScrollHover-background: #2a2d2e;--vscode-editorWidget-background: #252526;--vscode-editorWidget-foreground: #cccccc;--vscode-editorWidget-border: #454545;--vscode-quickInput-background: #252526;--vscode-quickInput-foreground: #cccccc;--vscode-quickInputTitle-background: rgba(255, 255, 255, .1);--vscode-pickerGroup-foreground: #3794ff;--vscode-pickerGroup-border: #3f3f46;--vscode-keybindingLabel-background: rgba(128, 128, 128, .17);--vscode-keybindingLabel-foreground: #cccccc;--vscode-keybindingLabel-border: rgba(51, 51, 51, .6);--vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, .6);--vscode-editor-selectionBackground: #264f78;--vscode-editor-inactiveSelectionBackground: #3a3d41;--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, .15);--vscode-editor-findMatchBackground: #515c6a;--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, .33);--vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, .4);--vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, .22);--vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, .25);--vscode-editorHoverWidget-background: #252526;--vscode-editorHoverWidget-foreground: #cccccc;--vscode-editorHoverWidget-border: #454545;--vscode-editorHoverWidget-statusBarBackground: #2c2c2d;--vscode-editorLink-activeForeground: #4e94ce;--vscode-editorInlayHint-foreground: rgba(255, 255, 255, .8);--vscode-editorInlayHint-background: rgba(77, 77, 77, .6);--vscode-editorInlayHint-typeForeground: rgba(255, 255, 255, .8);--vscode-editorInlayHint-typeBackground: rgba(77, 77, 77, .6);--vscode-editorInlayHint-parameterForeground: rgba(255, 255, 255, .8);--vscode-editorInlayHint-parameterBackground: rgba(77, 77, 77, .6);--vscode-editorLightBulb-foreground: #ffcc00;--vscode-editorLightBulbAutoFix-foreground: #75beff;--vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, .2);--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, .4);--vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, .2);--vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, .2);--vscode-diffEditor-diagonalFill: rgba(204, 204, 204, .2);--vscode-list-focusOutline: #007fd4;--vscode-list-activeSelectionBackground: #04395e;--vscode-list-activeSelectionForeground: #ffffff;--vscode-list-activeSelectionIconForeground: #ffffff;--vscode-list-inactiveSelectionBackground: #37373d;--vscode-list-hoverBackground: #2a2d2e;--vscode-list-dropBackground: #383b3d;--vscode-list-highlightForeground: #2aaaff;--vscode-list-focusHighlightForeground: #2aaaff;--vscode-list-invalidItemForeground: #b89500;--vscode-list-errorForeground: #f88070;--vscode-list-warningForeground: #cca700;--vscode-listFilterWidget-background: #252526;--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);--vscode-listFilterWidget-noMatchesOutline: #be1100;--vscode-listFilterWidget-shadow: rgba(0, 0, 0, .36);--vscode-list-filterMatchBackground: rgba(234, 92, 0, .33);--vscode-tree-indentGuidesStroke: #585858;--vscode-tree-tableColumnsBorder: rgba(204, 204, 204, .13);--vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, .04);--vscode-list-deemphasizedForeground: #8c8c8c;--vscode-quickInputList-focusForeground: #ffffff;--vscode-quickInputList-focusIconForeground: #ffffff;--vscode-quickInputList-focusBackground: #04395e;--vscode-menu-foreground: #cccccc;--vscode-menu-background: #303031;--vscode-menu-selectionForeground: #ffffff;--vscode-menu-selectionBackground: #04395e;--vscode-menu-separatorBackground: #606060;--vscode-toolbar-hoverBackground: rgba(90, 93, 94, .31);--vscode-toolbar-activeBackground: rgba(99, 102, 103, .31);--vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, .3);--vscode-editor-snippetFinalTabstopHighlightBorder: #525252;--vscode-breadcrumb-foreground: rgba(204, 204, 204, .8);--vscode-breadcrumb-background: #1e1e1e;--vscode-breadcrumb-focusForeground: #e0e0e0;--vscode-breadcrumb-activeSelectionForeground: #e0e0e0;--vscode-breadcrumbPicker-background: #252526;--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, .5);--vscode-merge-currentContentBackground: rgba(64, 200, 174, .2);--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, .5);--vscode-merge-incomingContentBackground: rgba(64, 166, 255, .2);--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, .4);--vscode-merge-commonContentBackground: rgba(96, 96, 96, .16);--vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, .5);--vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, .5);--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, .4);--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, .49);--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, .8);--vscode-minimap-findMatchHighlight: #d18616;--vscode-minimap-selectionOccurrenceHighlight: #676767;--vscode-minimap-selectionHighlight: #264f78;--vscode-minimap-errorHighlight: rgba(255, 18, 18, .7);--vscode-minimap-warningHighlight: #cca700;--vscode-minimap-foregroundOpacity: #000000;--vscode-minimapSlider-background: rgba(121, 121, 121, .2);--vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, .35);--vscode-minimapSlider-activeBackground: rgba(191, 191, 191, .2);--vscode-problemsErrorIcon-foreground: #f14c4c;--vscode-problemsWarningIcon-foreground: #cca700;--vscode-problemsInfoIcon-foreground: #3794ff;--vscode-charts-foreground: #cccccc;--vscode-charts-lines: rgba(204, 204, 204, .5);--vscode-charts-red: #f14c4c;--vscode-charts-blue: #3794ff;--vscode-charts-yellow: #cca700;--vscode-charts-orange: #d18616;--vscode-charts-green: #89d185;--vscode-charts-purple: #b180d7;--vscode-editor-lineHighlightBorder: #282828;--vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, .04);--vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, .33);--vscode-editorCursor-foreground: #aeafad;--vscode-editorWhitespace-foreground: rgba(227, 228, 226, .16);--vscode-editorIndentGuide-background: #404040;--vscode-editorIndentGuide-activeBackground: #707070;--vscode-editorLineNumber-foreground: #858585;--vscode-editorActiveLineNumber-foreground: #c6c6c6;--vscode-editorLineNumber-activeForeground: #c6c6c6;--vscode-editorRuler-foreground: #5a5a5a;--vscode-editorCodeLens-foreground: #999999;--vscode-editorBracketMatch-background: rgba(0, 100, 0, .1);--vscode-editorBracketMatch-border: #888888;--vscode-editorOverviewRuler-border: rgba(127, 127, 127, .3);--vscode-editorGutter-background: #1e1e1e;--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, .67);--vscode-editorGhostText-foreground: rgba(255, 255, 255, .34);--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, .6);--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, .7);--vscode-editorOverviewRuler-warningForeground: #cca700;--vscode-editorOverviewRuler-infoForeground: #3794ff;--vscode-editorBracketHighlight-foreground1: #ffd700;--vscode-editorBracketHighlight-foreground2: #da70d6;--vscode-editorBracketHighlight-foreground3: #179fff;--vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0);--vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, .8);--vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0);--vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0);--vscode-editorUnicodeHighlight-border: #bd9b03;--vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, .15);--vscode-symbolIcon-arrayForeground: #cccccc;--vscode-symbolIcon-booleanForeground: #cccccc;--vscode-symbolIcon-classForeground: #ee9d28;--vscode-symbolIcon-colorForeground: #cccccc;--vscode-symbolIcon-constantForeground: #cccccc;--vscode-symbolIcon-constructorForeground: #b180d7;--vscode-symbolIcon-enumeratorForeground: #ee9d28;--vscode-symbolIcon-enumeratorMemberForeground: #75beff;--vscode-symbolIcon-eventForeground: #ee9d28;--vscode-symbolIcon-fieldForeground: #75beff;--vscode-symbolIcon-fileForeground: #cccccc;--vscode-symbolIcon-folderForeground: #cccccc;--vscode-symbolIcon-functionForeground: #b180d7;--vscode-symbolIcon-interfaceForeground: #75beff;--vscode-symbolIcon-keyForeground: #cccccc;--vscode-symbolIcon-keywordForeground: #cccccc;--vscode-symbolIcon-methodForeground: #b180d7;--vscode-symbolIcon-moduleForeground: #cccccc;--vscode-symbolIcon-namespaceForeground: #cccccc;--vscode-symbolIcon-nullForeground: #cccccc;--vscode-symbolIcon-numberForeground: #cccccc;--vscode-symbolIcon-objectForeground: #cccccc;--vscode-symbolIcon-operatorForeground: #cccccc;--vscode-symbolIcon-packageForeground: #cccccc;--vscode-symbolIcon-propertyForeground: #cccccc;--vscode-symbolIcon-referenceForeground: #cccccc;--vscode-symbolIcon-snippetForeground: #cccccc;--vscode-symbolIcon-stringForeground: #cccccc;--vscode-symbolIcon-structForeground: #cccccc;--vscode-symbolIcon-textForeground: #cccccc;--vscode-symbolIcon-typeParameterForeground: #cccccc;--vscode-symbolIcon-unitForeground: #cccccc;--vscode-symbolIcon-variableForeground: #75beff;--vscode-editorHoverWidget-highlightForeground: #2aaaff;--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;--vscode-editor-foldBackground: rgba(38, 79, 120, .3);--vscode-editorGutter-foldingControlForeground: #c5c5c5;--vscode-editor-linkedEditingBackground: rgba(255, 0, 0, .3);--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, .72);--vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, .72);--vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, .8);--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, .8);--vscode-peekViewTitle-background: rgba(55, 148, 255, .1);--vscode-peekViewTitleLabel-foreground: #ffffff;--vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, .7);--vscode-peekView-border: #3794ff;--vscode-peekViewResult-background: #252526;--vscode-peekViewResult-lineForeground: #bbbbbb;--vscode-peekViewResult-fileForeground: #ffffff;--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, .2);--vscode-peekViewResult-selectionForeground: #ffffff;--vscode-peekViewEditor-background: #001f33;--vscode-peekViewEditorGutter-background: #001f33;--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, .3);--vscode-peekViewEditor-matchHighlightBackground: rgba(255, 143, 0, .6);--vscode-editorMarkerNavigationError-background: #f14c4c;--vscode-editorMarkerNavigationError-headerBackground: rgba(241, 76, 76, .1);--vscode-editorMarkerNavigationWarning-background: #cca700;--vscode-editorMarkerNavigationWarning-headerBackground: rgba(204, 167, 0, .1);--vscode-editorMarkerNavigationInfo-background: #3794ff;--vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, .1);--vscode-editorMarkerNavigation-background: #1e1e1e;--vscode-editorSuggestWidget-background: #252526;--vscode-editorSuggestWidget-border: #454545;--vscode-editorSuggestWidget-foreground: #d4d4d4;--vscode-editorSuggestWidget-selectedForeground: #ffffff;--vscode-editorSuggestWidget-selectedIconForeground: #ffffff;--vscode-editorSuggestWidget-selectedBackground: #04395e;--vscode-editorSuggestWidget-highlightForeground: #2aaaff;--vscode-editorSuggestWidget-focusHighlightForeground: #2aaaff;--vscode-editorSuggestWidgetStatus-foreground: rgba(212, 212, 212, .5);--vscode-tab-activeBackground: #1e1e1e;--vscode-tab-unfocusedActiveBackground: #1e1e1e;--vscode-tab-inactiveBackground: #2d2d2d;--vscode-tab-unfocusedInactiveBackground: #2d2d2d;--vscode-tab-activeForeground: #ffffff;--vscode-tab-inactiveForeground: rgba(255, 255, 255, .5);--vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, .5);--vscode-tab-unfocusedInactiveForeground: rgba(255, 255, 255, .25);--vscode-tab-border: #252526;--vscode-tab-lastPinnedBorder: rgba(204, 204, 204, .2);--vscode-tab-activeModifiedBorder: #3399cc;--vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, .5);--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, .5);--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, .25);--vscode-editorPane-background: #1e1e1e;--vscode-editorGroupHeader-tabsBackground: #252526;--vscode-editorGroupHeader-noTabsBackground: #1e1e1e;--vscode-editorGroup-border: #444444;--vscode-editorGroup-dropBackground: rgba(83, 89, 93, .5);--vscode-editorGroup-dropIntoPromptForeground: #cccccc;--vscode-editorGroup-dropIntoPromptBackground: #252526;--vscode-sideBySideEditor-horizontalBorder: #444444;--vscode-sideBySideEditor-verticalBorder: #444444;--vscode-panel-background: #1e1e1e;--vscode-panel-border: rgba(128, 128, 128, .35);--vscode-panelTitle-activeForeground: #e7e7e7;--vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, .6);--vscode-panelTitle-activeBorder: #e7e7e7;--vscode-panel-dropBorder: #e7e7e7;--vscode-panelSection-dropBackground: rgba(83, 89, 93, .5);--vscode-panelSectionHeader-background: rgba(128, 128, 128, .2);--vscode-panelSection-border: rgba(128, 128, 128, .35);--vscode-banner-background: #04395e;--vscode-banner-foreground: #ffffff;--vscode-banner-iconForeground: #3794ff;--vscode-statusBar-foreground: #ffffff;--vscode-statusBar-noFolderForeground: #ffffff;--vscode-statusBar-background: #007acc;--vscode-statusBar-noFolderBackground: #68217a;--vscode-statusBar-focusBorder: #ffffff;--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, .18);--vscode-statusBarItem-focusBorder: #ffffff;--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, .12);--vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, .2);--vscode-statusBarItem-prominentForeground: #ffffff;--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, .5);--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, .3);--vscode-statusBarItem-errorBackground: #c72e0f;--vscode-statusBarItem-errorForeground: #ffffff;--vscode-statusBarItem-warningBackground: #7a6400;--vscode-statusBarItem-warningForeground: #ffffff;--vscode-activityBar-background: #333333;--vscode-activityBar-foreground: #ffffff;--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, .4);--vscode-activityBar-activeBorder: #ffffff;--vscode-activityBar-dropBorder: #ffffff;--vscode-activityBarBadge-background: #007acc;--vscode-activityBarBadge-foreground: #ffffff;--vscode-statusBarItem-remoteBackground: #16825d;--vscode-statusBarItem-remoteForeground: #ffffff;--vscode-extensionBadge-remoteBackground: #007acc;--vscode-extensionBadge-remoteForeground: #ffffff;--vscode-sideBar-background: #252526;--vscode-sideBarTitle-foreground: #bbbbbb;--vscode-sideBar-dropBackground: rgba(83, 89, 93, .5);--vscode-sideBarSectionHeader-background: rgba(0, 0, 0, 0);--vscode-sideBarSectionHeader-border: rgba(204, 204, 204, .2);--vscode-titleBar-activeForeground: #cccccc;--vscode-titleBar-inactiveForeground: rgba(204, 204, 204, .6);--vscode-titleBar-activeBackground: #3c3c3c;--vscode-titleBar-inactiveBackground: rgba(60, 60, 60, .6);--vscode-menubar-selectionForeground: #cccccc;--vscode-menubar-selectionBackground: rgba(90, 93, 94, .31);--vscode-notifications-foreground: #cccccc;--vscode-notifications-background: #252526;--vscode-notificationLink-foreground: #3794ff;--vscode-notificationCenterHeader-background: #303031;--vscode-notifications-border: #303031;--vscode-notificationsErrorIcon-foreground: #f14c4c;--vscode-notificationsWarningIcon-foreground: #cca700;--vscode-notificationsInfoIcon-foreground: #3794ff;--vscode-commandCenter-foreground: #cccccc;--vscode-commandCenter-activeForeground: #cccccc;--vscode-commandCenter-activeBackground: rgba(90, 93, 94, .31);--vscode-commandCenter-border: rgba(128, 128, 128, .35);--vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, .5);--vscode-editorCommentsWidget-unresolvedBorder: #3794ff;--vscode-editorCommentsWidget-rangeBackground: rgba(55, 148, 255, .1);--vscode-editorCommentsWidget-rangeBorder: rgba(55, 148, 255, .4);--vscode-editorCommentsWidget-rangeActiveBackground: rgba(55, 148, 255, .1);--vscode-editorCommentsWidget-rangeActiveBorder: rgba(55, 148, 255, .4);--vscode-editorGutter-commentRangeForeground: #37373d;--vscode-debugToolBar-background: #333333;--vscode-debugIcon-startForeground: #89d185;--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, .2);--vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, .3);--vscode-mergeEditor-change\.background: rgba(155, 185, 85, .2);--vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, .2);--vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, .48);--vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600;--vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, .29);--vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, .8);--vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, .93);--vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03;--vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, .28);--vscode-settings-headerForeground: #e7e7e7;--vscode-settings-modifiedItemIndicator: #0c7d9d;--vscode-settings-headerBorder: rgba(128, 128, 128, .35);--vscode-settings-sashBorder: rgba(128, 128, 128, .35);--vscode-settings-dropdownBackground: #3c3c3c;--vscode-settings-dropdownForeground: #f0f0f0;--vscode-settings-dropdownBorder: #3c3c3c;--vscode-settings-dropdownListBorder: #454545;--vscode-settings-checkboxBackground: #3c3c3c;--vscode-settings-checkboxForeground: #f0f0f0;--vscode-settings-checkboxBorder: #3c3c3c;--vscode-settings-textInputBackground: #3c3c3c;--vscode-settings-textInputForeground: #cccccc;--vscode-settings-numberInputBackground: #3c3c3c;--vscode-settings-numberInputForeground: #cccccc;--vscode-settings-focusedRowBackground: rgba(42, 45, 46, .6);--vscode-settings-rowHoverBackground: rgba(42, 45, 46, .3);--vscode-settings-focusedRowBorder: rgba(255, 255, 255, .12);--vscode-terminal-foreground: #cccccc;--vscode-terminal-selectionBackground: #264f78;--vscode-terminal-inactiveSelectionBackground: #3a3d41;--vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, .25);--vscode-terminalCommandDecoration-successBackground: #1b81a8;--vscode-terminalCommandDecoration-errorBackground: #f14c4c;--vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, .8);--vscode-terminal-border: rgba(128, 128, 128, .35);--vscode-terminal-findMatchBackground: #515c6a;--vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, .33);--vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, .49);--vscode-terminal-dropBackground: rgba(83, 89, 93, .5);--vscode-testing-iconFailed: #f14c4c;--vscode-testing-iconErrored: #f14c4c;--vscode-testing-iconPassed: #73c991;--vscode-testing-runAction: #73c991;--vscode-testing-iconQueued: #cca700;--vscode-testing-iconUnset: #848484;--vscode-testing-iconSkipped: #848484;--vscode-testing-peekBorder: #f14c4c;--vscode-testing-peekHeaderBackground: rgba(241, 76, 76, .1);--vscode-testing-message\.error\.decorationForeground: #f14c4c;--vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, .2);--vscode-testing-message\.info\.decorationForeground: rgba(212, 212, 212, .5);--vscode-welcomePage-tileBackground: #252526;--vscode-welcomePage-tileHoverBackground: #2c2c2d;--vscode-welcomePage-tileShadow: rgba(0, 0, 0, .36);--vscode-welcomePage-progress\.background: #3c3c3c;--vscode-welcomePage-progress\.foreground: #3794ff;--vscode-debugExceptionWidget-border: #a31515;--vscode-debugExceptionWidget-background: #420b0d;--vscode-ports-iconRunningProcessForeground: #369432;--vscode-statusBar-debuggingBackground: #cc6633;--vscode-statusBar-debuggingForeground: #ffffff;--vscode-editor-inlineValuesForeground: rgba(255, 255, 255, .5);--vscode-editor-inlineValuesBackground: rgba(255, 200, 0, .2);--vscode-editorGutter-modifiedBackground: #1b81a8;--vscode-editorGutter-addedBackground: #487e02;--vscode-editorGutter-deletedBackground: #f14c4c;--vscode-minimapGutter-modifiedBackground: #1b81a8;--vscode-minimapGutter-addedBackground: #487e02;--vscode-minimapGutter-deletedBackground: #f14c4c;--vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, .6);--vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, .6);--vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, .6);--vscode-debugIcon-breakpointForeground: #e51400;--vscode-debugIcon-breakpointDisabledForeground: #848484;--vscode-debugIcon-breakpointUnverifiedForeground: #848484;--vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00;--vscode-debugIcon-breakpointStackframeForeground: #89d185;--vscode-notebook-cellBorderColor: #37373d;--vscode-notebook-focusedEditorBorder: #007fd4;--vscode-notebookStatusSuccessIcon-foreground: #89d185;--vscode-notebookStatusErrorIcon-foreground: #f48771;--vscode-notebookStatusRunningIcon-foreground: #cccccc;--vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, .35);--vscode-notebook-selectedCellBackground: #37373d;--vscode-notebook-selectedCellBorder: #37373d;--vscode-notebook-focusedCellBorder: #007fd4;--vscode-notebook-inactiveFocusedCellBorder: #37373d;--vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, .15);--vscode-notebook-cellInsertionIndicator: #007fd4;--vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, .4);--vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, .7);--vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, .4);--vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, .04);--vscode-notebook-cellEditorBackground: #252526;--vscode-notebook-editorBackground: #1e1e1e;--vscode-keybindingTable-headerBackground: rgba(204, 204, 204, .04);--vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, .04);--vscode-scm-providerBorder: #454545;--vscode-debugTokenExpression-name: #c586c0;--vscode-debugTokenExpression-value: rgba(204, 204, 204, .6);--vscode-debugTokenExpression-string: #ce9178;--vscode-debugTokenExpression-boolean: #4e94ce;--vscode-debugTokenExpression-number: #b5cea8;--vscode-debugTokenExpression-error: #f48771;--vscode-debugView-exceptionLabelForeground: #cccccc;--vscode-debugView-exceptionLabelBackground: #6c2022;--vscode-debugView-stateLabelForeground: #cccccc;--vscode-debugView-stateLabelBackground: rgba(136, 136, 136, .27);--vscode-debugView-valueChangedHighlight: #569cd6;--vscode-debugConsole-infoForeground: #3794ff;--vscode-debugConsole-warningForeground: #cca700;--vscode-debugConsole-errorForeground: #f48771;--vscode-debugConsole-sourceForeground: #cccccc;--vscode-debugConsoleInputIcon-foreground: #cccccc;--vscode-debugIcon-pauseForeground: #75beff;--vscode-debugIcon-stopForeground: #f48771;--vscode-debugIcon-disconnectForeground: #f48771;--vscode-debugIcon-restartForeground: #89d185;--vscode-debugIcon-stepOverForeground: #75beff;--vscode-debugIcon-stepIntoForeground: #75beff;--vscode-debugIcon-stepOutForeground: #75beff;--vscode-debugIcon-continueForeground: #75beff;--vscode-debugIcon-stepBackForeground: #75beff;--vscode-extensionButton-prominentBackground: #0e639c;--vscode-extensionButton-prominentForeground: #ffffff;--vscode-extensionButton-prominentHoverBackground: #1177bb;--vscode-extensionIcon-starForeground: #ff8e00;--vscode-extensionIcon-verifiedForeground: #3794ff;--vscode-extensionIcon-preReleaseForeground: #1d9271;--vscode-extensionIcon-sponsorForeground: #d758b3;--vscode-terminal-ansiBlack: #000000;--vscode-terminal-ansiRed: #cd3131;--vscode-terminal-ansiGreen: #0dbc79;--vscode-terminal-ansiYellow: #e5e510;--vscode-terminal-ansiBlue: #2472c8;--vscode-terminal-ansiMagenta: #bc3fbc;--vscode-terminal-ansiCyan: #11a8cd;--vscode-terminal-ansiWhite: #e5e5e5;--vscode-terminal-ansiBrightBlack: #666666;--vscode-terminal-ansiBrightRed: #f14c4c;--vscode-terminal-ansiBrightGreen: #23d18b;--vscode-terminal-ansiBrightYellow: #f5f543;--vscode-terminal-ansiBrightBlue: #3b8eea;--vscode-terminal-ansiBrightMagenta: #d670d6;--vscode-terminal-ansiBrightCyan: #29b8db;--vscode-terminal-ansiBrightWhite: #e5e5e5;--vscode-interactive-activeCodeBorder: #3794ff;--vscode-interactive-inactiveCodeBorder: #37373d;--vscode-gitDecoration-addedResourceForeground: #81b88b;--vscode-gitDecoration-modifiedResourceForeground: #e2c08d;--vscode-gitDecoration-deletedResourceForeground: #c74e39;--vscode-gitDecoration-renamedResourceForeground: #73c991;--vscode-gitDecoration-untrackedResourceForeground: #73c991;--vscode-gitDecoration-ignoredResourceForeground: #8c8c8c;--vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d;--vscode-gitDecoration-stageDeletedResourceForeground: #c74e39;--vscode-gitDecoration-conflictingResourceForeground: #e4676b;--vscode-gitDecoration-submoduleResourceForeground: #8db9e2}.test-error-container{position:relative;white-space:pre;flex:none;padding:0;background-color:var(--color-canvas-subtle);border-radius:6px;line-height:initial;margin-bottom:6px}.test-error-view{overflow:auto;padding:16px}.test-error-text{font-family:monospace}.test-result{flex:auto;display:flex;flex-direction:column;margin-bottom:24px}.test-result>div{flex:none}.test-result video,.test-result img.screenshot{flex:none;box-shadow:var(--box-shadow-thick);margin:24px auto;min-width:200px;max-width:80%}.test-result-path{padding:0 0 0 5px;color:var(--color-fg-muted)}.test-result-counter{border-radius:12px;color:var(--color-canvas-default);padding:2px 8px;line-height:normal}.step-title-container{display:flex;align-items:center;flex:auto;min-width:0}.step-title-container>*{flex-shrink:0}.step-title-text{flex-shrink:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;min-width:0}.step-spacer{flex:auto}.step-attachment-link{display:flex;flex:none;border-radius:4px;padding:4px}.step-attachment-link:hover{background-color:var(--color-neutral-muted)}.step-attachment-link .octicon{margin-right:0}.step-duration{flex:none;white-space:nowrap;margin-left:4px}:root.light-mode .test-result-counter{background:var(--color-scale-gray-5)}:root.dark-mode .test-result-counter{background:var(--color-scale-gray-3)}@media only screen and (max-width: 600px){.test-result{padding:0!important}}.test-file-test{line-height:32px;align-items:center;padding:2px 8px;overflow:hidden;text-overflow:ellipsis}.test-file-test:hover{background-color:var(--color-canvas-subtle)}.test-file-title{font-weight:600;font-size:16px}.test-file-details-row{padding:0 0 6px 8px;margin:0 0 0 15px;line-height:16px;font-weight:400;color:var(--color-fg-muted);display:flex;align-items:center}.test-file-details-row-items{display:flex;height:16px}.test-file-details-row-items>.link-badge{margin-top:-2px}.test-file-details-row-items>.trace-link{margin-top:-4px}.test-file-path{text-overflow:ellipsis;overflow:hidden;color:var(--color-fg-muted)}.test-file-path-link{margin-right:10px}.test-file-test-outcome-skipped{color:var(--color-fg-muted)}.test-file-test-status-icon{flex:none}.test-file-header-info{display:flex;align-items:center;gap:4px 8px;color:var(--color-fg-muted)}.test-file-header-br{flex-basis:100%;height:0}.test-file-no-files{margin-top:12px;color:var(--color-fg-muted);background-color:unset;font-weight:unset;border:1px solid var(--color-border-default);border-bottom-left-radius:6px;border-bottom-right-radius:6px}#root{color:var(--color-fg-default);font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";-webkit-font-smoothing:antialiased}.metadata-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--color-fg-default)}.metadata-toggle-second-line{margin-top:8px;margin-left:8px}.metadata-view{border:1px solid var(--color-border-default);border-radius:6px;margin-top:12px}.metadata-view .metadata-section{margin:8px 10px 8px 32px}.metadata-view span:not(.copy-button-container),.metadata-view a{display:inline-block;line-height:24px}.metadata-properties{display:flex;flex-direction:column;align-items:normal;gap:8px}.metadata-properties>div{height:24px}.metadata-separator{height:1px;border-bottom:1px solid var(--color-border-default)}.metadata-view a{color:var(--color-fg-default)}.copyable-property{white-space:pre}.copyable-property>span{display:flex;align-items:center}.gantt-bar{transition:opacity .2s;cursor:pointer;outline:none}.gantt-bar:hover,.gantt-bar:focus{opacity:.8;stroke:var(--color-fg-default);stroke-width:2} 79 + </style> 80 + </head> 81 + <body> 82 + <div id='root'></div> 83 + </body> 84 + </html> 85 + <script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
+12
packages/app/public/favicon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> 2 + <style> 3 + :root { --fg: #3b82f6; --ring: #1a1a2e; } 4 + @media (prefers-color-scheme: dark) { 5 + :root { --fg: #60a5fa; --ring: #f1f5f9; } 6 + } 7 + </style> 8 + <!-- Inner sphere --> 9 + <circle cx="32" cy="32" r="14" fill="var(--fg)" /> 10 + <!-- Exosphere ring --> 11 + <circle cx="32" cy="32" r="27" fill="none" stroke="var(--ring)" stroke-width="2.5" /> 12 + </svg>
+46
packages/app/src/api/spheres.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + import type { Sphere } from "@exosphere/core/db/schema"; 3 + import type { SphereModuleInfo, SphereData } from "@exosphere/core/types"; 4 + 5 + export type { Sphere, SphereModuleInfo, SphereData }; 6 + 7 + export interface ModulesData { 8 + modules: SphereModuleInfo[]; 9 + available: string[]; 10 + } 11 + 12 + export function getSphereModules(slug: string) { 13 + return apiFetch<ModulesData>(`/api/spheres/${encodeURIComponent(slug)}/modules`); 14 + } 15 + 16 + export function createSphere(body: { 17 + name: string; 18 + slug?: string; 19 + description?: string; 20 + visibility: "public" | "private"; 21 + writeAccess: "open" | "members"; 22 + }) { 23 + return apiFetch<{ sphere: Sphere }>("/api/spheres", { 24 + method: "POST", 25 + headers: { "Content-Type": "application/json" }, 26 + body: JSON.stringify(body), 27 + }); 28 + } 29 + 30 + export function enableModule(slug: string, moduleName: string) { 31 + return apiFetch<{ modules: SphereModuleInfo[] }>( 32 + `/api/spheres/${encodeURIComponent(slug)}/modules`, 33 + { 34 + method: "POST", 35 + headers: { "Content-Type": "application/json" }, 36 + body: JSON.stringify({ module: moduleName }), 37 + }, 38 + ); 39 + } 40 + 41 + export function disableModule(slug: string, moduleName: string) { 42 + return apiFetch<{ modules: SphereModuleInfo[] }>( 43 + `/api/spheres/${encodeURIComponent(slug)}/modules/${encodeURIComponent(moduleName)}`, 44 + { method: "DELETE" }, 45 + ); 46 + }
+93
packages/app/src/app.css.ts
··· 1 + import { globalStyle, createTheme } from "@vanilla-extract/css"; 2 + import { vars } from "@exosphere/client/theme.css"; 3 + 4 + const shared = { 5 + space: { 6 + xs: "4px", 7 + sm: "8px", 8 + md: "16px", 9 + lg: "24px", 10 + xl: "32px", 11 + xxl: "48px", 12 + }, 13 + radius: { 14 + sm: "8px", 15 + md: "10px", 16 + lg: "14px", 17 + }, 18 + // see https://github.com/system-fonts/modern-font-stacks 19 + font: { 20 + heading: "Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif", 21 + body: "ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT', 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif", 22 + }, 23 + }; 24 + 25 + export const lightTheme = createTheme(vars, { 26 + color: { 27 + text: "#1a1a2e", 28 + textMuted: "#64748b", 29 + bg: "#f8fafc", 30 + surface: "#ffffff", 31 + surfaceHover: "#f8fafc", 32 + border: "#e2e8f0", 33 + primary: "#3b82f6", 34 + primaryHover: "#2563eb", 35 + primaryLight: "#eff6ff", 36 + danger: "#ef4444", 37 + dangerLight: "#fef2f2", 38 + warning: "#f59e0b", 39 + warningLight: "#fffbeb", 40 + success: "#10b981", 41 + successLight: "#ecfdf5", 42 + shadow: "rgba(0, 0, 0, 0.04)", 43 + shadowStrong: "rgba(0, 0, 0, 0.08)", 44 + focusRing: "rgba(59, 130, 246, 0.2)", 45 + }, 46 + ...shared, 47 + }); 48 + 49 + export const darkTheme = createTheme(vars, { 50 + color: { 51 + text: "#f1f5f9", 52 + textMuted: "#94a3b8", 53 + bg: "#0f172a", 54 + surface: "#1e293b", 55 + surfaceHover: "#253349", 56 + border: "#334155", 57 + primary: "#60a5fa", 58 + primaryHover: "#93bbfd", 59 + primaryLight: "#1e3a5f", 60 + danger: "#f87171", 61 + dangerLight: "#3b1c1c", 62 + warning: "#fbbf24", 63 + warningLight: "#3b2f0a", 64 + success: "#34d399", 65 + successLight: "#0d3326", 66 + shadow: "rgba(0, 0, 0, 0.2)", 67 + shadowStrong: "rgba(0, 0, 0, 0.35)", 68 + focusRing: "rgba(96, 165, 250, 0.25)", 69 + }, 70 + ...shared, 71 + }); 72 + 73 + globalStyle("*, *::before, *::after", { 74 + margin: 0, 75 + }); 76 + 77 + globalStyle("body", { 78 + WebkitFontSmoothing: "antialiased", 79 + MozOsxFontSmoothing: "grayscale", 80 + }); 81 + 82 + globalStyle("#app", { 83 + minHeight: "100vh", 84 + }); 85 + 86 + globalStyle("a", { 87 + color: vars.color.primary, 88 + textDecoration: "none", 89 + }); 90 + 91 + globalStyle("a:hover", { 92 + textDecoration: "underline", 93 + });
+144
packages/app/src/app.tsx
··· 1 + import { useEffect } from "preact/hooks"; 2 + import { auth, logout } from "@exosphere/client/auth"; 3 + import { useLocation } from "@exosphere/client/router"; 4 + import { sphereState } from "@exosphere/client/sphere"; 5 + import * as ui from "@exosphere/client/ui.css"; 6 + import { ThemeToggle } from "@exosphere/client/components/theme-toggle"; 7 + import { Sun, Moon, Monitor } from "lucide-preact"; 8 + import { SignIn } from "./pages/sign-in.tsx"; 9 + import { CreateSphere } from "./pages/create-sphere.tsx"; 10 + import { SpherePage } from "./pages/sphere.tsx"; 11 + import type { ModuleRoute } from "@exosphere/client/types"; 12 + import { feedsModule } from "@exosphere/feeds/client"; 13 + import { featureRequestsModule } from "@exosphere/feature-requests/client"; 14 + import { LocationProvider, Router, Route } from "preact-iso"; 15 + 16 + const defaultRoutes: ModuleRoute[] = [feedsModule, featureRequestsModule].flatMap( 17 + (m) => m.routes ?? [], 18 + ); 19 + 20 + function SignInPage() { 21 + const { route } = useLocation(); 22 + const { loading: authLoading, authenticated } = auth.value; 23 + useEffect(() => { 24 + if (!authLoading && authenticated) route("/"); 25 + }, [authLoading, authenticated]); 26 + if (!authLoading && authenticated) return null; 27 + return <SignIn />; 28 + } 29 + 30 + function CreateSphereGuarded() { 31 + const { route } = useLocation(); 32 + const { loading: authLoading, authenticated } = auth.value; 33 + useEffect(() => { 34 + if (!authLoading && !authenticated) route("/sign-in"); 35 + }, [authLoading, authenticated]); 36 + if (!authLoading && !authenticated) return null; 37 + return <CreateSphere />; 38 + } 39 + 40 + function WelcomePage() { 41 + const { error } = sphereState.value; 42 + return ( 43 + <div class={ui.container}> 44 + <div class={ui.section}> 45 + <h1 class={ui.pageTitle}>Welcome to Exosphere</h1> 46 + <p class={ui.description}> 47 + {error === "No sphere configured" 48 + ? "No sphere has been created yet." 49 + : (error ?? "Set up your sphere to get started.")} 50 + </p> 51 + {auth.value.authenticated && ( 52 + <a href="/spheres/new" class={ui.button}> 53 + Create sphere 54 + </a> 55 + )} 56 + </div> 57 + </div> 58 + ); 59 + } 60 + 61 + function MainContent({ moduleRoutes }: { moduleRoutes: ModuleRoute[] }) { 62 + const { pending, data } = sphereState.value; 63 + 64 + // No sphere exists — limited routing 65 + if (!pending && !data) { 66 + return ( 67 + <Router> 68 + <Route path="/sign-in" component={SignInPage} /> 69 + <Route path="/spheres/new" component={CreateSphereGuarded} /> 70 + <Route default component={WelcomePage} /> 71 + </Router> 72 + ); 73 + } 74 + 75 + // Still loading sphere 76 + if (pending) { 77 + return ( 78 + <div class={ui.container}> 79 + <p class={ui.muted}>Loading...</p> 80 + </div> 81 + ); 82 + } 83 + 84 + // Sphere loaded — full routing 85 + return ( 86 + <Router> 87 + <Route path="/sign-in" component={SignInPage} /> 88 + {moduleRoutes.map((r) => ( 89 + <Route key={r.path} path={r.path} component={r.component} /> 90 + ))} 91 + <Route default component={SpherePage} /> 92 + </Router> 93 + ); 94 + } 95 + 96 + function Header() { 97 + const { route } = useLocation(); 98 + const { loading, authenticated, did, handle } = auth.value; 99 + const sphere = sphereState.value.data?.sphere; 100 + 101 + return ( 102 + <header class={ui.header}> 103 + <div class={ui.headerInner}> 104 + <a href="/" class={ui.headerTitle}> 105 + {sphere?.name ?? "Exosphere"} 106 + </a> 107 + <nav class={ui.headerNav}> 108 + {authenticated && ( 109 + <span class={`${ui.muted} ${ui.hiddenMobile}`} title={did ?? undefined}> 110 + {handle ? `@${handle}` : did} 111 + </span> 112 + )} 113 + <ThemeToggle icons={{ light: Sun, dark: Moon, system: Monitor }} /> 114 + {loading ? ( 115 + // optimistically show a dummy "signout button" 116 + // avoid layout flickering 117 + <button class={ui.buttonSecondary} disabled> 118 + Sign out 119 + </button> 120 + ) : authenticated ? ( 121 + <button class={ui.buttonSecondary} onClick={() => logout().then(() => route("/"))}> 122 + Sign out 123 + </button> 124 + ) : ( 125 + <a href="/sign-in" class={ui.button}> 126 + Sign in 127 + </a> 128 + )} 129 + </nav> 130 + </div> 131 + </header> 132 + ); 133 + } 134 + 135 + export function App({ moduleRoutes = defaultRoutes }: { moduleRoutes?: ModuleRoute[] }) { 136 + return ( 137 + <LocationProvider> 138 + <div class={ui.themeRoot}> 139 + <Header /> 140 + <MainContent moduleRoutes={moduleRoutes} /> 141 + </div> 142 + </LocationProvider> 143 + ); 144 + }
+46
packages/app/src/client.tsx
··· 1 + import { hydrate } from "preact-iso"; 2 + import { App } from "./app.tsx"; 3 + import { checkSession, auth } from "@exosphere/client/auth"; 4 + import { loadSphere, refreshSphere, sphereState } from "@exosphere/client/sphere"; 5 + import { ssrPageData } from "@exosphere/client/ssr-data"; 6 + import { initTheme } from "@exosphere/client/theme-state"; 7 + import { lightTheme, darkTheme } from "./app.css.ts"; 8 + 9 + // Initialize browser-only systems 10 + initTheme(lightTheme, darkTheme); 11 + 12 + // Read server-injected data to match SSR output during hydration 13 + const ssrData: import("./entry-server.tsx").SSRData | undefined = (window as any).__SSR_DATA__; 14 + if (ssrData) { 15 + auth.value = { loading: false, authenticated: ssrData.auth.authenticated, did: ssrData.auth.did, handle: ssrData.auth.handle }; 16 + if (ssrData.sphere) { 17 + sphereState.value = { pending: false, loading: false, data: ssrData.sphere, error: null }; 18 + } else { 19 + sphereState.value = { 20 + pending: false, 21 + loading: false, 22 + data: null, 23 + error: ssrData.sphereError ?? null, 24 + }; 25 + } 26 + if (ssrData.pageData) { 27 + ssrPageData.value = ssrData.pageData; 28 + } 29 + } else { 30 + // Fallback: no SSR data (dev without SSR), behave as before 31 + checkSession(); 32 + loadSphere(); 33 + } 34 + 35 + hydrate(<App />, document.getElementById("app")!); 36 + 37 + // SSR page data was only needed for hydration — clear it so client-side 38 + // navigations don't reuse stale prefetched data 39 + ssrPageData.value = null; 40 + 41 + // After hydration, silently refresh data in background to catch changes 42 + // Use refreshSphere (not loadSphere) to avoid resetting state to pending/null 43 + if (ssrData) { 44 + checkSession(); 45 + refreshSphere(); 46 + }
+42
packages/app/src/entry-server.tsx
··· 1 + import prerender, { locationStub } from "preact-iso/prerender"; 2 + import { App } from "./app.tsx"; 3 + import { auth } from "@exosphere/client/auth"; 4 + import { sphereState } from "@exosphere/client/sphere"; 5 + import { ssrPageData } from "@exosphere/client/ssr-data"; 6 + import { lightTheme, darkTheme } from "./app.css.ts"; 7 + import type { SphereData } from "@exosphere/core/types"; 8 + import { feedsModule } from "@exosphere/feeds/client-ssr"; 9 + import { featureRequestsModule } from "@exosphere/feature-requests/client-ssr"; 10 + 11 + const ssrRoutes = [feedsModule, featureRequestsModule].flatMap((m) => m.routes ?? []); 12 + 13 + export interface SSRData { 14 + auth: { authenticated: boolean; did: string | null; handle: string | null }; 15 + sphere: SphereData | null; 16 + sphereError: string | null; 17 + pageData: Record<string, unknown> | null; 18 + } 19 + 20 + export async function render(url: string, data: SSRData) { 21 + // Stub location for preact-iso router 22 + locationStub(url); 23 + 24 + // Set signals before rendering 25 + auth.value = { loading: false, authenticated: data.auth.authenticated, did: data.auth.did, handle: data.auth.handle }; 26 + 27 + if (data.sphere) { 28 + sphereState.value = { pending: false, loading: false, data: data.sphere, error: null }; 29 + } else { 30 + sphereState.value = { pending: false, loading: false, data: null, error: data.sphereError }; 31 + } 32 + 33 + ssrPageData.value = data.pageData; 34 + 35 + const { html } = await prerender(<App moduleRoutes={ssrRoutes} />); 36 + 37 + // Inline script that applies the correct theme class before paint. 38 + // Runs synchronously in <head> so the user never sees the wrong theme. 39 + const themeScript = `<script>(function(){var t=localStorage.getItem("theme")||"system";var d=t==="dark"||(t==="system"&&matchMedia("(prefers-color-scheme:dark)").matches);document.documentElement.className=d?"${darkTheme}":"${lightTheme}"})()</script>`; 40 + 41 + return { html, themeScript, data }; 42 + }
+134
packages/app/src/pages/create-sphere.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { useLocation } from "@exosphere/client/router"; 3 + import { loadSphere } from "@exosphere/client/sphere"; 4 + import * as ui from "@exosphere/client/ui.css"; 5 + import { createSphere } from "../api/spheres.ts"; 6 + 7 + export function CreateSphere() { 8 + const name = useSignal(""); 9 + const slug = useSignal(""); 10 + const description = useSignal(""); 11 + const visibility = useSignal("public"); 12 + const writeAccess = useSignal("open"); 13 + const error = useSignal(""); 14 + const submitting = useSignal(false); 15 + const { route } = useLocation(); 16 + 17 + const submit = async (e: Event) => { 18 + e.preventDefault(); 19 + if (!name.value.trim()) { 20 + error.value = "Name is required."; 21 + return; 22 + } 23 + 24 + submitting.value = true; 25 + error.value = ""; 26 + 27 + const body: Record<string, string> = { 28 + name: name.value.trim(), 29 + visibility: visibility.value, 30 + writeAccess: writeAccess.value, 31 + }; 32 + if (slug.value.trim()) body.slug = slug.value.trim(); 33 + if (description.value.trim()) body.description = description.value.trim(); 34 + 35 + try { 36 + await createSphere(body as Parameters<typeof createSphere>[0]); 37 + await loadSphere(); 38 + route("/"); 39 + } catch (e) { 40 + error.value = e instanceof Error ? e.message : "Network error. Please try again."; 41 + submitting.value = false; 42 + } 43 + }; 44 + 45 + return ( 46 + <div class={`${ui.container} ${ui.stackLg}`}> 47 + <h1 class={ui.pageTitle}>Create a Sphere</h1> 48 + 49 + <form onSubmit={submit} class={ui.formStack}> 50 + <div> 51 + <label class={ui.label} htmlFor="name"> 52 + Name 53 + </label> 54 + <input 55 + id="name" 56 + class={ui.input} 57 + type="text" 58 + placeholder="My Community" 59 + value={name.value} 60 + onInput={(e) => (name.value = (e.target as HTMLInputElement).value)} 61 + /> 62 + </div> 63 + 64 + <div> 65 + <label class={ui.label} htmlFor="slug"> 66 + Slug <span class={ui.labelHint}>(optional — auto-generated from name)</span> 67 + </label> 68 + <input 69 + id="slug" 70 + class={ui.input} 71 + type="text" 72 + placeholder="my-community" 73 + value={slug.value} 74 + onInput={(e) => (slug.value = (e.target as HTMLInputElement).value)} 75 + /> 76 + </div> 77 + 78 + <div> 79 + <label class={ui.label} htmlFor="description"> 80 + Description 81 + </label> 82 + <textarea 83 + id="description" 84 + class={ui.textarea} 85 + placeholder="What is this sphere about?" 86 + value={description.value} 87 + onInput={(e) => (description.value = (e.target as HTMLTextAreaElement).value)} 88 + /> 89 + </div> 90 + 91 + <div> 92 + <label class={ui.label} htmlFor="visibility"> 93 + Visibility 94 + </label> 95 + <select 96 + id="visibility" 97 + class={ui.select} 98 + value={visibility.value} 99 + onChange={(e) => (visibility.value = (e.target as HTMLSelectElement).value)} 100 + > 101 + <option value="public">Public — anyone can view</option> 102 + <option value="private">Private — members only</option> 103 + </select> 104 + </div> 105 + 106 + <div> 107 + <label class={ui.label} htmlFor="writeAccess"> 108 + Write access 109 + </label> 110 + <select 111 + id="writeAccess" 112 + class={ui.select} 113 + value={writeAccess.value} 114 + onChange={(e) => (writeAccess.value = (e.target as HTMLSelectElement).value)} 115 + > 116 + <option value="open">Open — any authenticated user</option> 117 + <option value="members">Members only</option> 118 + </select> 119 + </div> 120 + 121 + {error.value && <p class={ui.errorText}>{error.value}</p>} 122 + 123 + <div class={ui.row}> 124 + <button type="button" class={ui.buttonSecondary} onClick={() => route("/")}> 125 + Cancel 126 + </button> 127 + <button type="submit" class={ui.button} disabled={submitting.value}> 128 + {submitting.value ? "Creating..." : "Create sphere"} 129 + </button> 130 + </div> 131 + </form> 132 + </div> 133 + ); 134 + }
+52
packages/app/src/pages/sign-in.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import * as ui from "@exosphere/client/ui.css"; 3 + 4 + export function SignIn() { 5 + const handle = useSignal(""); 6 + const error = useSignal(""); 7 + 8 + const submit = (e: Event) => { 9 + e.preventDefault(); 10 + const value = handle.value.trim(); 11 + if (!value) { 12 + error.value = "Please enter your handle."; 13 + return; 14 + } 15 + // Full page redirect to initiate OAuth flow 16 + window.location.href = `/api/oauth/login?handle=${encodeURIComponent(value)}`; 17 + }; 18 + 19 + return ( 20 + <div class={ui.container}> 21 + <div class={ui.cardNarrow}> 22 + <h1 class={ui.cardHeading}>Sign in</h1> 23 + <p class={ui.introText}>Use your AT Protocol handle to sign in.</p> 24 + 25 + <form onSubmit={submit} class={ui.formStack}> 26 + <div> 27 + <label class={ui.label} htmlFor="handle"> 28 + Handle 29 + </label> 30 + <input 31 + id="handle" 32 + class={ui.input} 33 + type="text" 34 + placeholder="user.bsky.social" 35 + value={handle.value} 36 + onInput={(e) => { 37 + handle.value = (e.target as HTMLInputElement).value; 38 + error.value = ""; 39 + }} 40 + /> 41 + </div> 42 + 43 + {error.value && <p class={ui.errorText}>{error.value}</p>} 44 + 45 + <button type="submit" class={ui.button}> 46 + Sign in 47 + </button> 48 + </form> 49 + </div> 50 + </div> 51 + ); 52 + }
+129
packages/app/src/pages/sphere.tsx
··· 1 + import { auth } from "@exosphere/client/auth"; 2 + import { sphereState, sphereSlug, refreshSphere } from "@exosphere/client/sphere"; 3 + import { useQuery } from "@exosphere/client/hooks"; 4 + import * as ui from "@exosphere/client/ui.css"; 5 + import { 6 + getSphereModules, 7 + enableModule as apiEnableModule, 8 + disableModule as apiDisableModule, 9 + } from "../api/spheres.ts"; 10 + 11 + /** Map internal module names to user-facing labels (used for display and URL paths). */ 12 + const moduleLabels: Record<string, { label: string; path: string; description: string }> = { 13 + feeds: { 14 + label: "Feeds", 15 + path: "feeds", 16 + description: "Discuss topics and share updates with the community.", 17 + }, 18 + "feature-requests": { 19 + label: "Infuse", 20 + path: "infuse", 21 + description: "Submit, vote on, and track feature requests.", 22 + }, 23 + }; 24 + 25 + export function SpherePage() { 26 + const { data } = sphereState.value; 27 + const slug = sphereSlug.value!; 28 + const modules = useQuery(() => getSphereModules(slug), [slug]); 29 + 30 + const isAdminOrOwner = () => { 31 + if (!auth.value.authenticated || !data) return false; 32 + const role = data.role; 33 + if (role === "owner" || role === "admin") return true; 34 + return data.sphere.ownerDid === auth.value.did; 35 + }; 36 + 37 + const enableModule = async (moduleName: string) => { 38 + await apiEnableModule(slug, moduleName); 39 + modules.refetch(); 40 + refreshSphere(); 41 + }; 42 + 43 + const disableModule = async (moduleName: string) => { 44 + await apiDisableModule(slug, moduleName); 45 + modules.refetch(); 46 + refreshSphere(); 47 + }; 48 + 49 + if (!data) return null; 50 + 51 + const { sphere: s, modules: enabledModules, memberCount } = data; 52 + const enabledNames = enabledModules.map((m) => m.name); 53 + const availableToEnable = modules.data?.available.filter((a) => !enabledNames.includes(a)) ?? []; 54 + 55 + return ( 56 + <div class={ui.container}> 57 + <div class={ui.section}> 58 + <div class={ui.row}> 59 + <h1 class={ui.pageTitle}>{s.name}</h1> 60 + <span class={ui.badge}>{s.visibility}</span> 61 + </div> 62 + 63 + {s.description && <p class={ui.description}>{s.description}</p>} 64 + 65 + <div class={ui.metaRow}> 66 + <span class={ui.muted}> 67 + {memberCount} {memberCount === 1 ? "member" : "members"} 68 + </span> 69 + <span class={ui.muted}>Write: {s.writeAccess}</span> 70 + </div> 71 + </div> 72 + 73 + {/* Modules section */} 74 + <div class={ui.section}> 75 + <h2 class={ui.sectionTitle}>Modules</h2> 76 + 77 + {enabledModules.length === 0 && availableToEnable.length === 0 && ( 78 + <p class={ui.muted}>No modules available.</p> 79 + )} 80 + 81 + {enabledModules.length > 0 && ( 82 + <div class={ui.stackSm}> 83 + {enabledModules.map((mod) => ( 84 + <div class={ui.card} key={mod.name}> 85 + <div class={ui.row}> 86 + <div> 87 + <a href={`/${moduleLabels[mod.name]?.path ?? mod.name}`}> 88 + <strong>{moduleLabels[mod.name]?.label ?? mod.name}</strong> 89 + </a> 90 + <span class={`${ui.muted} ${ui.inlineTag}`}>enabled</span> 91 + </div> 92 + {isAdminOrOwner() && ( 93 + <button class={ui.buttonDanger} onClick={() => disableModule(mod.name)}> 94 + Disable 95 + </button> 96 + )} 97 + </div> 98 + {moduleLabels[mod.name]?.description && ( 99 + <p class={ui.muted}>{moduleLabels[mod.name].description}</p> 100 + )} 101 + </div> 102 + ))} 103 + </div> 104 + )} 105 + 106 + {isAdminOrOwner() && availableToEnable.length > 0 && ( 107 + <div class={ui.stackSm}> 108 + <h3 class={ui.subsectionTitle}>Available modules</h3> 109 + <div class={ui.stackSm}> 110 + {availableToEnable.map((name) => ( 111 + <div class={ui.card} key={name}> 112 + <div class={ui.row}> 113 + <strong>{moduleLabels[name]?.label ?? name}</strong> 114 + <button class={ui.buttonSecondary} onClick={() => enableModule(name)}> 115 + Enable 116 + </button> 117 + </div> 118 + {moduleLabels[name]?.description && ( 119 + <p class={ui.muted}>{moduleLabels[name].description}</p> 120 + )} 121 + </div> 122 + ))} 123 + </div> 124 + </div> 125 + )} 126 + </div> 127 + </div> 128 + ); 129 + }
+186
packages/app/src/server.ts
··· 1 + import { Hono } from "hono"; 2 + import { serveStatic } from "hono/bun"; 3 + import { getCookie } from "hono/cookie"; 4 + import { getOAuthClient, oauthRoutes } from "@exosphere/core/auth"; 5 + import { createSphereRoutes, getCurrentSphere } from "@exosphere/core/sphere"; 6 + import { startJetstream, stopCursorFlushing } from "@exosphere/indexer"; 7 + import { modules, coreIndexer } from "@exosphere/indexer/modules"; 8 + import { createMcpRoutes } from "@exosphere/mcp"; 9 + import { ssrPrefetch } from "./ssr-prefetch.ts"; 10 + 11 + const app = new Hono(); 12 + 13 + // Mount OAuth routes 14 + app.route("/api/oauth", oauthRoutes); 15 + 16 + // Register modules 17 + for (const mod of modules) { 18 + app.route(`/api/${mod.name}`, mod.api); 19 + } 20 + 21 + // Mount Sphere routes (needs the list of available modules for validation) 22 + app.route("/api/spheres", createSphereRoutes(modules.map((m) => m.name))); 23 + 24 + // Start Jetstream consumer (non-blocking, collects indexers from modules + core) 25 + // Set DISABLE_INDEXER=1 to run the API without the built-in indexer 26 + // (e.g. when running the indexer as a standalone service) 27 + if (!process.env.DISABLE_INDEXER) { 28 + startJetstream(modules, coreIndexer); 29 + } 30 + 31 + app.get("/api/health", (c) => c.json({ status: "ok" })); 32 + 33 + // Mount MCP server (Streamable HTTP, stateless mode) 34 + app.route("/mcp", createMcpRoutes((path) => app.request(path))); 35 + 36 + const isProd = process.env.NODE_ENV === "production"; 37 + 38 + if (isProd) { 39 + // Security headers 40 + app.use(async (c, next) => { 41 + await next(); 42 + c.header("X-Content-Type-Options", "nosniff"); 43 + c.header("X-Frame-Options", "DENY"); 44 + c.header("Referrer-Policy", "strict-origin-when-cross-origin"); 45 + c.header("Permissions-Policy", "camera=(), microphone=(), geolocation=()"); 46 + }); 47 + 48 + const root = new URL("..", import.meta.url).pathname; 49 + 50 + // Serve client assets with immutable cache (hashed filenames) 51 + app.use("/assets/*", async (c, next) => { 52 + await next(); 53 + c.header("Cache-Control", "public, max-age=31536000, immutable"); 54 + }); 55 + app.use("/assets/*", serveStatic({ root: `${root}dist/client` })); 56 + 57 + // Serve public root files (favicon, etc.) 58 + app.use("/favicon.svg", serveStatic({ root: `${root}dist/client`, path: "/favicon.svg" })); 59 + 60 + // Load SSR template and render function 61 + // Collect all CSS from the Vite manifest (including lazy chunks) and inject 62 + // them as render-blocking stylesheets before any <script> to prevent FOUC. 63 + const rawTemplate = await Bun.file(`${root}dist/client/index.html`).text(); 64 + const manifest: Record<string, { css?: string[] }> = await Bun.file( 65 + `${root}dist/client/.vite/manifest.json`, 66 + ).json(); 67 + const allCss = new Set<string>(); 68 + for (const entry of Object.values(manifest)) { 69 + for (const css of entry.css ?? []) allCss.add(`/${css}`); 70 + } 71 + const cssLinks = [...allCss].map( 72 + (href) => `<link rel="stylesheet" blocking="render" crossorigin href="${href}">`, 73 + ); 74 + const template = rawTemplate 75 + .replace(/\s*<link rel="stylesheet"[^>]*>/g, "") 76 + .replace(/<script /, `${cssLinks.join("\n ")}\n <script `); 77 + const { render } = await import(`${root}dist/server/entry-server.js`); 78 + 79 + // Catch-all: server-render pages 80 + app.get("*", async (c) => { 81 + try { 82 + // Resolve auth from cookie 83 + let authData = { authenticated: false, did: null as string | null, handle: null as string | null }; 84 + const sid = getCookie(c, "sid"); 85 + if (sid) { 86 + try { 87 + const client = await getOAuthClient(); 88 + const session = await client.restore(sid); 89 + let handle: string | null = null; 90 + try { 91 + const res = await session.fetchHandler( 92 + `/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(session.did)}`, 93 + ); 94 + if (res.ok) { 95 + const repo = (await res.json()) as { handle: string }; 96 + handle = repo.handle; 97 + } 98 + } catch {} 99 + authData = { authenticated: true, did: session.did, handle }; 100 + } catch { 101 + /* invalid session */ 102 + } 103 + } 104 + 105 + // Load sphere data 106 + let sphere; 107 + try { 108 + sphere = getCurrentSphere(authData.did); 109 + } catch { 110 + sphere = null; 111 + } 112 + 113 + // Prefetch page data by calling our own API routes internally 114 + const pageData: Record<string, unknown> = {}; 115 + if (sphere) { 116 + const prefetches = ssrPrefetch(c.req.path); 117 + for (const prefetch of prefetches) { 118 + try { 119 + const res = await app.request(prefetch.apiUrl); 120 + if (res.ok) pageData[prefetch.key] = await res.json(); 121 + } catch { 122 + /* prefetch failed — client will fetch */ 123 + } 124 + } 125 + 126 + // For individual feature requests, also prefetch comments 127 + const frData = pageData["feature-request"] as 128 + | { featureRequest?: { id: string } } 129 + | undefined; 130 + if (frData?.featureRequest?.id) { 131 + try { 132 + const res = await app.request( 133 + `/api/feature-requests/${frData.featureRequest.id}/comments`, 134 + ); 135 + if (res.ok) pageData["feature-request-comments"] = await res.json(); 136 + } catch { 137 + /* prefetch failed — client will fetch */ 138 + } 139 + } 140 + } 141 + 142 + const ssrData = { 143 + auth: authData, 144 + sphere, 145 + sphereError: sphere ? null : "No sphere configured", 146 + pageData, 147 + }; 148 + 149 + const { html, themeScript, data } = await render(c.req.path, ssrData); 150 + 151 + const page = template 152 + .replace("</head>", `${themeScript}\n</head>`) 153 + .replace( 154 + '<div id="app"></div>', 155 + `<div id="app">${html}</div>\n<script>window.__SSR_DATA__=${JSON.stringify(data).replace(/</g, "\\u003c")}</script>`, 156 + ); 157 + 158 + const isAuthenticated = Boolean(sid && authData.authenticated); 159 + c.header("Vary", "Cookie"); 160 + if (isAuthenticated) { 161 + c.header("Cache-Control", "private, no-cache"); 162 + } else { 163 + c.header("Cache-Control", "public, s-maxage=60, stale-while-revalidate=300"); 164 + } 165 + 166 + return c.html(page); 167 + } catch { 168 + // SSR failed — fall back to client-side rendering 169 + return c.html(template); 170 + } 171 + }); 172 + } 173 + 174 + // Graceful shutdown for Railway restarts 175 + function shutdown() { 176 + console.log("[server] Shutting down..."); 177 + stopCursorFlushing(); 178 + process.exit(0); 179 + } 180 + process.on("SIGTERM", shutdown); 181 + process.on("SIGINT", shutdown); 182 + 183 + export default { 184 + port: Number(process.env.PORT) || 3001, 185 + fetch: app.fetch, 186 + };
+19
packages/app/src/ssr-prefetch.ts
··· 1 + /** Map a page path to prefetch descriptors for SSR data loading. */ 2 + export function ssrPrefetch(path: string): { key: string; apiUrl: string }[] { 3 + if (path === "/infuse") 4 + return [ 5 + { 6 + key: "feature-requests", 7 + apiUrl: "/api/feature-requests?status=requested,approved,in-progress", 8 + }, 9 + ]; 10 + if (path === "/infuse/done") 11 + return [{ key: "feature-requests-done", apiUrl: "/api/feature-requests?status=done" }]; 12 + if (path === "/infuse/not-planned") 13 + return [ 14 + { key: "feature-requests-not-planned", apiUrl: "/api/feature-requests?status=not-planned" }, 15 + ]; 16 + const frMatch = path.match(/^\/infuse\/(\d+)$/); 17 + if (frMatch) return [{ key: "feature-request", apiUrl: `/api/feature-requests/${frMatch[1]}` }]; 18 + return []; 19 + }
+163
packages/app/src/vite-ssr-plugin.ts
··· 1 + import type { Plugin, ViteDevServer, ModuleNode } from "vite"; 2 + import fs from "node:fs"; 3 + import path from "node:path"; 4 + import { ssrPrefetch } from "./ssr-prefetch.ts"; 5 + 6 + const API_SERVER = "http://localhost:3001"; 7 + 8 + /** 9 + * Walk the Vite module graph from an SSR entry and collect all CSS 10 + * module URLs (regular .css, vanilla-extract virtual CSS, etc.). 11 + * Then transform each one via the client pipeline and extract the 12 + * raw CSS from the JS wrapper so we can inline it as <style> tags. 13 + */ 14 + async function collectSsrCss(server: ViteDevServer, entryUrl: string): Promise<string> { 15 + const cssUrls = new Set<string>(); 16 + const visited = new Set<string>(); 17 + 18 + function walk(mod: ModuleNode | undefined) { 19 + if (!mod || visited.has(mod.url)) return; 20 + visited.add(mod.url); 21 + if (/\.css($|\?)/.test(mod.url)) { 22 + cssUrls.add(mod.url); 23 + } 24 + for (const dep of mod.ssrImportedModules) { 25 + walk(dep); 26 + } 27 + } 28 + 29 + const resolved = await server.moduleGraph.resolveUrl(entryUrl); 30 + walk(server.moduleGraph.getModuleById(resolved[1])); 31 + 32 + const styles: string[] = []; 33 + for (const url of cssUrls) { 34 + try { 35 + const result = await server.transformRequest(url); 36 + if (!result?.code) continue; 37 + // Vite wraps CSS in a JS module with: const __vite__css = "...css..." 38 + const match = result.code.match(/__vite__css\s*=\s*("[\s\S]*?")\s*[\n;]/); 39 + if (match?.[1]) { 40 + styles.push(JSON.parse(match[1])); 41 + } 42 + } catch { 43 + // skip modules that fail to transform 44 + } 45 + } 46 + 47 + return styles.map((css) => `<style data-vite-dev-css>${css}</style>`).join("\n"); 48 + } 49 + 50 + export function ssrDevPlugin(): Plugin { 51 + return { 52 + name: "exosphere-ssr", 53 + configureServer(server) { 54 + // Register as pre-middleware so it runs BEFORE Vite's SPA fallback 55 + // (which would otherwise serve index.html for all page requests). 56 + server.middlewares.use(async (req, res, next) => { 57 + const url = req.url ?? "/"; 58 + 59 + // Only handle page navigations — let Vite handle everything else 60 + const accept = req.headers.accept ?? ""; 61 + if ( 62 + !accept.includes("text/html") || 63 + url.startsWith("/api") || 64 + url.startsWith("/@") || 65 + url.startsWith("/oauth") || 66 + url.startsWith("/.well-known") || 67 + url.startsWith("/xrpc") || 68 + url.startsWith("/node_modules") || 69 + url.startsWith("/src") || 70 + url.includes(".") 71 + ) { 72 + return next(); 73 + } 74 + 75 + try { 76 + const cookie = req.headers.cookie ?? ""; 77 + 78 + // Fetch auth and sphere data from the Hono API server 79 + let authData: { authenticated: boolean; did?: string } = { authenticated: false }; 80 + let sphereData: unknown = null; 81 + try { 82 + const [authRes, sphereRes] = await Promise.all([ 83 + fetch(`${API_SERVER}/api/oauth/session`, { headers: { cookie } }), 84 + fetch(`${API_SERVER}/api/spheres/current`, { headers: { cookie } }), 85 + ]); 86 + authData = await authRes.json(); 87 + sphereData = sphereRes.ok ? await sphereRes.json() : null; 88 + } catch { 89 + // API server not running — render with empty data 90 + } 91 + 92 + // Read and transform the index.html template 93 + let template = fs.readFileSync(path.resolve(server.config.root, "index.html"), "utf-8"); 94 + template = await server.transformIndexHtml(url, template); 95 + 96 + // Load the SSR entry via Vite's module graph (supports HMR) 97 + const { render } = await server.ssrLoadModule("/src/entry-server.tsx"); 98 + 99 + // Prefetch page data from the API server 100 + const pageData: Record<string, unknown> = {}; 101 + if (sphereData) { 102 + const prefetches = ssrPrefetch(url); 103 + for (const prefetch of prefetches) { 104 + try { 105 + const res = await fetch(`${API_SERVER}${prefetch.apiUrl}`, { headers: { cookie } }); 106 + if (res.ok) pageData[prefetch.key] = await res.json(); 107 + } catch { 108 + /* prefetch failed — client will fetch */ 109 + } 110 + } 111 + 112 + // For individual feature requests, also prefetch comments 113 + const frData = pageData["feature-request"] as 114 + | { featureRequest?: { id: string } } 115 + | undefined; 116 + if (frData?.featureRequest?.id) { 117 + try { 118 + const res = await fetch( 119 + `${API_SERVER}/api/feature-requests/${frData.featureRequest.id}/comments`, 120 + { headers: { cookie } }, 121 + ); 122 + if (res.ok) pageData["feature-request-comments"] = await res.json(); 123 + } catch { 124 + /* prefetch failed — client will fetch */ 125 + } 126 + } 127 + } 128 + 129 + const ssrData = { 130 + auth: { 131 + authenticated: authData.authenticated, 132 + did: authData.did ?? null, 133 + }, 134 + sphere: sphereData, 135 + sphereError: sphereData ? null : "No sphere configured", 136 + pageData, 137 + }; 138 + 139 + const { html, themeScript, data } = await render(url, ssrData); 140 + 141 + // Collect CSS from the module graph and inline it to prevent FOUC. 142 + // In dev, CSS is normally loaded as JS modules that inject <style> tags, 143 + // which causes a flash of unstyled content when SSR HTML arrives first. 144 + const devCss = await collectSsrCss(server, "/src/entry-server.tsx"); 145 + 146 + const page = template 147 + .replace("</head>", `${devCss}\n${themeScript}\n</head>`) 148 + .replace( 149 + '<div id="app"></div>', 150 + `<div id="app">${html}</div>\n<script>window.__SSR_DATA__=${JSON.stringify(data).replace(/</g, "\\u003c")}</script>`, 151 + ); 152 + 153 + res.statusCode = 200; 154 + res.setHeader("Content-Type", "text/html"); 155 + res.end(page); 156 + } catch (e) { 157 + server.ssrFixStacktrace(e as Error); 158 + next(e); 159 + } 160 + }); 161 + }, 162 + }; 163 + }
+4
packages/app/test-results/.last-run.json
··· 1 + { 2 + "status": "passed", 3 + "failedTests": [] 4 + }
+4
packages/app/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+136
packages/app/vite.config.ts
··· 1 + import { defineConfig } from "vite"; 2 + import preact from "@preact/preset-vite"; 3 + import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; 4 + import { ssrDevPlugin } from "./src/vite-ssr-plugin.ts"; 5 + 6 + const PDS_TARGET = "http://localhost:3000"; 7 + 8 + // Fetch the PDS issuer URL so we can rewrite it in proxied responses. 9 + // The PDS thinks it's at e.g. https://pds.dev, but we proxy it through the app. 10 + async function getPdsIssuer(): Promise<string | null> { 11 + try { 12 + const res = await fetch(`${PDS_TARGET}/.well-known/oauth-authorization-server`); 13 + if (!res.ok) return null; 14 + const data = (await res.json()) as { issuer?: string }; 15 + return data.issuer ?? null; 16 + } catch { 17 + return null; 18 + } 19 + } 20 + 21 + export default defineConfig(async () => { 22 + const pdsIssuer = await getPdsIssuer(); 23 + 24 + const pdsProxy: import("vite").ProxyOptions | undefined = pdsIssuer 25 + ? { 26 + target: PDS_TARGET, 27 + changeOrigin: true, 28 + selfHandleResponse: true, 29 + configure(proxy) { 30 + const issuerUrl = new URL(pdsIssuer); 31 + const appOrigin = "http://127.0.0.1:5173"; 32 + 33 + // Rewrite request headers so the PDS sees same-origin 34 + proxy.on("proxyReq", (proxyReq, req) => { 35 + proxyReq.removeHeader("origin"); 36 + proxyReq.setHeader("sec-fetch-site", "same-origin"); 37 + // Prevent compressed responses — we need plain text for URL rewriting 38 + proxyReq.removeHeader("accept-encoding"); 39 + const referer = req.headers.referer; 40 + if (referer) { 41 + try { 42 + const url = new URL(referer); 43 + url.protocol = issuerUrl.protocol; 44 + url.hostname = issuerUrl.hostname; 45 + url.port = issuerUrl.port; 46 + proxyReq.setHeader("referer", url.href); 47 + } catch {} 48 + } 49 + }); 50 + 51 + // Rewrite ALL responses: Location headers AND body content. 52 + // The PDS OAuth SPA receives JSON with redirect URLs pointing to 53 + // https://pds.dev — the browser would navigate there (→ GitHub). 54 + proxy.on("proxyRes", (proxyRes, _req, res) => { 55 + // Rewrite Location header 56 + const location = proxyRes.headers.location; 57 + if (location) { 58 + proxyRes.headers.location = location.replaceAll(pdsIssuer, appOrigin); 59 + } 60 + 61 + // Strip HTTPS-only headers — the PDS expects HTTPS (pds.dev) 62 + // but we're proxying over HTTP in dev. 63 + // - upgrade-insecure-requests in CSP upgrades all subresource 64 + // loads to HTTPS, which fail on the HTTP dev server. 65 + // - HSTS would cause the browser to refuse HTTP connections. 66 + const csp = proxyRes.headers["content-security-policy"]; 67 + if (csp) { 68 + const strip = (s: string) => s.replace(/upgrade-insecure-requests\s*;?\s*/g, ""); 69 + proxyRes.headers["content-security-policy"] = Array.isArray(csp) 70 + ? csp.map(strip) 71 + : strip(csp); 72 + } 73 + delete proxyRes.headers["strict-transport-security"]; 74 + 75 + // Strip Secure flag from cookies so they work over HTTP 76 + const cookies = proxyRes.headers["set-cookie"]; 77 + if (cookies) { 78 + proxyRes.headers["set-cookie"] = (Array.isArray(cookies) ? cookies : [cookies]).map( 79 + (c) => c.replace(/;\s*Secure/gi, ""), 80 + ); 81 + } 82 + 83 + // Check if this is a text response that might contain PDS URLs 84 + const contentType = proxyRes.headers["content-type"] ?? ""; 85 + const isText = 86 + contentType.includes("json") || 87 + contentType.includes("html") || 88 + contentType.includes("javascript"); 89 + 90 + if (!isText) { 91 + // Non-text: copy headers as-is and pipe through 92 + res.writeHead(proxyRes.statusCode!, proxyRes.headers); 93 + proxyRes.pipe(res); 94 + return; 95 + } 96 + 97 + // Buffer text responses and rewrite PDS URLs. 98 + // Remove content-length since rewriting changes the body size. 99 + const headers = { ...proxyRes.headers }; 100 + delete headers["content-length"]; 101 + delete headers["content-encoding"]; 102 + res.writeHead(proxyRes.statusCode!, headers); 103 + 104 + const chunks: Buffer[] = []; 105 + proxyRes.on("data", (chunk) => chunks.push(chunk)); 106 + proxyRes.on("end", () => { 107 + const body = Buffer.concat(chunks).toString("utf-8"); 108 + const rewritten = body.replaceAll(pdsIssuer, appOrigin); 109 + res.end(rewritten); 110 + }); 111 + }); 112 + }, 113 + } 114 + : undefined; 115 + 116 + return { 117 + build: { manifest: true }, 118 + plugins: [preact(), vanillaExtractPlugin(), ssrDevPlugin()], 119 + ssr: { 120 + noExternal: ["@exosphere/client", "@exosphere/feeds", "@exosphere/feature-requests"], 121 + }, 122 + server: { 123 + host: true, 124 + port: 5173, 125 + proxy: { 126 + "/api": "http://localhost:3001", 127 + ...(pdsProxy && { 128 + "/oauth": pdsProxy, 129 + "/@atproto": pdsProxy, 130 + "/.well-known": pdsProxy, 131 + "/xrpc": pdsProxy, 132 + }), 133 + }, 134 + }, 135 + }; 136 + });
+26
packages/client/package.json
··· 1 + { 2 + "name": "@exosphere/client", 3 + "private": true, 4 + "type": "module", 5 + "exports": { 6 + "./api": "./src/api.ts", 7 + "./auth": "./src/auth.ts", 8 + "./router": "./src/router.tsx", 9 + "./sphere": "./src/sphere.ts", 10 + "./hooks": "./src/hooks.ts", 11 + "./ssr-data": "./src/ssr-data.ts", 12 + "./theme.css": "./src/theme.css.ts", 13 + "./ui.css": "./src/ui.css.ts", 14 + "./types": "./src/types.ts", 15 + "./format": "./src/format.ts", 16 + "./theme-state": "./src/theme-state.ts", 17 + "./components/collapsible-section": "./src/components/collapsible-section.tsx", 18 + "./components/theme-toggle": "./src/components/theme-toggle.tsx" 19 + }, 20 + "peerDependencies": { 21 + "@preact/signals": "^2.0.0", 22 + "@vanilla-extract/css": "^1.0.0", 23 + "preact": "^10.25.0", 24 + "preact-iso": "^2.0.0" 25 + } 26 + }
+20
packages/client/src/api.ts
··· 1 + export class ApiError extends Error { 2 + constructor( 3 + public status: number, 4 + message: string, 5 + ) { 6 + super(message); 7 + } 8 + } 9 + 10 + export async function apiFetch<T>(url: string, options?: RequestInit): Promise<T> { 11 + const res = await fetch(url, options); 12 + const data = await res.json(); 13 + if (!res.ok) { 14 + throw new ApiError( 15 + res.status, 16 + typeof data.error === "string" ? data.error : "Something went wrong.", 17 + ); 18 + } 19 + return data as T; 20 + }
+35
packages/client/src/auth.ts
··· 1 + import { signal } from "@preact/signals"; 2 + 3 + interface AuthState { 4 + loading: boolean; 5 + authenticated: boolean; 6 + did: string | null; 7 + handle: string | null; 8 + } 9 + 10 + export const auth = signal<AuthState>({ 11 + loading: true, 12 + authenticated: false, 13 + did: null, 14 + handle: null, 15 + }); 16 + 17 + export async function checkSession() { 18 + try { 19 + const res = await fetch("/api/oauth/session"); 20 + const data = await res.json(); 21 + auth.value = { 22 + loading: false, 23 + authenticated: data.authenticated, 24 + did: data.did ?? null, 25 + handle: data.handle ?? null, 26 + }; 27 + } catch { 28 + auth.value = { loading: false, authenticated: false, did: null, handle: null }; 29 + } 30 + } 31 + 32 + export async function logout() { 33 + await fetch("/api/oauth/logout", { method: "POST" }); 34 + auth.value = { loading: false, authenticated: false, did: null, handle: null }; 35 + }
+33
packages/client/src/components/collapsible-section.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import type { ComponentChildren } from "preact"; 3 + import * as ui from "../ui.css.ts"; 4 + 5 + export function CollapsibleSection({ 6 + title, 7 + defaultExpanded = false, 8 + onToggle, 9 + children, 10 + }: { 11 + title: ComponentChildren; 12 + defaultExpanded?: boolean; 13 + onToggle?: (expanded: boolean) => void; 14 + children: ComponentChildren; 15 + }) { 16 + const expanded = useSignal(defaultExpanded); 17 + const chevronClasses = `${ui.chevron}${expanded.value ? ` ${ui.chevronExpanded}` : ""}`; 18 + 19 + const handleToggle = () => { 20 + expanded.value = !expanded.value; 21 + onToggle?.(expanded.value); 22 + }; 23 + 24 + return ( 25 + <div class={ui.stack}> 26 + <button type="button" class={ui.collapsibleHeading} onClick={handleToggle}> 27 + <span class={chevronClasses}>{"\u25BC"}</span> 28 + {title} 29 + </button> 30 + {expanded.value && children} 31 + </div> 32 + ); 33 + }
+41
packages/client/src/components/theme-toggle.tsx
··· 1 + import type { ComponentType } from "preact"; 2 + import { theme, setTheme } from "../theme-state.ts"; 3 + import * as ui from "@exosphere/client/ui.css"; 4 + 5 + type IconComponent = ComponentType<{ size: string | number }>; 6 + 7 + interface ThemeToggleProps { 8 + icons: Record<"light" | "dark" | "system", IconComponent>; 9 + } 10 + 11 + const themeOptions = ["light", "dark", "system"] as const; 12 + const labels: Record<(typeof themeOptions)[number], string> = { 13 + light: "Light", 14 + dark: "Dark", 15 + system: "System", 16 + }; 17 + 18 + export function ThemeToggle({ icons }: ThemeToggleProps) { 19 + const current = theme.value; 20 + 21 + return ( 22 + <div class={ui.themeToggle} role="radiogroup" aria-label="Theme"> 23 + {themeOptions.map((value) => { 24 + const Icon = icons[value]; 25 + return ( 26 + <button 27 + key={value} 28 + class={`${ui.themeToggleBtn} ${current === value ? ui.themeToggleBtnActive : ""}`} 29 + onClick={() => setTheme(value)} 30 + role="radio" 31 + aria-checked={current === value} 32 + aria-label={labels[value]} 33 + title={labels[value]} 34 + > 35 + <Icon size={16} /> 36 + </button> 37 + ); 38 + })} 39 + </div> 40 + ); 41 + }
+8
packages/client/src/format.ts
··· 1 + const defaultOptions: Intl.DateTimeFormatOptions = { 2 + month: "short", 3 + day: "numeric", 4 + }; 5 + 6 + export function formatDate(iso: string, opts?: Intl.DateTimeFormatOptions): string { 7 + return new Date(iso + "Z").toLocaleDateString(undefined, opts ?? defaultOptions); 8 + }
+67
packages/client/src/hooks.ts
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { useEffect } from "preact/hooks"; 3 + 4 + interface QueryResult<T> { 5 + data: T | null; 6 + pending: boolean; 7 + loading: boolean; 8 + error: string; 9 + refetch: () => void; 10 + } 11 + 12 + const LOADING_DELAY = 400; 13 + 14 + export function useQuery<T>( 15 + fetcher: () => Promise<T>, 16 + deps: unknown[] = [], 17 + options?: { initialData?: T }, 18 + ): QueryResult<T> { 19 + const hasInitial = options?.initialData !== undefined; 20 + const data = useSignal<T | null>(options?.initialData ?? null); 21 + const pending = useSignal(!hasInitial); 22 + const loading = useSignal(false); 23 + const error = useSignal(""); 24 + const skipNext = useSignal(hasInitial); 25 + 26 + const run = () => { 27 + if (skipNext.value) { 28 + skipNext.value = false; 29 + return; 30 + } 31 + pending.value = true; 32 + error.value = ""; 33 + const timer = setTimeout(() => { 34 + loading.value = true; 35 + }, LOADING_DELAY); 36 + fetcher() 37 + .then((d) => { 38 + data.value = d; 39 + }) 40 + .catch((e) => { 41 + error.value = e.message || "Something went wrong."; 42 + }) 43 + .finally(() => { 44 + clearTimeout(timer); 45 + pending.value = false; 46 + loading.value = false; 47 + }); 48 + }; 49 + 50 + useEffect(run, deps); 51 + 52 + return { 53 + get data() { 54 + return data.value; 55 + }, 56 + get pending() { 57 + return pending.value; 58 + }, 59 + get loading() { 60 + return loading.value; 61 + }, 62 + get error() { 63 + return error.value; 64 + }, 65 + refetch: run, 66 + }; 67 + }
+4
packages/client/src/router.tsx
··· 1 + export { useLocation, useRoute } from "preact-iso"; 2 + // Re-exported from preact-iso/lazy (not preact/compat) because it installs 3 + // the options.__e patch that preact-iso's Router needs to handle suspense. 4 + export { default as lazy } from "preact-iso/lazy";
+54
packages/client/src/sphere.ts
··· 1 + import { signal, computed } from "@preact/signals"; 2 + import type { SphereData } from "@exosphere/core/types"; 3 + import { apiFetch } from "./api.ts"; 4 + 5 + const LOADING_DELAY = 400; 6 + 7 + interface SphereState { 8 + pending: boolean; 9 + loading: boolean; 10 + data: SphereData | null; 11 + error: string | null; 12 + } 13 + 14 + export const sphereState = signal<SphereState>({ 15 + pending: true, 16 + loading: false, 17 + data: null, 18 + error: null, 19 + }); 20 + 21 + export const sphereSlug = computed(() => sphereState.value.data?.sphere.slug ?? null); 22 + 23 + export async function loadSphere() { 24 + sphereState.value = { pending: true, loading: false, data: null, error: null }; 25 + const timer = setTimeout(() => { 26 + sphereState.value = { ...sphereState.value, loading: true }; 27 + }, LOADING_DELAY); 28 + try { 29 + const data = await apiFetch<SphereData>("/api/spheres/current"); 30 + clearTimeout(timer); 31 + sphereState.value = { pending: false, loading: false, data, error: null }; 32 + } catch (err) { 33 + clearTimeout(timer); 34 + sphereState.value = { 35 + pending: false, 36 + loading: false, 37 + data: null, 38 + error: err instanceof Error ? err.message : "Failed to load sphere", 39 + }; 40 + } 41 + } 42 + 43 + /** Silent refresh — keeps existing data visible while fetching. */ 44 + export async function refreshSphere() { 45 + try { 46 + const data = await apiFetch<SphereData>("/api/spheres/current"); 47 + sphereState.value = { pending: false, loading: false, data, error: null }; 48 + } catch (err) { 49 + sphereState.value = { 50 + ...sphereState.value, 51 + error: err instanceof Error ? err.message : "Failed to load sphere", 52 + }; 53 + } 54 + }
+4
packages/client/src/ssr-data.ts
··· 1 + import { signal } from "@preact/signals"; 2 + 3 + /** Holds server-prefetched page data, keyed by identifier. */ 4 + export const ssrPageData = signal<Record<string, unknown> | null>(null);
+50
packages/client/src/theme-state.ts
··· 1 + import { signal, effect } from "@preact/signals"; 2 + 3 + type Theme = "light" | "dark" | "system"; 4 + 5 + let lightClass = ""; 6 + let darkClass = ""; 7 + let initialized = false; 8 + 9 + export const theme = signal<Theme>("system"); 10 + const osPrefersDark = signal( 11 + typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false, 12 + ); 13 + 14 + function resolvedTheme(): "light" | "dark" { 15 + if (theme.value !== "system") return theme.value; 16 + return osPrefersDark.value ? "dark" : "light"; 17 + } 18 + 19 + export function themeClass() { 20 + return resolvedTheme() === "dark" ? darkClass : lightClass; 21 + } 22 + 23 + export function setTheme(value: Theme) { 24 + theme.value = value; 25 + } 26 + 27 + export function initTheme(light: string, dark: string) { 28 + lightClass = light; 29 + darkClass = dark; 30 + 31 + if (initialized) return; 32 + initialized = true; 33 + 34 + const stored = localStorage.getItem("theme") as Theme | null; 35 + if (stored) theme.value = stored; 36 + 37 + effect(() => { 38 + localStorage.setItem("theme", theme.value); 39 + 40 + // Sync theme class onto <html> so CSS vars are available globally 41 + const root = document.documentElement; 42 + root.classList.remove(lightClass, darkClass); 43 + root.classList.add(themeClass()); 44 + }); 45 + 46 + // Listen for OS theme changes — updates the signal so the effect re-runs 47 + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => { 48 + osPrefersDark.value = e.matches; 49 + }); 50 + }
+41
packages/client/src/theme.css.ts
··· 1 + import { createThemeContract } from "@vanilla-extract/css"; 2 + 3 + export const vars = createThemeContract({ 4 + color: { 5 + text: null, 6 + textMuted: null, 7 + bg: null, 8 + surface: null, 9 + surfaceHover: null, 10 + border: null, 11 + primary: null, 12 + primaryHover: null, 13 + primaryLight: null, 14 + danger: null, 15 + dangerLight: null, 16 + warning: null, 17 + warningLight: null, 18 + success: null, 19 + successLight: null, 20 + shadow: null, 21 + shadowStrong: null, 22 + focusRing: null, 23 + }, 24 + space: { 25 + xs: null, 26 + sm: null, 27 + md: null, 28 + lg: null, 29 + xl: null, 30 + xxl: null, 31 + }, 32 + radius: { 33 + sm: null, 34 + md: null, 35 + lg: null, 36 + }, 37 + font: { 38 + body: null, 39 + heading: null, 40 + }, 41 + });
+13
packages/client/src/types.ts
··· 1 + import type { ComponentType } from "preact"; 2 + 3 + export interface ModuleRoute { 4 + /** URL path: "/infuse" or "/infuse/:number" */ 5 + path: string; 6 + component: ComponentType; 7 + } 8 + 9 + /** Client-side module definition — name + routes only, no backend deps */ 10 + export interface ClientModule { 11 + name: string; 12 + routes?: ModuleRoute[]; 13 + }
+587
packages/client/src/ui.css.ts
··· 1 + import { globalStyle, style } from "@vanilla-extract/css"; 2 + import { vars } from "./theme.css.ts"; 3 + 4 + globalStyle("html, body", { 5 + backgroundColor: vars.color.surface, 6 + margin: 0, 7 + }); 8 + 9 + // Mobile-first breakpoints (min-width) 10 + const bp = { 11 + sm: "screen and (min-width: 480px)", 12 + md: "screen and (min-width: 768px)", 13 + }; 14 + 15 + // ---- Root ---- 16 + 17 + export const themeRoot = style({ 18 + minHeight: "100vh", 19 + fontFamily: vars.font.body, 20 + lineHeight: 1.6, 21 + color: vars.color.text, 22 + backgroundColor: vars.color.bg, 23 + transition: "background-color 0.2s, color 0.2s", 24 + }); 25 + 26 + // ---- Layout ---- 27 + 28 + export const container = style({ 29 + maxWidth: "640px", 30 + marginInline: "auto", 31 + paddingInline: vars.space.md, 32 + paddingBlockEnd: vars.space.xl, 33 + }); 34 + 35 + export const stack = style({ 36 + display: "flex", 37 + flexDirection: "column", 38 + gap: vars.space.md, 39 + }); 40 + 41 + export const stackSm = style({ 42 + display: "flex", 43 + flexDirection: "column", 44 + gap: vars.space.sm, 45 + }); 46 + 47 + export const stackLg = style({ 48 + display: "flex", 49 + flexDirection: "column", 50 + gap: vars.space.lg, 51 + }); 52 + 53 + export const cluster = style({ 54 + display: "flex", 55 + flexWrap: "wrap", 56 + gap: vars.space.sm, 57 + }); 58 + 59 + export const row = style({ 60 + display: "flex", 61 + alignItems: "center", 62 + justifyContent: "space-between", 63 + }); 64 + 65 + export const section = style({ 66 + marginBlockStart: vars.space.lg, 67 + display: "flex", 68 + flexDirection: "column", 69 + gap: vars.space.md, 70 + }); 71 + 72 + // ---- Header ---- 73 + 74 + export const header = style({ 75 + borderBlockEnd: `1px solid ${vars.color.border}`, 76 + backgroundColor: vars.color.surface, 77 + paddingBlock: vars.space.sm, 78 + marginBlockEnd: vars.space.lg, 79 + boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 80 + transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 81 + "@media": { 82 + [bp.sm]: { 83 + paddingBlock: vars.space.md, 84 + marginBlockEnd: vars.space.xl, 85 + }, 86 + }, 87 + }); 88 + 89 + export const headerInner = style({ 90 + marginInline: "auto", 91 + paddingInline: vars.space.lg, 92 + display: "flex", 93 + alignItems: "center", 94 + justifyContent: "space-between", 95 + }); 96 + 97 + export const headerTitle = style({ 98 + fontFamily: vars.font.heading, 99 + fontWeight: 700, 100 + fontSize: "1rem", 101 + color: vars.color.text, 102 + letterSpacing: "-0.01em", 103 + ":hover": { textDecoration: "none" }, 104 + "@media": { 105 + [bp.sm]: { 106 + fontSize: "1.125rem", 107 + }, 108 + }, 109 + }); 110 + 111 + export const headerNav = style({ 112 + display: "flex", 113 + alignItems: "center", 114 + gap: vars.space.sm, 115 + "@media": { 116 + [bp.sm]: { 117 + gap: vars.space.md, 118 + }, 119 + }, 120 + }); 121 + 122 + // ---- Cards ---- 123 + 124 + export const card = style({ 125 + backgroundColor: vars.color.surface, 126 + border: `1px solid ${vars.color.border}`, 127 + borderRadius: vars.radius.lg, 128 + paddingBlock: vars.space.md, 129 + paddingInline: vars.space.lg, 130 + boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 131 + transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 132 + ":hover": { 133 + backgroundColor: vars.color.surfaceHover, 134 + }, 135 + "@media": { 136 + [bp.sm]: { 137 + paddingBlock: vars.space.lg, 138 + paddingInline: vars.space.xl, 139 + }, 140 + }, 141 + }); 142 + 143 + export const cardLink = style({ 144 + display: "flex", 145 + flexDirection: "column", 146 + gap: vars.space.xs, 147 + textDecoration: "none", 148 + color: "inherit", 149 + backgroundColor: vars.color.surface, 150 + border: `1px solid ${vars.color.border}`, 151 + borderRadius: vars.radius.lg, 152 + paddingBlock: vars.space.md, 153 + paddingInline: vars.space.lg, 154 + boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 155 + transition: "border-color 0.15s, box-shadow 0.2s, background-color 0.2s, transform 0.15s", 156 + ":hover": { 157 + backgroundColor: vars.color.surfaceHover, 158 + borderColor: vars.color.primary, 159 + boxShadow: `0 4px 12px ${vars.color.shadowStrong}, 0 2px 4px ${vars.color.shadow}`, 160 + transform: "translateY(-1px)", 161 + textDecoration: "none", 162 + }, 163 + "@media": { 164 + [bp.sm]: { 165 + paddingBlock: vars.space.lg, 166 + paddingInline: vars.space.xl, 167 + }, 168 + }, 169 + }); 170 + 171 + export const cardNarrow = style({ 172 + backgroundColor: vars.color.surface, 173 + border: `1px solid ${vars.color.border}`, 174 + borderRadius: vars.radius.lg, 175 + paddingBlock: vars.space.lg, 176 + paddingInline: vars.space.xl, 177 + maxWidth: "400px", 178 + marginInline: "auto", 179 + boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 180 + transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 181 + "@media": { 182 + [bp.sm]: { 183 + paddingBlock: vars.space.xl, 184 + paddingInline: vars.space.xxl, 185 + }, 186 + }, 187 + }); 188 + 189 + export const cardFlat = style({ 190 + borderBlockStart: `1px solid ${vars.color.border}`, 191 + paddingBlock: vars.space.sm, 192 + "@media": { 193 + [bp.sm]: { 194 + paddingBlock: vars.space.md, 195 + }, 196 + }, 197 + }); 198 + 199 + // ---- Buttons ---- 200 + 201 + const btnBase = { 202 + boxSizing: "border-box" as const, 203 + display: "inline-flex" as const, 204 + alignItems: "center" as const, 205 + justifyContent: "center" as const, 206 + borderRadius: vars.radius.sm, 207 + fontWeight: 500, 208 + lineHeight: 1.5, 209 + cursor: "pointer", 210 + fontFamily: vars.font.body, 211 + minBlockSize: "44px", 212 + whiteSpace: "nowrap" as const, 213 + transition: "background-color 0.15s, opacity 0.15s, box-shadow 0.15s, transform 0.1s", 214 + }; 215 + 216 + export const button = style({ 217 + ...btnBase, 218 + paddingBlock: vars.space.sm, 219 + paddingInline: vars.space.lg, 220 + border: "none", 221 + fontSize: "0.875rem", 222 + backgroundColor: vars.color.primary, 223 + color: "#fff", 224 + boxShadow: `0 1px 2px ${vars.color.shadow}`, 225 + ":hover": { 226 + backgroundColor: vars.color.primaryHover, 227 + textDecoration: "none", 228 + boxShadow: `0 2px 6px ${vars.color.shadowStrong}`, 229 + }, 230 + ":disabled": { opacity: 0.5, cursor: "not-allowed" }, 231 + ":active": { transform: "scale(0.98)" }, 232 + }); 233 + 234 + export const buttonSecondary = style({ 235 + ...btnBase, 236 + paddingBlock: vars.space.sm, 237 + paddingInline: vars.space.lg, 238 + border: `1px solid ${vars.color.border}`, 239 + fontSize: "0.875rem", 240 + backgroundColor: vars.color.surface, 241 + color: vars.color.text, 242 + ":hover": { 243 + backgroundColor: vars.color.bg, 244 + textDecoration: "none", 245 + borderColor: vars.color.primary, 246 + }, 247 + ":active": { transform: "scale(0.98)" }, 248 + }); 249 + 250 + export const buttonDanger = style({ 251 + ...btnBase, 252 + paddingBlock: "6px", 253 + paddingInline: vars.space.md, 254 + border: "none", 255 + fontSize: "0.75rem", 256 + minBlockSize: "36px", 257 + backgroundColor: vars.color.danger, 258 + color: "#fff", 259 + ":hover": { opacity: 0.9 }, 260 + ":active": { transform: "scale(0.98)" }, 261 + }); 262 + 263 + // ---- Forms ---- 264 + 265 + export const formStack = style({ 266 + display: "flex", 267 + flexDirection: "column", 268 + gap: vars.space.lg, 269 + }); 270 + 271 + export const label = style({ 272 + display: "block", 273 + fontSize: "0.875rem", 274 + fontWeight: 500, 275 + marginBlockEnd: vars.space.xs, 276 + }); 277 + 278 + export const labelHint = style({ 279 + fontWeight: 400, 280 + color: vars.color.textMuted, 281 + }); 282 + 283 + const inputBase = { 284 + boxSizing: "border-box" as const, 285 + width: "100%" as const, 286 + paddingBlock: "10px", 287 + paddingInline: vars.space.md, 288 + borderRadius: vars.radius.sm, 289 + border: `1px solid ${vars.color.border}`, 290 + fontSize: "1rem", 291 + lineHeight: 1.5, 292 + color: vars.color.text, 293 + backgroundColor: vars.color.surface, 294 + outline: "none" as const, 295 + fontFamily: vars.font.body, 296 + transition: "border-color 0.15s, box-shadow 0.15s", 297 + }; 298 + 299 + const inputFocus = { 300 + borderColor: vars.color.primary, 301 + boxShadow: `0 0 0 3px ${vars.color.focusRing}`, 302 + }; 303 + 304 + export const input = style({ 305 + ...inputBase, 306 + ":focus": inputFocus, 307 + }); 308 + 309 + export const select = style({ 310 + ...inputBase, 311 + ":focus": inputFocus, 312 + }); 313 + 314 + export const textarea = style({ 315 + ...inputBase, 316 + resize: "vertical" as const, 317 + minHeight: "80px", 318 + fontFamily: vars.font.body, 319 + ":focus": inputFocus, 320 + }); 321 + 322 + // ---- Typography ---- 323 + 324 + export const pageTitle = style({ 325 + fontFamily: vars.font.heading, 326 + fontSize: "1.25rem", 327 + fontWeight: 700, 328 + letterSpacing: "-0.02em", 329 + "@media": { 330 + [bp.sm]: { 331 + fontSize: "1.5rem", 332 + }, 333 + }, 334 + }); 335 + 336 + export const sectionTitle = style({ 337 + fontFamily: vars.font.heading, 338 + fontSize: "1rem", 339 + fontWeight: 600, 340 + letterSpacing: "-0.01em", 341 + "@media": { 342 + [bp.sm]: { 343 + fontSize: "1.125rem", 344 + }, 345 + }, 346 + }); 347 + 348 + export const cardTitle = style({ 349 + fontFamily: vars.font.heading, 350 + fontSize: "1rem", 351 + fontWeight: 600, 352 + letterSpacing: "-0.01em", 353 + "@media": { 354 + [bp.sm]: { 355 + fontSize: "1.0625rem", 356 + }, 357 + }, 358 + }); 359 + 360 + export const cardHeading = style({ 361 + fontFamily: vars.font.heading, 362 + fontSize: "1.125rem", 363 + fontWeight: 600, 364 + marginBlockEnd: vars.space.sm, 365 + letterSpacing: "-0.01em", 366 + "@media": { 367 + [bp.sm]: { 368 + fontSize: "1.25rem", 369 + }, 370 + }, 371 + }); 372 + 373 + export const muted = style({ 374 + color: vars.color.textMuted, 375 + fontSize: "0.8125rem", 376 + }); 377 + 378 + export const description = style({ 379 + color: vars.color.textMuted, 380 + fontSize: "0.875rem", 381 + lineHeight: 1.5, 382 + display: "-webkit-box", 383 + WebkitLineClamp: 2, 384 + WebkitBoxOrient: "vertical", 385 + overflow: "hidden", 386 + }); 387 + 388 + export const subsectionTitle = style({ 389 + fontSize: "0.875rem", 390 + fontWeight: 500, 391 + color: vars.color.textMuted, 392 + }); 393 + 394 + export const collapsibleHeading = style({ 395 + display: "flex", 396 + alignItems: "center", 397 + gap: vars.space.sm, 398 + cursor: "pointer", 399 + userSelect: "none", 400 + background: "none", 401 + border: "none", 402 + padding: 0, 403 + fontFamily: vars.font.heading, 404 + fontSize: "1rem", 405 + fontWeight: 600, 406 + color: vars.color.text, 407 + ":hover": { 408 + color: vars.color.primary, 409 + }, 410 + }); 411 + 412 + export const chevron = style({ 413 + display: "inline-block", 414 + fontSize: "0.75rem", 415 + transition: "transform 0.15s", 416 + }); 417 + 418 + export const chevronExpanded = style({ 419 + transform: "rotate(180deg)", 420 + }); 421 + 422 + export const introText = style({ 423 + color: vars.color.textMuted, 424 + fontSize: "0.875rem", 425 + lineHeight: 1.6, 426 + marginBlockEnd: vars.space.lg, 427 + }); 428 + 429 + export const badge = style({ 430 + display: "inline-block", 431 + paddingBlock: "2px", 432 + paddingInline: vars.space.sm, 433 + borderRadius: vars.radius.sm, 434 + fontSize: "0.75rem", 435 + fontWeight: 500, 436 + backgroundColor: vars.color.primaryLight, 437 + color: vars.color.primary, 438 + }); 439 + 440 + globalStyle(`${badge}[data-status="not-planned"]`, { 441 + backgroundColor: vars.color.dangerLight, 442 + color: vars.color.danger, 443 + }); 444 + 445 + globalStyle(`${badge}[data-status="approved"]`, { 446 + backgroundColor: vars.color.primaryLight, 447 + color: vars.color.primary, 448 + }); 449 + 450 + globalStyle(`${badge}[data-status="in-progress"]`, { 451 + backgroundColor: vars.color.warningLight, 452 + color: vars.color.warning, 453 + }); 454 + 455 + globalStyle(`${badge}[data-status="done"]`, { 456 + backgroundColor: vars.color.successLight, 457 + color: vars.color.success, 458 + }); 459 + 460 + export const errorText = style({ 461 + color: vars.color.danger, 462 + fontSize: "0.875rem", 463 + }); 464 + 465 + // ---- Misc ---- 466 + 467 + export const metaRow = style({ 468 + display: "flex", 469 + flexWrap: "wrap", 470 + alignItems: "center", 471 + gap: vars.space.md, 472 + fontSize: "0.875rem", 473 + }); 474 + 475 + export const buttonInline = style({ 476 + background: "none", 477 + border: "none", 478 + color: vars.color.primary, 479 + fontSize: "0.875rem", 480 + fontWeight: 500, 481 + fontFamily: vars.font.body, 482 + cursor: "pointer", 483 + paddingBlock: 0, 484 + paddingInline: vars.space.sm, 485 + ":hover": { textDecoration: "underline" }, 486 + }); 487 + 488 + export const buttonDangerInline = style({ 489 + background: "none", 490 + border: "none", 491 + color: vars.color.danger, 492 + fontSize: "0.875rem", 493 + fontWeight: 500, 494 + fontFamily: vars.font.body, 495 + cursor: "pointer", 496 + paddingBlock: 0, 497 + paddingInline: vars.space.sm, 498 + ":hover": { textDecoration: "underline" }, 499 + }); 500 + 501 + export const inlineTag = style({ 502 + marginInlineStart: vars.space.sm, 503 + }); 504 + 505 + // ---- Responsive utilities ---- 506 + 507 + export const hiddenMobile = style({ 508 + display: "none", 509 + "@media": { 510 + [bp.md]: { 511 + display: "initial", 512 + }, 513 + }, 514 + }); 515 + 516 + // ---- Theme toggle ---- 517 + 518 + // ---- Tab navigation ---- 519 + 520 + export const tabNav = style({ 521 + display: "flex", 522 + gap: vars.space.xs, 523 + borderBlockEnd: `1px solid ${vars.color.border}`, 524 + paddingBlockEnd: vars.space.xs, 525 + }); 526 + 527 + export const tabNavLink = style({ 528 + paddingBlock: vars.space.xs, 529 + paddingInline: vars.space.md, 530 + borderRadius: vars.radius.sm, 531 + fontSize: "0.875rem", 532 + fontWeight: 500, 533 + color: vars.color.textMuted, 534 + textDecoration: "none", 535 + transition: "color 0.15s, background-color 0.15s", 536 + ":hover": { 537 + color: vars.color.text, 538 + backgroundColor: vars.color.surfaceHover, 539 + textDecoration: "none", 540 + }, 541 + }); 542 + 543 + export const tabNavActive = style({ 544 + paddingBlock: vars.space.xs, 545 + paddingInline: vars.space.md, 546 + borderRadius: vars.radius.sm, 547 + fontSize: "0.875rem", 548 + fontWeight: 500, 549 + color: vars.color.primary, 550 + backgroundColor: vars.color.primaryLight, 551 + }); 552 + 553 + // ---- Theme toggle ---- 554 + 555 + export const themeToggle = style({ 556 + display: "inline-flex", 557 + alignItems: "center", 558 + border: `1px solid ${vars.color.border}`, 559 + borderRadius: vars.radius.sm, 560 + overflow: "hidden", 561 + padding: "2px", 562 + gap: "2px", 563 + backgroundColor: vars.color.bg, 564 + transition: "background-color 0.2s, border-color 0.2s", 565 + }); 566 + 567 + export const themeToggleBtn = style({ 568 + display: "inline-flex", 569 + alignItems: "center", 570 + justifyContent: "center", 571 + inlineSize: "28px", 572 + blockSize: "28px", 573 + border: "none", 574 + borderRadius: "6px", 575 + cursor: "pointer", 576 + backgroundColor: "transparent", 577 + color: vars.color.textMuted, 578 + padding: 0, 579 + transition: "background-color 0.15s, color 0.15s", 580 + ":hover": { color: vars.color.text }, 581 + }); 582 + 583 + export const themeToggleBtnActive = style({ 584 + backgroundColor: vars.color.surface, 585 + color: vars.color.text, 586 + boxShadow: `0 1px 2px ${vars.color.shadow}`, 587 + });
+28
packages/core/package.json
··· 1 + { 2 + "name": "@exosphere/core", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + "./auth": "./src/auth/index.ts", 8 + "./db": "./src/db/index.ts", 9 + "./db/drizzle": "./src/db/drizzle.ts", 10 + "./db/schema": "./src/db/schema/index.ts", 11 + "./indexer": "./src/indexer/index.ts", 12 + "./pds": "./src/pds.ts", 13 + "./sphere": "./src/sphere/index.ts", 14 + "./types": "./src/types/index.ts" 15 + }, 16 + "dependencies": { 17 + "@atproto/common-web": "^0.4.18", 18 + "@atproto/jwk-jose": "^0.1.0", 19 + "@atproto/oauth-client-node": "^0.3.17", 20 + "drizzle-orm": "^0.45.1", 21 + "hono": "^4.7.0", 22 + "zod": "^4.3.6" 23 + }, 24 + "devDependencies": { 25 + "@types/bun": "latest", 26 + "typescript": "^5.7.0" 27 + } 28 + }
+12
packages/core/scripts/generate-key.ts
··· 1 + import { JoseKey } from "@atproto/jwk-jose"; 2 + 3 + const key = await JoseKey.generate(["ES256"]); 4 + const jwk = key.privateJwk; 5 + 6 + if (!jwk) { 7 + console.error("Failed to generate key"); 8 + process.exit(1); 9 + } 10 + 11 + console.log("Add this to your .env file:\n"); 12 + console.log(`OAUTH_PRIVATE_KEY='${JSON.stringify(jwk)}'`);
+42
packages/core/src/__tests__/helpers/test-auth.ts
··· 1 + import { vi } from "vitest"; 2 + 3 + const TEST_DID = "did:plc:test-user-1"; 4 + 5 + /** 6 + * Mock the OAuth client module so that `requireAuth` / `optionalAuth` middlewares 7 + * restore a fake session for any DID passed as the `sid` cookie. 8 + * 9 + * Call this at the top of test files that exercise authenticated routes. 10 + */ 11 + export function mockOAuthClient() { 12 + vi.mock("../../auth/client.ts", () => ({ 13 + getOAuthClient: vi.fn().mockResolvedValue({ 14 + restore: vi 15 + .fn() 16 + .mockImplementation((did: string) => Promise.resolve({ did, dpopFetch: fetch })), 17 + }), 18 + })); 19 + } 20 + 21 + /** 22 + * Build a Request suitable for `app.request()` with an authenticated session. 23 + * Sets the `sid` cookie to the given DID. 24 + */ 25 + export function authedRequest( 26 + method: string, 27 + path: string, 28 + body?: unknown, 29 + did = TEST_DID, 30 + ): Request { 31 + const headers: Record<string, string> = { 32 + Cookie: `sid=${did}`, 33 + }; 34 + const init: RequestInit = { method, headers }; 35 + if (body !== undefined) { 36 + headers["Content-Type"] = "application/json"; 37 + init.body = JSON.stringify(body); 38 + } 39 + return new Request(`http://localhost${path}`, init); 40 + } 41 + 42 + export { TEST_DID };
+45
packages/core/src/__tests__/helpers/test-db.ts
··· 1 + import Database from "better-sqlite3"; 2 + import { drizzle, type BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; 3 + import { migrate } from "drizzle-orm/better-sqlite3/migrator"; 4 + import { resolve } from "node:path"; 5 + 6 + import { spheres, sphereMembers } from "../../db/schema/index.ts"; 7 + 8 + // Resolve the migrations folder relative to the repo root 9 + const migrationsFolder = resolve(import.meta.dirname, "../../../../../drizzle"); 10 + 11 + /** Create an in-memory SQLite database with all tables applied via migrations. */ 12 + export function createTestDb(): BetterSQLite3Database { 13 + const sqlite = new Database(":memory:"); 14 + sqlite.pragma("foreign_keys = ON"); 15 + const db = drizzle(sqlite); 16 + migrate(db, { migrationsFolder }); 17 + return db; 18 + } 19 + 20 + /** Insert a sphere with sensible defaults. Returns the inserted values. */ 21 + export function seedSphere( 22 + db: BetterSQLite3Database, 23 + overrides: Partial<typeof spheres.$inferInsert> & { id: string; slug: string; ownerDid: string }, 24 + ) { 25 + const values = { 26 + name: overrides.slug, 27 + visibility: "public" as const, 28 + writeAccess: "open" as const, 29 + ...overrides, 30 + }; 31 + db.insert(spheres).values(values).run(); 32 + 33 + // Auto-add owner as active member 34 + db.insert(sphereMembers) 35 + .values({ 36 + sphereId: values.id, 37 + did: values.ownerDid, 38 + role: "owner", 39 + status: "active", 40 + }) 41 + .onConflictDoNothing() 42 + .run(); 43 + 44 + return values; 45 + }
+279
packages/core/src/__tests__/sphere-operations.test.ts
··· 1 + import { describe, it, expect, vi, beforeEach } from "vitest"; 2 + import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; 3 + 4 + import { createTestDb, seedSphere } from "./helpers/test-db.ts"; 5 + import { spheres, sphereMembers } from "../db/schema/index.ts"; 6 + import { eq, and } from "drizzle-orm"; 7 + 8 + let db: BetterSQLite3Database; 9 + 10 + vi.mock("../db/index.ts", () => ({ 11 + getDb: () => db, 12 + })); 13 + 14 + import { 15 + getActiveMemberRole, 16 + upsertSphereFromRecord, 17 + findSphereByAtUri, 18 + upsertMemberInvite, 19 + activateMember, 20 + deleteSphereMemberApproval, 21 + deleteSphereMember, 22 + } from "../sphere/operations.ts"; 23 + 24 + const OWNER_DID = "did:plc:owner1"; 25 + const MEMBER_DID = "did:plc:member1"; 26 + const SPHERE_ID = "sphere-1"; 27 + const SPHERE_SLUG = "test-sphere"; 28 + 29 + beforeEach(() => { 30 + db = createTestDb(); 31 + }); 32 + 33 + // ---- getActiveMemberRole ---- 34 + 35 + describe("getActiveMemberRole", () => { 36 + it("returns role for an active member", () => { 37 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 38 + expect(getActiveMemberRole(SPHERE_ID, OWNER_DID)).toBe("owner"); 39 + }); 40 + 41 + it("returns null for an invited (non-active) member", () => { 42 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 43 + db.insert(sphereMembers) 44 + .values({ sphereId: SPHERE_ID, did: MEMBER_DID, role: "member", status: "invited" }) 45 + .run(); 46 + expect(getActiveMemberRole(SPHERE_ID, MEMBER_DID)).toBeNull(); 47 + }); 48 + 49 + it("returns null for a non-member", () => { 50 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 51 + expect(getActiveMemberRole(SPHERE_ID, "did:plc:unknown")).toBeNull(); 52 + }); 53 + }); 54 + 55 + // ---- findSphereByAtUri ---- 56 + 57 + describe("findSphereByAtUri", () => { 58 + it("finds a sphere by pdsUri", () => { 59 + const pdsUri = "at://did:plc:owner1/com.exosphere.sphere/sphere-1"; 60 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID, pdsUri }); 61 + const result = findSphereByAtUri(pdsUri); 62 + expect(result).toEqual({ id: SPHERE_ID }); 63 + }); 64 + 65 + it("falls back to ownerDid lookup when pdsUri doesn't match", () => { 66 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 67 + const result = findSphereByAtUri(`at://${OWNER_DID}/com.exosphere.sphere/any-rkey`); 68 + expect(result).toEqual({ id: SPHERE_ID }); 69 + }); 70 + 71 + it("returns null for unknown sphere", () => { 72 + expect(findSphereByAtUri("at://did:plc:unknown/com.exosphere.sphere/x")).toBeNull(); 73 + }); 74 + 75 + it("returns null for invalid AT URI", () => { 76 + expect(findSphereByAtUri("not-a-valid-uri")).toBeNull(); 77 + }); 78 + }); 79 + 80 + // ---- upsertSphereFromRecord ---- 81 + 82 + describe("upsertSphereFromRecord", () => { 83 + it("creates a new sphere and owner membership", () => { 84 + upsertSphereFromRecord({ 85 + did: OWNER_DID, 86 + rkey: SPHERE_ID, 87 + record: { slug: SPHERE_SLUG, name: "My Sphere", visibility: "public", writeAccess: "open" }, 88 + pdsUri: "at://did:plc:owner1/com.exosphere.sphere/sphere-1", 89 + }); 90 + 91 + const sphere = db.select().from(spheres).where(eq(spheres.id, SPHERE_ID)).get(); 92 + expect(sphere).toBeDefined(); 93 + expect(sphere!.slug).toBe(SPHERE_SLUG); 94 + expect(sphere!.name).toBe("My Sphere"); 95 + expect(sphere!.ownerDid).toBe(OWNER_DID); 96 + 97 + const member = db 98 + .select() 99 + .from(sphereMembers) 100 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, OWNER_DID))) 101 + .get(); 102 + expect(member).toBeDefined(); 103 + expect(member!.role).toBe("owner"); 104 + expect(member!.status).toBe("active"); 105 + }); 106 + 107 + it("updates an existing sphere owned by the same DID", () => { 108 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 109 + 110 + upsertSphereFromRecord({ 111 + did: OWNER_DID, 112 + rkey: SPHERE_ID, 113 + record: { slug: SPHERE_SLUG, name: "Updated Name", description: "New desc" }, 114 + pdsUri: "at://did:plc:owner1/com.exosphere.sphere/sphere-1", 115 + }); 116 + 117 + const sphere = db.select().from(spheres).where(eq(spheres.id, SPHERE_ID)).get(); 118 + expect(sphere!.name).toBe("Updated Name"); 119 + expect(sphere!.description).toBe("New desc"); 120 + }); 121 + 122 + it("ignores update when DID doesn't match owner", () => { 123 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 124 + 125 + upsertSphereFromRecord({ 126 + did: "did:plc:attacker", 127 + rkey: "other-rkey", 128 + record: { slug: SPHERE_SLUG, name: "Hijacked" }, 129 + pdsUri: "at://did:plc:attacker/com.exosphere.sphere/other-rkey", 130 + }); 131 + 132 + const sphere = db.select().from(spheres).where(eq(spheres.id, SPHERE_ID)).get(); 133 + expect(sphere!.name).toBe(SPHERE_SLUG); // unchanged (name defaults to slug in seedSphere) 134 + }); 135 + 136 + it("ignores record with no slug", () => { 137 + upsertSphereFromRecord({ 138 + did: OWNER_DID, 139 + rkey: SPHERE_ID, 140 + record: { name: "No Slug" }, 141 + pdsUri: "at://did:plc:owner1/com.exosphere.sphere/sphere-1", 142 + }); 143 + 144 + const sphere = db.select().from(spheres).where(eq(spheres.id, SPHERE_ID)).get(); 145 + expect(sphere).toBeUndefined(); 146 + }); 147 + }); 148 + 149 + // ---- upsertMemberInvite ---- 150 + 151 + describe("upsertMemberInvite", () => { 152 + it("inserts a new invited member", () => { 153 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 154 + 155 + upsertMemberInvite({ 156 + sphereId: SPHERE_ID, 157 + did: MEMBER_DID, 158 + role: "member", 159 + invitedBy: OWNER_DID, 160 + approvalPdsUri: "at://did:plc:owner1/com.exosphere.sphereMemberApproval/rkey1", 161 + }); 162 + 163 + const member = db 164 + .select() 165 + .from(sphereMembers) 166 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, MEMBER_DID))) 167 + .get(); 168 + expect(member).toBeDefined(); 169 + expect(member!.status).toBe("invited"); 170 + expect(member!.role).toBe("member"); 171 + expect(member!.invitedBy).toBe(OWNER_DID); 172 + }); 173 + 174 + it("re-invites an existing member (updates role and status)", () => { 175 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 176 + db.insert(sphereMembers) 177 + .values({ sphereId: SPHERE_ID, did: MEMBER_DID, role: "member", status: "active" }) 178 + .run(); 179 + 180 + upsertMemberInvite({ 181 + sphereId: SPHERE_ID, 182 + did: MEMBER_DID, 183 + role: "admin", 184 + invitedBy: OWNER_DID, 185 + approvalPdsUri: null, 186 + }); 187 + 188 + const member = db 189 + .select() 190 + .from(sphereMembers) 191 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, MEMBER_DID))) 192 + .get(); 193 + expect(member!.status).toBe("invited"); 194 + expect(member!.role).toBe("admin"); 195 + }); 196 + }); 197 + 198 + // ---- activateMember ---- 199 + 200 + describe("activateMember", () => { 201 + it("activates an invited member", () => { 202 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 203 + db.insert(sphereMembers) 204 + .values({ 205 + sphereId: SPHERE_ID, 206 + did: MEMBER_DID, 207 + role: "member", 208 + status: "invited", 209 + invitedBy: OWNER_DID, 210 + }) 211 + .run(); 212 + 213 + const pdsUri = "at://did:plc:member1/com.exosphere.sphereMember/rkey1"; 214 + activateMember(SPHERE_ID, MEMBER_DID, pdsUri); 215 + 216 + const member = db 217 + .select() 218 + .from(sphereMembers) 219 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, MEMBER_DID))) 220 + .get(); 221 + expect(member!.status).toBe("active"); 222 + expect(member!.pdsUri).toBe(pdsUri); 223 + }); 224 + }); 225 + 226 + // ---- deleteSphereMemberApproval ---- 227 + 228 + describe("deleteSphereMemberApproval", () => { 229 + it("revokes a member by approvalPdsUri", () => { 230 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 231 + const approvalUri = "at://did:plc:owner1/com.exosphere.sphereMemberApproval/rkey1"; 232 + db.insert(sphereMembers) 233 + .values({ 234 + sphereId: SPHERE_ID, 235 + did: MEMBER_DID, 236 + role: "member", 237 + status: "invited", 238 + invitedBy: OWNER_DID, 239 + approvalPdsUri: approvalUri, 240 + }) 241 + .run(); 242 + 243 + deleteSphereMemberApproval(OWNER_DID, approvalUri); 244 + 245 + const member = db 246 + .select() 247 + .from(sphereMembers) 248 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, MEMBER_DID))) 249 + .get(); 250 + expect(member!.status).toBe("revoked"); 251 + }); 252 + }); 253 + 254 + // ---- deleteSphereMember ---- 255 + 256 + describe("deleteSphereMember", () => { 257 + it("clears pdsUri for the member", () => { 258 + seedSphere(db, { id: SPHERE_ID, slug: SPHERE_SLUG, ownerDid: OWNER_DID }); 259 + const memberPdsUri = "at://did:plc:member1/com.exosphere.sphereMember/rkey1"; 260 + db.insert(sphereMembers) 261 + .values({ 262 + sphereId: SPHERE_ID, 263 + did: MEMBER_DID, 264 + role: "member", 265 + status: "active", 266 + pdsUri: memberPdsUri, 267 + }) 268 + .run(); 269 + 270 + deleteSphereMember(MEMBER_DID, memberPdsUri); 271 + 272 + const member = db 273 + .select() 274 + .from(sphereMembers) 275 + .where(and(eq(sphereMembers.sphereId, SPHERE_ID), eq(sphereMembers.did, MEMBER_DID))) 276 + .get(); 277 + expect(member!.pdsUri).toBeNull(); 278 + }); 279 + });
+124
packages/core/src/__tests__/sphere-schemas.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + 3 + import { 4 + createSphereSchema, 5 + updateSphereSchema, 6 + inviteMemberSchema, 7 + enableModuleSchema, 8 + } from "../sphere/schemas.ts"; 9 + 10 + describe("createSphereSchema", () => { 11 + it("accepts valid input with defaults", () => { 12 + const result = createSphereSchema.parse({ name: "My Sphere" }); 13 + expect(result.visibility).toBe("public"); 14 + expect(result.writeAccess).toBe("open"); 15 + expect(result.slug).toBeUndefined(); 16 + }); 17 + 18 + it("accepts explicit visibility and writeAccess", () => { 19 + const result = createSphereSchema.parse({ 20 + name: "Private Sphere", 21 + visibility: "private", 22 + writeAccess: "members", 23 + }); 24 + expect(result.visibility).toBe("private"); 25 + expect(result.writeAccess).toBe("members"); 26 + }); 27 + 28 + it("accepts a valid slug", () => { 29 + const result = createSphereSchema.parse({ name: "Test", slug: "my-sphere" }); 30 + expect(result.slug).toBe("my-sphere"); 31 + }); 32 + 33 + it("accepts slug with numbers", () => { 34 + expect(createSphereSchema.parse({ name: "T", slug: "sphere-42" }).slug).toBe("sphere-42"); 35 + }); 36 + 37 + it("rejects slug shorter than 3 chars", () => { 38 + expect(() => createSphereSchema.parse({ name: "T", slug: "ab" })).toThrow(); 39 + }); 40 + 41 + it("rejects slug longer than 50 chars", () => { 42 + expect(() => createSphereSchema.parse({ name: "T", slug: "a".repeat(51) })).toThrow(); 43 + }); 44 + 45 + it("rejects slug with uppercase letters", () => { 46 + expect(() => createSphereSchema.parse({ name: "T", slug: "Bad" })).toThrow(); 47 + }); 48 + 49 + it("rejects slug starting with a hyphen", () => { 50 + expect(() => createSphereSchema.parse({ name: "T", slug: "-bad" })).toThrow(); 51 + }); 52 + 53 + it("rejects slug ending with a hyphen", () => { 54 + expect(() => createSphereSchema.parse({ name: "T", slug: "bad-" })).toThrow(); 55 + }); 56 + 57 + it("rejects empty name", () => { 58 + expect(() => createSphereSchema.parse({ name: "" })).toThrow(); 59 + }); 60 + 61 + it("rejects name over 100 chars", () => { 62 + expect(() => createSphereSchema.parse({ name: "x".repeat(101) })).toThrow(); 63 + }); 64 + 65 + it("rejects description over 500 chars", () => { 66 + expect(() => createSphereSchema.parse({ name: "T", description: "x".repeat(501) })).toThrow(); 67 + }); 68 + 69 + it("rejects unknown visibility", () => { 70 + expect(() => createSphereSchema.parse({ name: "T", visibility: "secret" })).toThrow(); 71 + }); 72 + }); 73 + 74 + describe("updateSphereSchema", () => { 75 + it("accepts an empty update (all fields optional)", () => { 76 + const result = updateSphereSchema.parse({}); 77 + expect(result).toEqual({}); 78 + }); 79 + 80 + it("accepts partial updates", () => { 81 + const result = updateSphereSchema.parse({ name: "New Name" }); 82 + expect(result.name).toBe("New Name"); 83 + expect(result.visibility).toBeUndefined(); 84 + }); 85 + 86 + it("rejects empty name", () => { 87 + expect(() => updateSphereSchema.parse({ name: "" })).toThrow(); 88 + }); 89 + 90 + it("rejects unknown writeAccess value", () => { 91 + expect(() => updateSphereSchema.parse({ writeAccess: "closed" })).toThrow(); 92 + }); 93 + }); 94 + 95 + describe("inviteMemberSchema", () => { 96 + it("accepts a valid DID with default role", () => { 97 + const result = inviteMemberSchema.parse({ did: "did:plc:abc123" }); 98 + expect(result.role).toBe("member"); 99 + }); 100 + 101 + it("accepts admin role", () => { 102 + const result = inviteMemberSchema.parse({ did: "did:web:example.com", role: "admin" }); 103 + expect(result.role).toBe("admin"); 104 + }); 105 + 106 + it("rejects DID not starting with 'did:'", () => { 107 + expect(() => inviteMemberSchema.parse({ did: "plc:abc123" })).toThrow(); 108 + }); 109 + 110 + it("rejects owner role", () => { 111 + expect(() => inviteMemberSchema.parse({ did: "did:plc:abc", role: "owner" })).toThrow(); 112 + }); 113 + }); 114 + 115 + describe("enableModuleSchema", () => { 116 + it("accepts a valid module name", () => { 117 + const result = enableModuleSchema.parse({ module: "feature-requests" }); 118 + expect(result.module).toBe("feature-requests"); 119 + }); 120 + 121 + it("rejects empty module name", () => { 122 + expect(() => enableModuleSchema.parse({ module: "" })).toThrow(); 123 + }); 124 + });
+61
packages/core/src/__tests__/uri.test.ts
··· 1 + import { describe, it, expect, vi } from "vitest"; 2 + import { parseAtUri, buildAtUri } from "../indexer/uri.ts"; 3 + 4 + // Mock the DB module to prevent bun:sqlite from being loaded 5 + vi.mock("../db/index.ts", () => ({ getDb: vi.fn() })); 6 + 7 + import { isAdminOrOwner } from "../sphere/operations.ts"; 8 + 9 + describe("parseAtUri", () => { 10 + it("parses a valid AT URI", () => { 11 + const result = parseAtUri("at://did:plc:abc123/app.bsky.feed.post/rkey1"); 12 + expect(result).toEqual({ 13 + did: "did:plc:abc123", 14 + collection: "app.bsky.feed.post", 15 + rkey: "rkey1", 16 + }); 17 + }); 18 + 19 + it("returns null for invalid URIs", () => { 20 + expect(parseAtUri("")).toBeNull(); 21 + expect(parseAtUri("https://example.com")).toBeNull(); 22 + expect(parseAtUri("at://did:plc:abc123")).toBeNull(); 23 + expect(parseAtUri("at://did:plc:abc123/collection")).toBeNull(); 24 + }); 25 + 26 + it("returns null when rkey contains slashes", () => { 27 + expect(parseAtUri("at://did:plc:abc123/collection/rkey/extra")).toBeNull(); 28 + }); 29 + }); 30 + 31 + describe("buildAtUri", () => { 32 + it("builds a valid AT URI from components", () => { 33 + expect(buildAtUri("did:plc:abc123", "app.bsky.feed.post", "rkey1")).toBe( 34 + "at://did:plc:abc123/app.bsky.feed.post/rkey1", 35 + ); 36 + }); 37 + 38 + it("roundtrips with parseAtUri", () => { 39 + const uri = "at://did:plc:xyz/com.example.ns/abc"; 40 + const parsed = parseAtUri(uri)!; 41 + expect(buildAtUri(parsed.did, parsed.collection, parsed.rkey)).toBe(uri); 42 + }); 43 + }); 44 + 45 + describe("isAdminOrOwner", () => { 46 + it("returns true for owner", () => { 47 + expect(isAdminOrOwner("owner")).toBe(true); 48 + }); 49 + 50 + it("returns true for admin", () => { 51 + expect(isAdminOrOwner("admin")).toBe(true); 52 + }); 53 + 54 + it("returns false for member", () => { 55 + expect(isAdminOrOwner("member")).toBe(false); 56 + }); 57 + 58 + it("returns false for null", () => { 59 + expect(isAdminOrOwner(null)).toBe(false); 60 + }); 61 + });
+148
packages/core/src/auth/client.ts
··· 1 + import { NodeOAuthClient, type OAuthClientMetadataInput } from "@atproto/oauth-client-node"; 2 + import { JoseKey } from "@atproto/jwk-jose"; 3 + import { createStateStore, createSessionStore } from "./stores.ts"; 4 + 5 + let oauthClient: NodeOAuthClient | null = null; 6 + 7 + const pdsUrl = process.env.PDS_URL?.replace(/\/$/, ""); 8 + const isLocalDev = Boolean(pdsUrl); 9 + 10 + // Cached PDS hostname for URL rewriting (populated on first getOAuthClient call) 11 + let pdsHostname: string | null = null; 12 + 13 + export async function getOAuthClient(): Promise<NodeOAuthClient> { 14 + if (oauthClient) return oauthClient; 15 + 16 + const publicUrl = process.env.PUBLIC_URL?.replace(/\/$/, ""); 17 + if (!publicUrl) throw new Error("PUBLIC_URL env var is required"); 18 + 19 + const clientMetadata: OAuthClientMetadataInput = isLocalDev 20 + ? buildLoopbackMetadata(publicUrl) 21 + : buildProductionMetadata(publicUrl); 22 + 23 + // In local dev, the PDS registers DIDs with its public hostname (e.g. 24 + // "pds.dev") as the service endpoint — but that hostname isn't reachable. 25 + // Intercept fetch to rewrite those URLs to the actual local PDS. 26 + let devFetch: typeof fetch | undefined; 27 + if (isLocalDev) { 28 + pdsHostname = await fetchPdsHostname(); 29 + if (pdsHostname) { 30 + devFetch = Object.assign( 31 + (input: URL | RequestInfo, init?: RequestInit) => { 32 + const req = new Request(input, init); 33 + return fetch(new Request(rewritePdsUrl(req.url), req)); 34 + }, 35 + { preconnect: fetch.preconnect }, 36 + ); 37 + } 38 + } 39 + 40 + const keyset = isLocalDev 41 + ? undefined 42 + : [await JoseKey.fromImportable(requireEnv("OAUTH_PRIVATE_KEY"), "exosphere-key-1")]; 43 + 44 + oauthClient = new NodeOAuthClient({ 45 + clientMetadata, 46 + keyset, 47 + stateStore: createStateStore(), 48 + sessionStore: createSessionStore(), 49 + allowHttp: isLocalDev, 50 + fetch: devFetch, 51 + }); 52 + 53 + return oauthClient; 54 + } 55 + 56 + /** 57 + * Resolve a handle to a DID. In local dev, queries the local PDS since DNS 58 + * won't resolve local handles (e.g. alice.pds.dev). Falls back to returning 59 + * the input unchanged (the OAuth client will resolve it via DNS). 60 + */ 61 + export async function resolveHandle(handle: string): Promise<string> { 62 + if (handle.startsWith("did:") || !pdsUrl) return handle; 63 + try { 64 + const res = await fetch( 65 + `${pdsUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, 66 + ); 67 + if (res.ok) { 68 + const data = (await res.json()) as { did: string }; 69 + return data.did; 70 + } 71 + } catch {} 72 + return handle; 73 + } 74 + 75 + /** 76 + * Rewrite a URL targeting the PDS public hostname to the local PDS. 77 + * Server-side calls go directly to PDS_URL; browser redirects go through 78 + * the app's Vite proxy (same-origin) to avoid PDS origin/sec-fetch checks. 79 + */ 80 + export function rewritePdsUrl(urlStr: string, target: "server" | "browser" = "server"): string { 81 + if (!pdsHostname) return urlStr; 82 + const baseUrl = target === "browser" ? process.env.PUBLIC_URL?.replace(/\/$/, "") : pdsUrl; 83 + if (!baseUrl) return urlStr; 84 + try { 85 + const url = new URL(urlStr); 86 + if (url.hostname === pdsHostname || url.hostname.endsWith(`.${pdsHostname}`)) { 87 + const local = new URL(baseUrl); 88 + url.protocol = local.protocol; 89 + url.hostname = local.hostname; 90 + url.port = local.port; 91 + return url.href; 92 + } 93 + } catch {} 94 + return urlStr; 95 + } 96 + 97 + // --- Internal helpers --- 98 + 99 + function requireEnv(name: string): string { 100 + const value = process.env[name]; 101 + if (!value) throw new Error(`${name} env var is required`); 102 + return value; 103 + } 104 + 105 + function buildLoopbackMetadata(publicUrl: string): OAuthClientMetadataInput { 106 + const redirectUri = publicUrl.replace("localhost", "127.0.0.1") + "/api/oauth/callback"; 107 + return { 108 + client_id: `http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent("atproto transition:generic")}`, 109 + client_name: "Exosphere", 110 + client_uri: publicUrl, 111 + redirect_uris: [redirectUri], 112 + grant_types: ["authorization_code", "refresh_token"], 113 + response_types: ["code"], 114 + application_type: "native", 115 + token_endpoint_auth_method: "none", 116 + dpop_bound_access_tokens: true, 117 + scope: "atproto transition:generic", 118 + }; 119 + } 120 + 121 + function buildProductionMetadata(publicUrl: string): OAuthClientMetadataInput { 122 + return { 123 + client_id: `${publicUrl}/api/oauth/client-metadata.json`, 124 + client_name: "Exosphere", 125 + client_uri: publicUrl, 126 + redirect_uris: [`${publicUrl}/api/oauth/callback`], 127 + grant_types: ["authorization_code", "refresh_token"], 128 + response_types: ["code"], 129 + application_type: "web", 130 + token_endpoint_auth_method: "private_key_jwt", 131 + token_endpoint_auth_signing_alg: "ES256", 132 + dpop_bound_access_tokens: true, 133 + jwks_uri: `${publicUrl}/api/oauth/jwks.json`, 134 + scope: "atproto transition:generic", 135 + }; 136 + } 137 + 138 + async function fetchPdsHostname(): Promise<string | null> { 139 + try { 140 + const res = await fetch(`${pdsUrl}/xrpc/com.atproto.server.describeServer`); 141 + if (!res.ok) return null; 142 + const data = (await res.json()) as { availableUserDomains?: string[] }; 143 + const domain = data.availableUserDomains?.[0]; 144 + return domain?.startsWith(".") ? domain.slice(1) : (domain ?? null); 145 + } catch { 146 + return null; 147 + } 148 + }
+4
packages/core/src/auth/index.ts
··· 1 + export { getOAuthClient } from "./client.ts"; 2 + export { oauthRoutes } from "./routes.ts"; 3 + export { requireAuth, optionalAuth } from "./middleware.ts"; 4 + export type { AuthEnv } from "./middleware.ts";
+53
packages/core/src/auth/middleware.ts
··· 1 + import { createMiddleware } from "hono/factory"; 2 + import { getCookie, deleteCookie } from "hono/cookie"; 3 + import { getOAuthClient } from "./client.ts"; 4 + import type { OAuthSession } from "@atproto/oauth-client-node"; 5 + 6 + /** Hono env type — augments context with the authenticated user's DID */ 7 + export type AuthEnv = { 8 + Variables: { 9 + did: string; 10 + session: OAuthSession; 11 + }; 12 + }; 13 + 14 + /** 15 + * Middleware that requires authentication. 16 + * Returns 401 if no valid session. Sets `c.var.did` and `c.var.session`. 17 + */ 18 + export const requireAuth = createMiddleware<AuthEnv>(async (c, next) => { 19 + const did = getCookie(c, "sid"); 20 + if (!did) { 21 + return c.json({ error: "Unauthorized" }, 401); 22 + } 23 + 24 + try { 25 + const client = await getOAuthClient(); 26 + const session = await client.restore(did); 27 + c.set("did", session.did); 28 + c.set("session", session); 29 + await next(); 30 + } catch { 31 + deleteCookie(c, "sid", { path: "/" }); 32 + return c.json({ error: "Session expired" }, 401); 33 + } 34 + }); 35 + 36 + /** 37 + * Middleware that optionally attaches auth info. 38 + * Does not block unauthenticated requests. 39 + */ 40 + export const optionalAuth = createMiddleware<AuthEnv>(async (c, next) => { 41 + const did = getCookie(c, "sid"); 42 + if (did) { 43 + try { 44 + const client = await getOAuthClient(); 45 + const session = await client.restore(did); 46 + c.set("did", session.did); 47 + c.set("session", session); 48 + } catch { 49 + deleteCookie(c, "sid", { path: "/" }); 50 + } 51 + } 52 + await next(); 53 + });
+100
packages/core/src/auth/routes.ts
··· 1 + import { Hono } from "hono"; 2 + import { setCookie, getCookie, deleteCookie } from "hono/cookie"; 3 + import { getOAuthClient, resolveHandle, rewritePdsUrl } from "./client.ts"; 4 + 5 + const auth = new Hono(); 6 + 7 + // Serve client metadata for AT Protocol OAuth discovery 8 + auth.get("/client-metadata.json", async (c) => { 9 + const client = await getOAuthClient(); 10 + return c.json(client.clientMetadata); 11 + }); 12 + 13 + // Serve JWKS for token endpoint authentication 14 + auth.get("/jwks.json", async (c) => { 15 + const client = await getOAuthClient(); 16 + return c.json(client.jwks); 17 + }); 18 + 19 + // Initiate OAuth login — expects ?handle=user.bsky.social (or a DID) 20 + auth.get("/login", async (c) => { 21 + const handle = c.req.query("handle"); 22 + if (!handle) { 23 + return c.json({ error: "Missing 'handle' query parameter" }, 400); 24 + } 25 + 26 + try { 27 + const client = await getOAuthClient(); 28 + const input = await resolveHandle(handle); 29 + const url = await client.authorize(input, { scope: "atproto transition:generic" }); 30 + const redirectUrl = rewritePdsUrl(url.toString(), "browser"); 31 + return c.redirect(redirectUrl); 32 + } catch (err) { 33 + console.error("[oauth/login] error:", err instanceof Error ? err.message : err); 34 + return c.json({ error: "OAuth login failed" }, 500); 35 + } 36 + }); 37 + 38 + // OAuth callback — exchanges code for session 39 + auth.get("/callback", async (c) => { 40 + try { 41 + const client = await getOAuthClient(); 42 + const params = new URLSearchParams(c.req.url.split("?")[1]); 43 + const { session } = await client.callback(params); 44 + 45 + setCookie(c, "sid", session.did, { 46 + httpOnly: true, 47 + secure: c.req.url.startsWith("https:"), 48 + sameSite: "Lax", 49 + path: "/", 50 + maxAge: 60 * 60 * 24 * 30, // 30 days 51 + }); 52 + 53 + return c.redirect("/"); 54 + } catch (err) { 55 + console.error("[oauth/callback] error:", err instanceof Error ? err.message : err); 56 + return c.json({ error: "OAuth callback failed" }, 500); 57 + } 58 + }); 59 + 60 + // Get current session info 61 + auth.get("/session", async (c) => { 62 + const did = getCookie(c, "sid"); 63 + if (!did) { 64 + return c.json({ authenticated: false }); 65 + } 66 + 67 + try { 68 + const client = await getOAuthClient(); 69 + const session = await client.restore(did); 70 + let handle: string | undefined; 71 + try { 72 + const res = await session.fetchHandler( 73 + `/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(session.did)}`, 74 + ); 75 + if (res.ok) { 76 + const repo = (await res.json()) as { handle: string }; 77 + handle = repo.handle; 78 + } 79 + } catch {} 80 + return c.json({ authenticated: true, did: session.did, handle }); 81 + } catch { 82 + deleteCookie(c, "sid", { path: "/" }); 83 + return c.json({ authenticated: false }); 84 + } 85 + }); 86 + 87 + // Logout 88 + auth.post("/logout", async (c) => { 89 + const did = getCookie(c, "sid"); 90 + if (did) { 91 + try { 92 + const client = await getOAuthClient(); 93 + await client.revoke(did); 94 + } catch {} 95 + deleteCookie(c, "sid", { path: "/" }); 96 + } 97 + return c.json({ ok: true }); 98 + }); 99 + 100 + export { auth as oauthRoutes };
+71
packages/core/src/auth/stores.ts
··· 1 + import { eq } from "drizzle-orm"; 2 + import { getDb } from "../db/index.ts"; 3 + import { oauthStates, oauthSessions } from "../db/schema/index.ts"; 4 + import type { NodeSavedSession, NodeSavedState } from "@atproto/oauth-client-node"; 5 + 6 + /** SQLite-backed state store for OAuth authorization flows */ 7 + export function createStateStore() { 8 + return { 9 + async set(key: string, state: NodeSavedState): Promise<void> { 10 + const db = getDb(); 11 + db.insert(oauthStates) 12 + .values({ key, state: JSON.stringify(state) }) 13 + .onConflictDoUpdate({ 14 + target: oauthStates.key, 15 + set: { state: JSON.stringify(state) }, 16 + }) 17 + .run(); 18 + }, 19 + 20 + async get(key: string): Promise<NodeSavedState | undefined> { 21 + const db = getDb(); 22 + const row = db 23 + .select({ state: oauthStates.state }) 24 + .from(oauthStates) 25 + .where(eq(oauthStates.key, key)) 26 + .get(); 27 + if (!row) return undefined; 28 + return JSON.parse(row.state) as NodeSavedState; 29 + }, 30 + 31 + async del(key: string): Promise<void> { 32 + const db = getDb(); 33 + db.delete(oauthStates).where(eq(oauthStates.key, key)).run(); 34 + }, 35 + }; 36 + } 37 + 38 + /** SQLite-backed session store for authenticated users */ 39 + export function createSessionStore() { 40 + return { 41 + async set(key: string, session: NodeSavedSession): Promise<void> { 42 + const db = getDb(); 43 + db.insert(oauthSessions) 44 + .values({ key, session: JSON.stringify(session) }) 45 + .onConflictDoUpdate({ 46 + target: oauthSessions.key, 47 + set: { 48 + session: JSON.stringify(session), 49 + updatedAt: new Date().toISOString().replace("T", " ").slice(0, 19), 50 + }, 51 + }) 52 + .run(); 53 + }, 54 + 55 + async get(key: string): Promise<NodeSavedSession | undefined> { 56 + const db = getDb(); 57 + const row = db 58 + .select({ session: oauthSessions.session }) 59 + .from(oauthSessions) 60 + .where(eq(oauthSessions.key, key)) 61 + .get(); 62 + if (!row) return undefined; 63 + return JSON.parse(row.session) as NodeSavedSession; 64 + }, 65 + 66 + async del(key: string): Promise<void> { 67 + const db = getDb(); 68 + db.delete(oauthSessions).where(eq(oauthSessions.key, key)).run(); 69 + }, 70 + }; 71 + }
+2
packages/core/src/db/drizzle.ts
··· 1 + export { eq, and, or, not, count, max, sql, desc, asc, inArray, like } from "drizzle-orm"; 2 + export { getTableColumns } from "drizzle-orm";
+15
packages/core/src/db/index.ts
··· 1 + import { Database } from "bun:sqlite"; 2 + import { drizzle, type BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; 3 + 4 + let db: BunSQLiteDatabase; 5 + 6 + export function getDb(): BunSQLiteDatabase { 7 + if (!db) { 8 + const dbPath = process.env.DATABASE_PATH || "exosphere.sqlite"; 9 + const sqlite = new Database(dbPath, { create: true }); 10 + sqlite.run("PRAGMA journal_mode = WAL;"); 11 + sqlite.run("PRAGMA foreign_keys = ON;"); 12 + db = drizzle(sqlite); 13 + } 14 + return db; 15 + }
+6
packages/core/src/db/migrate.ts
··· 1 + import { migrate } from "drizzle-orm/bun-sqlite/migrator"; 2 + import { getDb } from "./index.ts"; 3 + 4 + const migrationsFolder = process.env.MIGRATIONS_PATH || "drizzle"; 5 + 6 + migrate(getDb(), { migrationsFolder });
+21
packages/core/src/db/schema/auth.ts
··· 1 + import { sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 + import { sql } from "drizzle-orm"; 3 + 4 + export const oauthStates = sqliteTable("oauth_states", { 5 + key: text("key").primaryKey(), 6 + state: text("state").notNull(), 7 + createdAt: text("created_at") 8 + .notNull() 9 + .default(sql`(datetime('now'))`), 10 + }); 11 + 12 + export const oauthSessions = sqliteTable("oauth_sessions", { 13 + key: text("key").primaryKey(), 14 + session: text("session").notNull(), 15 + createdAt: text("created_at") 16 + .notNull() 17 + .default(sql`(datetime('now'))`), 18 + updatedAt: text("updated_at") 19 + .notNull() 20 + .default(sql`(datetime('now'))`), 21 + });
+10
packages/core/src/db/schema/cursor.ts
··· 1 + import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; 2 + import { sql } from "drizzle-orm"; 3 + 4 + export const indexerCursor = sqliteTable("indexer_cursor", { 5 + id: text("id").primaryKey().default("jetstream"), 6 + cursor: integer("cursor").notNull(), 7 + updatedAt: text("updated_at") 8 + .notNull() 9 + .default(sql`(datetime('now'))`), 10 + });
+4
packages/core/src/db/schema/index.ts
··· 1 + export { spheres, sphereMembers, sphereModules } from "./spheres.ts"; 2 + export type { Sphere, SphereMember, SphereModule } from "./spheres.ts"; 3 + export { oauthStates, oauthSessions } from "./auth.ts"; 4 + export { indexerCursor } from "./cursor.ts";
+68
packages/core/src/db/schema/spheres.ts
··· 1 + import { sqliteTable, text, primaryKey, index } from "drizzle-orm/sqlite-core"; 2 + import { sql } from "drizzle-orm"; 3 + import type { InferSelectModel } from "drizzle-orm"; 4 + 5 + export const spheres = sqliteTable("spheres", { 6 + id: text("id").primaryKey(), 7 + slug: text("slug").unique().notNull(), 8 + name: text("name").notNull(), 9 + description: text("description"), 10 + visibility: text("visibility", { enum: ["public", "private"] }) 11 + .notNull() 12 + .default("public"), 13 + writeAccess: text("write_access", { enum: ["open", "members"] }) 14 + .notNull() 15 + .default("open"), 16 + ownerDid: text("owner_did").notNull(), 17 + pdsUri: text("pds_uri"), 18 + createdAt: text("created_at") 19 + .notNull() 20 + .default(sql`(datetime('now'))`), 21 + updatedAt: text("updated_at") 22 + .notNull() 23 + .default(sql`(datetime('now'))`), 24 + }); 25 + 26 + export const sphereMembers = sqliteTable( 27 + "sphere_members", 28 + { 29 + sphereId: text("sphere_id") 30 + .notNull() 31 + .references(() => spheres.id), 32 + did: text("did").notNull(), 33 + role: text("role", { enum: ["owner", "admin", "member"] }) 34 + .notNull() 35 + .default("member"), 36 + status: text("status", { enum: ["active", "invited", "revoked"] }) 37 + .notNull() 38 + .default("invited"), 39 + invitedBy: text("invited_by"), 40 + pdsUri: text("pds_uri"), 41 + approvalPdsUri: text("approval_pds_uri"), 42 + createdAt: text("created_at") 43 + .notNull() 44 + .default(sql`(datetime('now'))`), 45 + }, 46 + (table) => [ 47 + primaryKey({ columns: [table.sphereId, table.did] }), 48 + index("idx_sphere_members_did").on(table.did), 49 + ], 50 + ); 51 + 52 + export const sphereModules = sqliteTable( 53 + "sphere_modules", 54 + { 55 + sphereId: text("sphere_id") 56 + .notNull() 57 + .references(() => spheres.id), 58 + moduleName: text("module_name").notNull(), 59 + enabledAt: text("enabled_at") 60 + .notNull() 61 + .default(sql`(datetime('now'))`), 62 + }, 63 + (table) => [primaryKey({ columns: [table.sphereId, table.moduleName] })], 64 + ); 65 + 66 + export type Sphere = InferSelectModel<typeof spheres>; 67 + export type SphereMember = InferSelectModel<typeof sphereMembers>; 68 + export type SphereModule = InferSelectModel<typeof sphereModules>;
+2
packages/core/src/indexer/index.ts
··· 1 + export { parseAtUri, buildAtUri } from "./uri.ts"; 2 + export type { ParsedAtUri } from "./uri.ts";
+17
packages/core/src/indexer/uri.ts
··· 1 + export interface ParsedAtUri { 2 + did: string; 3 + collection: string; 4 + rkey: string; 5 + } 6 + 7 + /** Parse an AT URI (at://did/collection/rkey) into its components. */ 8 + export function parseAtUri(uri: string): ParsedAtUri | null { 9 + const match = uri.match(/^at:\/\/(did:[^/]+)\/([^/]+)\/([^/]+)$/); 10 + if (!match) return null; 11 + return { did: match[1], collection: match[2], rkey: match[3] }; 12 + } 13 + 14 + /** Build an AT URI from its components. */ 15 + export function buildAtUri(did: string, collection: string, rkey: string): string { 16 + return `at://${did}/${collection}/${rkey}`; 17 + }
+37
packages/core/src/lexicons/site.exosphere.featureRequest.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.featureRequest", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A feature request submitted to a Sphere. Published on the author's PDS for public Spheres.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["title", "sphereSlug", "createdAt"], 12 + "properties": { 13 + "title": { 14 + "type": "string", 15 + "maxLength": 512 16 + }, 17 + "description": { 18 + "type": "string", 19 + "maxLength": 10000 20 + }, 21 + "category": { 22 + "type": "string", 23 + "knownValues": ["general", "enhancement", "bug", "integration", "ui-ux"] 24 + }, 25 + "sphereSlug": { 26 + "type": "string", 27 + "description": "Slug of the Sphere this request belongs to." 28 + }, 29 + "createdAt": { 30 + "type": "string", 31 + "format": "datetime" 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+34
packages/core/src/lexicons/site.exosphere.featureRequestComment.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.featureRequestComment", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A comment on a feature request. Published on the commenter's PDS.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "content", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the feature request being commented on." 17 + }, 18 + "content": { 19 + "type": "string", 20 + "maxLength": 10000 21 + }, 22 + "createdAt": { 23 + "type": "string", 24 + "format": "datetime" 25 + }, 26 + "updatedAt": { 27 + "type": "string", 28 + "format": "datetime" 29 + } 30 + } 31 + } 32 + } 33 + } 34 + }
+26
packages/core/src/lexicons/site.exosphere.featureRequestCommentVote.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.featureRequestCommentVote", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "An upvote on a feature request comment. Published on the voter's PDS.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the comment being voted on." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+34
packages/core/src/lexicons/site.exosphere.featureRequestStatus.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.featureRequestStatus", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A status change on a feature request, published by an admin/owner on their PDS. Third-party indexers can replay these to derive the current status of a request.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "status", "sphereSlug", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the feature request whose status is being changed." 17 + }, 18 + "status": { 19 + "type": "string", 20 + "knownValues": ["requested", "not-planned", "approved", "in-progress", "done"] 21 + }, 22 + "sphereSlug": { 23 + "type": "string", 24 + "description": "Slug of the Sphere (used by indexers to scope the status change)." 25 + }, 26 + "createdAt": { 27 + "type": "string", 28 + "format": "datetime" 29 + } 30 + } 31 + } 32 + } 33 + } 34 + }
+26
packages/core/src/lexicons/site.exosphere.featureRequestVote.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.featureRequestVote", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "An upvote on a feature request. Published on the voter's PDS.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the feature request being voted on." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+35
packages/core/src/lexicons/site.exosphere.moderation.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.moderation", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A moderation action taken by a Sphere admin/owner. Published on the moderator's PDS.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["sphere", "subject", "action", "createdAt"], 12 + "properties": { 13 + "sphere": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the Sphere." 17 + }, 18 + "subject": { 19 + "type": "string", 20 + "format": "at-uri", 21 + "description": "AT URI of the content being moderated." 22 + }, 23 + "action": { 24 + "type": "string", 25 + "knownValues": ["remove"] 26 + }, 27 + "createdAt": { 28 + "type": "string", 29 + "format": "datetime" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+51
packages/core/src/lexicons/site.exosphere.sphere.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.sphere", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Declaration of a Exosphere Sphere, published on the owner's PDS. Enables third-party discovery and indexing of Spheres across the AT Protocol network.", 8 + "key": "self", 9 + "record": { 10 + "type": "object", 11 + "required": ["slug", "name", "visibility", "writeAccess", "createdAt"], 12 + "properties": { 13 + "slug": { 14 + "type": "string", 15 + "description": "URL-friendly unique identifier for the Sphere.", 16 + "maxLength": 64 17 + }, 18 + "name": { 19 + "type": "string", 20 + "description": "Human-readable display name.", 21 + "maxLength": 256 22 + }, 23 + "description": { 24 + "type": "string", 25 + "description": "Short description of the Sphere's purpose.", 26 + "maxLength": 1024 27 + }, 28 + "visibility": { 29 + "type": "string", 30 + "description": "Whether the Sphere's content is publicly readable.", 31 + "knownValues": ["public", "private"] 32 + }, 33 + "writeAccess": { 34 + "type": "string", 35 + "description": "Who can create content in this Sphere.", 36 + "knownValues": ["open", "members"] 37 + }, 38 + "modules": { 39 + "type": "array", 40 + "description": "Module names enabled for this Sphere.", 41 + "items": { "type": "string" } 42 + }, 43 + "createdAt": { 44 + "type": "string", 45 + "format": "datetime" 46 + } 47 + } 48 + } 49 + } 50 + } 51 + }
+26
packages/core/src/lexicons/site.exosphere.sphereMember.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.sphereMember", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A user's declaration that they are a member of a Sphere. Published on the member's own PDS. The Sphere owner/admin publishes a corresponding sphereMemberApproval record to confirm the membership. Together, the two records form a bilateral proof of membership — similar to how AT Protocol follows work.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["sphere", "createdAt"], 12 + "properties": { 13 + "sphere": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the Sphere record (site.exosphere.sphere) on the owner's PDS." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+36
packages/core/src/lexicons/site.exosphere.sphereMemberApproval.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "site.exosphere.sphereMemberApproval", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Published by a Sphere owner or admin on their own PDS to confirm a user's membership. References the member's sphereMember record. The role field indicates the member's permissions within the Sphere.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["sphere", "subject", "role", "createdAt"], 12 + "properties": { 13 + "sphere": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "AT URI of the Sphere record (site.exosphere.sphere)." 17 + }, 18 + "subject": { 19 + "type": "string", 20 + "format": "did", 21 + "description": "DID of the member being approved." 22 + }, 23 + "role": { 24 + "type": "string", 25 + "description": "Role granted to the member.", 26 + "knownValues": ["admin", "member"] 27 + }, 28 + "createdAt": { 29 + "type": "string", 30 + "format": "datetime" 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+61
packages/core/src/pds.ts
··· 1 + import type { OAuthSession } from "@atproto/oauth-client-node"; 2 + import { TID } from "@atproto/common-web"; 3 + 4 + export function generateRkey(): string { 5 + return TID.nextStr(); 6 + } 7 + 8 + export async function putPdsRecord( 9 + session: OAuthSession, 10 + collection: string, 11 + rkey: string, 12 + record: Record<string, unknown>, 13 + ): Promise<string | null> { 14 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 15 + method: "POST", 16 + headers: { "Content-Type": "application/json" }, 17 + body: JSON.stringify({ 18 + repo: session.did, 19 + collection, 20 + rkey, 21 + record: { $type: collection, ...record }, 22 + }), 23 + }); 24 + 25 + if (res.ok) { 26 + const data = (await res.json()) as { uri: string }; 27 + return data.uri; 28 + } 29 + 30 + console.error( 31 + `[pds] putRecord failed for ${collection}:`, 32 + res.status, 33 + await res.text().catch(() => ""), 34 + ); 35 + return null; 36 + } 37 + 38 + export async function deletePdsRecord( 39 + session: OAuthSession, 40 + collection: string, 41 + rkey: string, 42 + ): Promise<boolean> { 43 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.deleteRecord", { 44 + method: "POST", 45 + headers: { "Content-Type": "application/json" }, 46 + body: JSON.stringify({ 47 + repo: session.did, 48 + collection, 49 + rkey, 50 + }), 51 + }); 52 + 53 + if (!res.ok) { 54 + console.error( 55 + `[pds] deleteRecord failed for ${collection}:`, 56 + res.status, 57 + await res.text().catch(() => ""), 58 + ); 59 + } 60 + return res.ok; 61 + }
+23
packages/core/src/sphere/api/helpers.ts
··· 1 + import { eq } from "../../db/drizzle.ts"; 2 + import { getDb } from "../../db/index.ts"; 3 + import { spheres, sphereModules } from "../../db/schema/index.ts"; 4 + 5 + export function slugify(text: string): string { 6 + return text 7 + .toLowerCase() 8 + .replace(/[^a-z0-9]+/g, "-") 9 + .replace(/^-+|-+$/g, "") 10 + .slice(0, 50); 11 + } 12 + 13 + export function formatModules(rows: (typeof sphereModules.$inferSelect)[]) { 14 + return rows.map((m) => ({ name: m.moduleName, enabledAt: m.enabledAt })); 15 + } 16 + 17 + export function findSphere(slug: string) { 18 + return getDb().select().from(spheres).where(eq(spheres.slug, slug)).get() ?? null; 19 + } 20 + 21 + export function getEnabledModules(sphereId: string) { 22 + return getDb().select().from(sphereModules).where(eq(sphereModules.sphereId, sphereId)).all(); 23 + }
+271
packages/core/src/sphere/api/members.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { eq, and } from "../../db/drizzle.ts"; 4 + import { getDb } from "../../db/index.ts"; 5 + import { sphereMembers } from "../../db/schema/index.ts"; 6 + import { requireAuth, type AuthEnv } from "../../auth/index.ts"; 7 + import { putPdsRecord, deletePdsRecord, generateRkey } from "../../pds.ts"; 8 + import { inviteMemberSchema } from "../schemas.ts"; 9 + import { 10 + getActiveMemberRole, 11 + isAdminOrOwner, 12 + upsertMemberInvite, 13 + activateMember, 14 + } from "../operations.ts"; 15 + import { findSphere } from "./helpers.ts"; 16 + 17 + const SPHERE_COLLECTION = "site.exosphere.sphere"; 18 + const MEMBER_COLLECTION = "site.exosphere.sphereMember"; 19 + const APPROVAL_COLLECTION = "site.exosphere.sphereMemberApproval"; 20 + 21 + const app = new Hono<AuthEnv>(); 22 + 23 + // List members of a sphere 24 + app.get("/:slug/members", requireAuth, (c) => { 25 + const sphere = findSphere(c.req.param("slug")); 26 + if (!sphere) { 27 + return c.json({ error: "Sphere not found" }, 404); 28 + } 29 + 30 + const role = getActiveMemberRole(sphere.id, c.var.did); 31 + if (!isAdminOrOwner(role)) { 32 + return c.json({ error: "Forbidden" }, 403); 33 + } 34 + 35 + const db = getDb(); 36 + const rows = db 37 + .select({ 38 + did: sphereMembers.did, 39 + role: sphereMembers.role, 40 + status: sphereMembers.status, 41 + invitedBy: sphereMembers.invitedBy, 42 + createdAt: sphereMembers.createdAt, 43 + }) 44 + .from(sphereMembers) 45 + .where(eq(sphereMembers.sphereId, sphere.id)) 46 + .orderBy(sphereMembers.createdAt) 47 + .all(); 48 + 49 + return c.json({ members: rows }); 50 + }); 51 + 52 + // Invite a member (admin/owner only) 53 + // Publishes a sphereMemberApproval record on the inviter's PDS 54 + app.post("/:slug/members", requireAuth, async (c) => { 55 + const sphere = findSphere(c.req.param("slug")); 56 + if (!sphere) { 57 + return c.json({ error: "Sphere not found" }, 404); 58 + } 59 + 60 + const inviterDid = c.var.did; 61 + const inviterRole = getActiveMemberRole(sphere.id, inviterDid); 62 + if (!isAdminOrOwner(inviterRole)) { 63 + return c.json({ error: "Forbidden" }, 403); 64 + } 65 + 66 + const body = await c.req.json(); 67 + const parsed = inviteMemberSchema.safeParse(body); 68 + if (!parsed.success) { 69 + return c.json({ error: z.flattenError(parsed.error) }, 400); 70 + } 71 + 72 + const { did: memberDid, role } = parsed.data; 73 + 74 + const db = getDb(); 75 + 76 + // Check if already a member 77 + const existing = db 78 + .select({ status: sphereMembers.status }) 79 + .from(sphereMembers) 80 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.did, memberDid))) 81 + .get(); 82 + 83 + if (existing && existing.status === "active") { 84 + return c.json({ error: "User is already an active member" }, 409); 85 + } 86 + 87 + // Publish approval record on inviter's PDS 88 + const session = c.var.session; 89 + const sphereUri = sphere.pdsUri ?? `at://${sphere.ownerDid}/${SPHERE_COLLECTION}/self`; 90 + const rkey = generateRkey(); 91 + const pdsUri = await putPdsRecord(session, APPROVAL_COLLECTION, rkey, { 92 + sphere: sphereUri, 93 + subject: memberDid, 94 + role, 95 + createdAt: new Date().toISOString(), 96 + }); 97 + 98 + upsertMemberInvite({ 99 + sphereId: sphere.id, 100 + did: memberDid, 101 + role, 102 + invitedBy: inviterDid, 103 + approvalPdsUri: pdsUri, 104 + }); 105 + 106 + return c.json({ ok: true, did: memberDid, role, status: "invited" }, 201); 107 + }); 108 + 109 + // Accept an invitation (the invited user calls this) 110 + // Publishes a sphereMember record on the member's PDS 111 + app.post("/:slug/members/accept", requireAuth, async (c) => { 112 + const sphere = findSphere(c.req.param("slug")); 113 + if (!sphere) { 114 + return c.json({ error: "Sphere not found" }, 404); 115 + } 116 + 117 + const did = c.var.did; 118 + const db = getDb(); 119 + 120 + const membership = db 121 + .select() 122 + .from(sphereMembers) 123 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.did, did))) 124 + .get(); 125 + 126 + if (!membership) { 127 + return c.json({ error: "No invitation found" }, 404); 128 + } 129 + if (membership.status === "active") { 130 + return c.json({ error: "Already an active member" }, 409); 131 + } 132 + if (membership.status === "revoked") { 133 + return c.json({ error: "Membership has been revoked" }, 403); 134 + } 135 + 136 + // Publish member record on the member's own PDS 137 + const session = c.var.session; 138 + const sphereUri = sphere.pdsUri ?? `at://${sphere.ownerDid}/${SPHERE_COLLECTION}/self`; 139 + const rkey = generateRkey(); 140 + const memberPdsUri = await putPdsRecord(session, MEMBER_COLLECTION, rkey, { 141 + sphere: sphereUri, 142 + createdAt: new Date().toISOString(), 143 + }); 144 + 145 + activateMember(sphere.id, did, memberPdsUri); 146 + 147 + return c.json({ ok: true, status: "active" }); 148 + }); 149 + 150 + // Revoke a member (admin/owner only) 151 + // Deletes the approval record from the revoker's PDS 152 + app.delete("/:slug/members/:did", requireAuth, async (c) => { 153 + const sphere = findSphere(c.req.param("slug")); 154 + if (!sphere) { 155 + return c.json({ error: "Sphere not found" }, 404); 156 + } 157 + 158 + const revokerDid = c.var.did; 159 + const revokerRole = getActiveMemberRole(sphere.id, revokerDid); 160 + if (!isAdminOrOwner(revokerRole)) { 161 + return c.json({ error: "Forbidden" }, 403); 162 + } 163 + 164 + const memberDid = c.req.param("did"); 165 + const db = getDb(); 166 + 167 + const membership = db 168 + .select() 169 + .from(sphereMembers) 170 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.did, memberDid))) 171 + .get(); 172 + 173 + if (!membership) { 174 + return c.json({ error: "Member not found" }, 404); 175 + } 176 + 177 + // Cannot revoke the owner 178 + if (membership.role === "owner") { 179 + return c.json({ error: "Cannot revoke the Sphere owner" }, 403); 180 + } 181 + 182 + // Delete the approval record from the inviter's PDS if we have the URI 183 + // Note: only works if the current admin is the one who published the approval 184 + if (membership.approvalPdsUri) { 185 + const session = c.var.session; 186 + const rkey = membership.approvalPdsUri.split("/").pop(); 187 + if (rkey) { 188 + await deletePdsRecord(session, APPROVAL_COLLECTION, rkey); 189 + } 190 + } 191 + 192 + // Note: the member's own sphereMember record (pdsUri) lives on the 193 + // member's PDS and can't be deleted by the admin. It stays on-protocol 194 + // but the revoked status is authoritative via the approval deletion. 195 + db.update(sphereMembers) 196 + .set({ status: "revoked" }) 197 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.did, memberDid))) 198 + .run(); 199 + 200 + return c.json({ ok: true, status: "revoked" }); 201 + }); 202 + 203 + // Update a member's role (admin/owner only) 204 + app.put("/:slug/members/:did", requireAuth, async (c) => { 205 + const sphere = findSphere(c.req.param("slug")); 206 + if (!sphere) { 207 + return c.json({ error: "Sphere not found" }, 404); 208 + } 209 + 210 + const callerRole = getActiveMemberRole(sphere.id, c.var.did); 211 + if (!isAdminOrOwner(callerRole)) { 212 + return c.json({ error: "Forbidden" }, 403); 213 + } 214 + 215 + const memberDid = c.req.param("did"); 216 + const body = await c.req.json(); 217 + const role = body.role; 218 + if (role !== "admin" && role !== "member") { 219 + return c.json({ error: "Role must be 'admin' or 'member'" }, 400); 220 + } 221 + 222 + const db = getDb(); 223 + const membership = db 224 + .select() 225 + .from(sphereMembers) 226 + .where( 227 + and( 228 + eq(sphereMembers.sphereId, sphere.id), 229 + eq(sphereMembers.did, memberDid), 230 + eq(sphereMembers.status, "active"), 231 + ), 232 + ) 233 + .get(); 234 + 235 + if (!membership) { 236 + return c.json({ error: "Active member not found" }, 404); 237 + } 238 + 239 + if (membership.role === "owner") { 240 + return c.json({ error: "Cannot change the owner's role" }, 403); 241 + } 242 + 243 + const session = c.var.session; 244 + 245 + // Delete the old approval record before publishing the new one 246 + if (membership.approvalPdsUri) { 247 + const oldRkey = membership.approvalPdsUri.split("/").pop(); 248 + if (oldRkey) { 249 + await deletePdsRecord(session, APPROVAL_COLLECTION, oldRkey); 250 + } 251 + } 252 + 253 + // Publish updated approval record on caller's PDS 254 + const sphereUri = sphere.pdsUri ?? `at://${sphere.ownerDid}/${SPHERE_COLLECTION}/self`; 255 + const rkey = generateRkey(); 256 + const approvalPdsUri = await putPdsRecord(session, APPROVAL_COLLECTION, rkey, { 257 + sphere: sphereUri, 258 + subject: memberDid, 259 + role, 260 + createdAt: new Date().toISOString(), 261 + }); 262 + 263 + db.update(sphereMembers) 264 + .set({ role, approvalPdsUri }) 265 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.did, memberDid))) 266 + .run(); 267 + 268 + return c.json({ ok: true, did: memberDid, role }); 269 + }); 270 + 271 + export { app as membersApi };
+94
packages/core/src/sphere/api/modules.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { and, eq } from "../../db/drizzle.ts"; 4 + import { getDb } from "../../db/index.ts"; 5 + import { sphereModules } from "../../db/schema/index.ts"; 6 + import { requireAuth, type AuthEnv } from "../../auth/index.ts"; 7 + import { getActiveMemberRole, isAdminOrOwner } from "../operations.ts"; 8 + import { enableModuleSchema } from "../schemas.ts"; 9 + import { findSphere, getEnabledModules, formatModules } from "./helpers.ts"; 10 + 11 + export function createModuleRoutes(availableModules: string[]) { 12 + const app = new Hono<AuthEnv>(); 13 + 14 + // List enabled modules for a sphere 15 + app.get("/:slug/modules", (c) => { 16 + const sphere = findSphere(c.req.param("slug")); 17 + if (!sphere) { 18 + return c.json({ error: "Sphere not found" }, 404); 19 + } 20 + 21 + return c.json({ 22 + modules: formatModules(getEnabledModules(sphere.id)), 23 + available: availableModules, 24 + }); 25 + }); 26 + 27 + // Enable module 28 + app.post("/:slug/modules", requireAuth, async (c) => { 29 + const sphere = findSphere(c.req.param("slug")); 30 + if (!sphere) { 31 + return c.json({ error: "Sphere not found" }, 404); 32 + } 33 + 34 + const role = getActiveMemberRole(sphere.id, c.var.did); 35 + if (!isAdminOrOwner(role)) { 36 + return c.json({ error: "Forbidden" }, 403); 37 + } 38 + 39 + const body = await c.req.json(); 40 + const parsed = enableModuleSchema.safeParse(body); 41 + if (!parsed.success) { 42 + return c.json({ error: z.flattenError(parsed.error) }, 400); 43 + } 44 + 45 + const moduleName = parsed.data.module; 46 + if (!availableModules.includes(moduleName)) { 47 + return c.json( 48 + { 49 + error: `Unknown module: ${moduleName}. Available: ${availableModules.join(", ")}`, 50 + }, 51 + 400, 52 + ); 53 + } 54 + 55 + getDb() 56 + .insert(sphereModules) 57 + .values({ sphereId: sphere.id, moduleName }) 58 + .onConflictDoNothing() 59 + .run(); 60 + 61 + return c.json({ 62 + modules: formatModules(getEnabledModules(sphere.id)), 63 + }); 64 + }); 65 + 66 + // Disable module 67 + app.delete("/:slug/modules/:moduleName", requireAuth, (c) => { 68 + const sphere = findSphere(c.req.param("slug")); 69 + if (!sphere) { 70 + return c.json({ error: "Sphere not found" }, 404); 71 + } 72 + 73 + const role = getActiveMemberRole(sphere.id, c.var.did); 74 + if (!isAdminOrOwner(role)) { 75 + return c.json({ error: "Forbidden" }, 403); 76 + } 77 + 78 + getDb() 79 + .delete(sphereModules) 80 + .where( 81 + and( 82 + eq(sphereModules.sphereId, sphere.id), 83 + eq(sphereModules.moduleName, c.req.param("moduleName")), 84 + ), 85 + ) 86 + .run(); 87 + 88 + return c.json({ 89 + modules: formatModules(getEnabledModules(sphere.id)), 90 + }); 91 + }); 92 + 93 + return app; 94 + }
+204
packages/core/src/sphere/api/spheres.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { eq, and, count, sql } from "../../db/drizzle.ts"; 4 + import { getDb } from "../../db/index.ts"; 5 + import { spheres, sphereMembers } from "../../db/schema/index.ts"; 6 + import { requireAuth, optionalAuth, type AuthEnv } from "../../auth/index.ts"; 7 + import { putPdsRecord, generateRkey } from "../../pds.ts"; 8 + import { createSphereSchema, updateSphereSchema } from "../schemas.ts"; 9 + import { getActiveMemberRole, isAdminOrOwner } from "../operations.ts"; 10 + import { findSphere, getEnabledModules, formatModules, slugify } from "./helpers.ts"; 11 + 12 + const SPHERE_COLLECTION = "site.exosphere.sphere"; 13 + 14 + /** Load the current sphere with its modules, member count, and caller's role. */ 15 + export function getCurrentSphere(did: string | null) { 16 + const sphere = getDb().select().from(spheres).orderBy(spheres.createdAt).limit(1).get(); 17 + if (!sphere) return null; 18 + const modules = getEnabledModules(sphere.id); 19 + const memberCount = getDb() 20 + .select({ count: count() }) 21 + .from(sphereMembers) 22 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.status, "active"))) 23 + .get(); 24 + const role = did ? getActiveMemberRole(sphere.id, did) : null; 25 + return { sphere, modules: formatModules(modules), memberCount: memberCount?.count ?? 0, role }; 26 + } 27 + 28 + const app = new Hono<AuthEnv>(); 29 + 30 + // Create sphere 31 + app.post("/", requireAuth, async (c) => { 32 + const body = await c.req.json(); 33 + const parsed = createSphereSchema.safeParse(body); 34 + if (!parsed.success) { 35 + return c.json({ error: z.flattenError(parsed.error) }, 400); 36 + } 37 + 38 + const { name, description, visibility, writeAccess } = parsed.data; 39 + const slug = parsed.data.slug || slugify(name); 40 + const did = c.var.did; 41 + const id = generateRkey(); 42 + 43 + if (slug.length < 3) { 44 + return c.json({ error: "Generated slug is too short. Provide a slug explicitly." }, 400); 45 + } 46 + 47 + const db = getDb(); 48 + 49 + const existing = db.select({ id: spheres.id }).from(spheres).where(eq(spheres.slug, slug)).get(); 50 + if (existing) { 51 + return c.json({ error: "Slug already taken" }, 409); 52 + } 53 + 54 + // Write Sphere declaration to owner's PDS 55 + const session = c.var.session; 56 + const now = new Date().toISOString(); 57 + const pdsUri = await putPdsRecord(session, SPHERE_COLLECTION, "self", { 58 + slug, 59 + name, 60 + description: description ?? undefined, 61 + visibility, 62 + writeAccess, 63 + createdAt: now, 64 + }); 65 + 66 + db.transaction((tx) => { 67 + tx.insert(spheres) 68 + .values({ 69 + id, 70 + slug, 71 + name, 72 + description: description ?? null, 73 + visibility, 74 + writeAccess, 75 + ownerDid: did, 76 + pdsUri, 77 + }) 78 + .run(); 79 + 80 + tx.insert(sphereMembers) 81 + .values({ 82 + sphereId: id, 83 + did, 84 + role: "owner", 85 + status: "active", 86 + }) 87 + .run(); 88 + }); 89 + 90 + const sphere = findSphere(slug)!; 91 + return c.json({ sphere }, 201); 92 + }); 93 + 94 + // List spheres 95 + app.get("/", (c) => { 96 + const rows = getDb().select().from(spheres).orderBy(spheres.createdAt).all(); 97 + return c.json({ spheres: rows }); 98 + }); 99 + 100 + // Get the current sphere (single-sphere-per-domain mode) 101 + app.get("/current", optionalAuth, (c) => { 102 + const sphere = getDb().select().from(spheres).orderBy(spheres.createdAt).limit(1).get(); 103 + 104 + if (!sphere) { 105 + return c.json({ error: "No sphere configured" }, 404); 106 + } 107 + 108 + const modules = getEnabledModules(sphere.id); 109 + const memberCount = getDb() 110 + .select({ count: count() }) 111 + .from(sphereMembers) 112 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.status, "active"))) 113 + .get(); 114 + 115 + const did = c.var.did; 116 + const role = did ? getActiveMemberRole(sphere.id, did) : null; 117 + 118 + return c.json({ 119 + sphere, 120 + modules: formatModules(modules), 121 + memberCount: memberCount?.count ?? 0, 122 + role, 123 + }); 124 + }); 125 + 126 + // Get sphere by slug 127 + app.get("/:slug", optionalAuth, (c) => { 128 + const sphere = findSphere(c.req.param("slug")); 129 + if (!sphere) { 130 + return c.json({ error: "Sphere not found" }, 404); 131 + } 132 + 133 + const modules = getEnabledModules(sphere.id); 134 + const memberCount = getDb() 135 + .select({ count: count() }) 136 + .from(sphereMembers) 137 + .where(and(eq(sphereMembers.sphereId, sphere.id), eq(sphereMembers.status, "active"))) 138 + .get(); 139 + 140 + const did = c.var.did; 141 + const role = did ? getActiveMemberRole(sphere.id, did) : null; 142 + 143 + return c.json({ 144 + sphere: sphere, 145 + modules: formatModules(modules), 146 + memberCount: memberCount?.count ?? 0, 147 + role, 148 + }); 149 + }); 150 + 151 + // Update sphere settings 152 + app.put("/:slug", requireAuth, async (c) => { 153 + const sphere = findSphere(c.req.param("slug")); 154 + if (!sphere) { 155 + return c.json({ error: "Sphere not found" }, 404); 156 + } 157 + 158 + const role = getActiveMemberRole(sphere.id, c.var.did); 159 + if (!isAdminOrOwner(role)) { 160 + return c.json({ error: "Forbidden" }, 403); 161 + } 162 + 163 + const body = await c.req.json(); 164 + const parsed = updateSphereSchema.safeParse(body); 165 + if (!parsed.success) { 166 + return c.json({ error: z.flattenError(parsed.error) }, 400); 167 + } 168 + 169 + const updates = parsed.data; 170 + const set: Record<string, unknown> = { 171 + updatedAt: sql`datetime('now')`, 172 + }; 173 + 174 + if (updates.name !== undefined) set.name = updates.name; 175 + if (updates.description !== undefined) set.description = updates.description; 176 + if (updates.visibility !== undefined) set.visibility = updates.visibility; 177 + if (updates.writeAccess !== undefined) set.writeAccess = updates.writeAccess; 178 + 179 + // Sync Sphere declaration to owner's PDS 180 + // Only the owner can update the PDS record (it lives on their repo) 181 + if (c.var.did === sphere.ownerDid) { 182 + const session = c.var.session; 183 + const modules = getEnabledModules(sphere.id).map((m) => m.moduleName); 184 + const pdsUri = await putPdsRecord(session, SPHERE_COLLECTION, "self", { 185 + slug: sphere.slug, 186 + name: updates.name ?? sphere.name, 187 + description: updates.description ?? sphere.description ?? undefined, 188 + visibility: updates.visibility ?? sphere.visibility, 189 + writeAccess: updates.writeAccess ?? sphere.writeAccess, 190 + modules, 191 + createdAt: sphere.createdAt, 192 + }); 193 + if (pdsUri && !sphere.pdsUri) { 194 + set.pdsUri = pdsUri; 195 + } 196 + } 197 + 198 + getDb().update(spheres).set(set).where(eq(spheres.id, sphere.id)).run(); 199 + 200 + const updated = findSphere(c.req.param("slug"))!; 201 + return c.json({ sphere: updated }); 202 + }); 203 + 204 + export { app as spheresCrudApi };
+9
packages/core/src/sphere/index.ts
··· 1 + export { createSphereRoutes, getCurrentSphere } from "./routes.ts"; 2 + export { coreIndexer } from "./indexer.ts"; 3 + export { 4 + getActiveMemberRole, 5 + isAdminOrOwner, 6 + registerModerationHandler, 7 + findSphereByAtUri, 8 + } from "./operations.ts"; 9 + export type { ModerationHandler } from "./operations.ts";
+57
packages/core/src/sphere/indexer.ts
··· 1 + import type { ModuleIndexer, JetstreamCommitEvent } from "../types/index.ts"; 2 + import { buildAtUri } from "../indexer/uri.ts"; 3 + import { 4 + upsertSphereFromRecord, 5 + indexSphereMemberApproval, 6 + indexSphereMember, 7 + deleteSphereMemberApproval, 8 + deleteSphereMember, 9 + indexModerationAction, 10 + } from "./operations.ts"; 11 + 12 + const SPHERE_COLLECTION = "site.exosphere.sphere"; 13 + const MEMBER_COLLECTION = "site.exosphere.sphereMember"; 14 + const APPROVAL_COLLECTION = "site.exosphere.sphereMemberApproval"; 15 + const MODERATION_COLLECTION = "site.exosphere.moderation"; 16 + 17 + export const coreIndexer: ModuleIndexer = { 18 + collections: [SPHERE_COLLECTION, MEMBER_COLLECTION, APPROVAL_COLLECTION, MODERATION_COLLECTION], 19 + 20 + handleCreateOrUpdate(event: JetstreamCommitEvent) { 21 + const { did, commit } = event; 22 + const { collection, rkey, record } = commit; 23 + if (!record) return; 24 + 25 + const pdsUri = buildAtUri(did, collection, rkey); 26 + 27 + switch (collection) { 28 + case SPHERE_COLLECTION: 29 + upsertSphereFromRecord({ did, rkey, record, pdsUri }); 30 + break; 31 + case APPROVAL_COLLECTION: 32 + indexSphereMemberApproval({ did, rkey, record, pdsUri }); 33 + break; 34 + case MEMBER_COLLECTION: 35 + indexSphereMember({ did, rkey, record, pdsUri }); 36 + break; 37 + case MODERATION_COLLECTION: 38 + indexModerationAction(did, record); 39 + break; 40 + } 41 + }, 42 + 43 + handleDelete(event: JetstreamCommitEvent) { 44 + const { did, commit } = event; 45 + const { collection, rkey } = commit; 46 + const pdsUri = buildAtUri(did, collection, rkey); 47 + 48 + switch (collection) { 49 + case APPROVAL_COLLECTION: 50 + deleteSphereMemberApproval(did, pdsUri); 51 + break; 52 + case MEMBER_COLLECTION: 53 + deleteSphereMember(did, pdsUri); 54 + break; 55 + } 56 + }, 57 + };
+248
packages/core/src/sphere/operations.ts
··· 1 + import { eq, and } from "drizzle-orm"; 2 + import { getDb } from "../db/index.ts"; 3 + import { spheres, sphereMembers } from "../db/schema/index.ts"; 4 + import { parseAtUri } from "../indexer/uri.ts"; 5 + 6 + // ---- Membership utilities ---- 7 + 8 + export function getActiveMemberRole(sphereId: string, did: string) { 9 + const row = getDb() 10 + .select({ role: sphereMembers.role }) 11 + .from(sphereMembers) 12 + .where( 13 + and( 14 + eq(sphereMembers.sphereId, sphereId), 15 + eq(sphereMembers.did, did), 16 + eq(sphereMembers.status, "active"), 17 + ), 18 + ) 19 + .get(); 20 + return row?.role ?? null; 21 + } 22 + 23 + export function isAdminOrOwner(role: typeof sphereMembers.$inferSelect.role | null): boolean { 24 + return role === "owner" || role === "admin"; 25 + } 26 + 27 + const VALID_ROLES = ["owner", "admin", "member"] as const; 28 + type MemberRole = (typeof VALID_ROLES)[number]; 29 + 30 + function validateRole(raw: string): MemberRole { 31 + return (VALID_ROLES as readonly string[]).includes(raw) ? (raw as MemberRole) : "member"; 32 + } 33 + 34 + /** Find a sphere by its AT URI, falling back to ownerDid lookup when pdsUri is null. */ 35 + export function findSphereByAtUri(sphereUri: string): { id: string } | null { 36 + const db = getDb(); 37 + const byUri = db 38 + .select({ id: spheres.id }) 39 + .from(spheres) 40 + .where(eq(spheres.pdsUri, sphereUri)) 41 + .get(); 42 + if (byUri) return byUri; 43 + 44 + // Fallback: parse the AT URI and look up by ownerDid 45 + const parsed = parseAtUri(sphereUri); 46 + if (!parsed) return null; 47 + return ( 48 + db.select({ id: spheres.id }).from(spheres).where(eq(spheres.ownerDid, parsed.did)).get() ?? 49 + null 50 + ); 51 + } 52 + 53 + interface UpsertSphereParams { 54 + did: string; 55 + rkey: string; 56 + record: Record<string, unknown>; 57 + pdsUri: string; 58 + } 59 + 60 + export function upsertSphereFromRecord({ did, rkey, record, pdsUri }: UpsertSphereParams): void { 61 + const slug = record.slug as string; 62 + if (!slug) return; 63 + 64 + const db = getDb(); 65 + const existing = db 66 + .select({ id: spheres.id, ownerDid: spheres.ownerDid }) 67 + .from(spheres) 68 + .where(eq(spheres.slug, slug)) 69 + .get(); 70 + 71 + if (existing) { 72 + if (existing.ownerDid !== did) return; 73 + 74 + const set: Record<string, unknown> = { pdsUri }; 75 + if (record.name) set.name = record.name; 76 + if (record.description !== undefined) set.description = record.description; 77 + if (record.visibility) set.visibility = record.visibility; 78 + if (record.writeAccess) set.writeAccess = record.writeAccess; 79 + set.updatedAt = new Date().toISOString(); 80 + 81 + db.update(spheres).set(set).where(eq(spheres.id, existing.id)).run(); 82 + } else { 83 + db.insert(spheres) 84 + .values({ 85 + id: rkey, 86 + slug, 87 + name: (record.name as string) ?? slug, 88 + description: (record.description as string) ?? null, 89 + visibility: (record.visibility as "public" | "private") ?? "public", 90 + writeAccess: (record.writeAccess as "open" | "members") ?? "open", 91 + ownerDid: did, 92 + pdsUri, 93 + }) 94 + .onConflictDoNothing() 95 + .run(); 96 + 97 + // Auto-add owner as active member 98 + db.insert(sphereMembers) 99 + .values({ sphereId: rkey, did, role: "owner", status: "active" }) 100 + .onConflictDoNothing() 101 + .run(); 102 + } 103 + } 104 + 105 + // ---- Shared member operations ---- 106 + 107 + /** Insert or re-invite a member. Used by both routes (invite) and indexer (approval). */ 108 + export function upsertMemberInvite(params: { 109 + sphereId: string; 110 + did: string; 111 + role: "owner" | "admin" | "member"; 112 + invitedBy: string; 113 + approvalPdsUri: string | null; 114 + }): void { 115 + const db = getDb(); 116 + const existing = db 117 + .select({ did: sphereMembers.did }) 118 + .from(sphereMembers) 119 + .where(and(eq(sphereMembers.sphereId, params.sphereId), eq(sphereMembers.did, params.did))) 120 + .get(); 121 + 122 + if (existing) { 123 + db.update(sphereMembers) 124 + .set({ 125 + role: params.role, 126 + status: "invited", 127 + invitedBy: params.invitedBy, 128 + approvalPdsUri: params.approvalPdsUri, 129 + pdsUri: null, 130 + }) 131 + .where(and(eq(sphereMembers.sphereId, params.sphereId), eq(sphereMembers.did, params.did))) 132 + .run(); 133 + } else { 134 + db.insert(sphereMembers) 135 + .values({ 136 + sphereId: params.sphereId, 137 + did: params.did, 138 + role: params.role, 139 + status: "invited", 140 + invitedBy: params.invitedBy, 141 + approvalPdsUri: params.approvalPdsUri, 142 + }) 143 + .onConflictDoNothing() 144 + .run(); 145 + } 146 + } 147 + 148 + /** Activate a member. Used by both routes (accept invite) and indexer (member record). */ 149 + export function activateMember(sphereId: string, did: string, pdsUri: string | null): void { 150 + getDb() 151 + .update(sphereMembers) 152 + .set({ status: "active", pdsUri }) 153 + .where(and(eq(sphereMembers.sphereId, sphereId), eq(sphereMembers.did, did))) 154 + .run(); 155 + } 156 + 157 + // ---- Indexer-specific member operations ---- 158 + 159 + interface IndexMemberApprovalParams { 160 + did: string; 161 + rkey: string; 162 + record: Record<string, unknown>; 163 + pdsUri: string; 164 + } 165 + 166 + export function indexSphereMemberApproval({ 167 + did, 168 + record, 169 + pdsUri, 170 + }: IndexMemberApprovalParams): void { 171 + const sphereUri = record.sphere as string; 172 + const subjectDid = record.subject as string; 173 + const role = validateRole((record.role as string) ?? "member"); 174 + if (!sphereUri || !subjectDid) return; 175 + 176 + const sphere = findSphereByAtUri(sphereUri); 177 + if (!sphere) return; 178 + 179 + upsertMemberInvite({ 180 + sphereId: sphere.id, 181 + did: subjectDid, 182 + role, 183 + invitedBy: did, 184 + approvalPdsUri: pdsUri, 185 + }); 186 + } 187 + 188 + interface IndexMemberParams { 189 + did: string; 190 + rkey: string; 191 + record: Record<string, unknown>; 192 + pdsUri: string; 193 + } 194 + 195 + export function indexSphereMember({ did, record, pdsUri }: IndexMemberParams): void { 196 + const sphereUri = record.sphere as string; 197 + if (!sphereUri) return; 198 + 199 + const sphere = findSphereByAtUri(sphereUri); 200 + if (!sphere) return; 201 + 202 + activateMember(sphere.id, did, pdsUri); 203 + } 204 + 205 + export function deleteSphereMemberApproval(did: string, pdsUri: string): void { 206 + getDb() 207 + .update(sphereMembers) 208 + .set({ status: "revoked" }) 209 + .where(and(eq(sphereMembers.approvalPdsUri, pdsUri), eq(sphereMembers.invitedBy, did))) 210 + .run(); 211 + } 212 + 213 + export function deleteSphereMember(did: string, pdsUri: string): void { 214 + getDb() 215 + .update(sphereMembers) 216 + .set({ pdsUri: null }) 217 + .where(and(eq(sphereMembers.did, did), eq(sphereMembers.pdsUri, pdsUri))) 218 + .run(); 219 + } 220 + 221 + // ---- Moderation ---- 222 + 223 + /** Callback that tries to hide content by its pdsUri. Returns true if it handled the subject. */ 224 + export type ModerationHandler = (subjectUri: string, moderatorDid: string) => boolean; 225 + 226 + const moderationHandlers: ModerationHandler[] = []; 227 + 228 + /** Register a module-level handler for moderation actions. */ 229 + export function registerModerationHandler(handler: ModerationHandler): void { 230 + moderationHandlers.push(handler); 231 + } 232 + 233 + export function indexModerationAction(did: string, record: Record<string, unknown>): void { 234 + const sphereUri = record.sphere as string; 235 + const subjectUri = record.subject as string; 236 + const action = record.action as string; 237 + if (!sphereUri || !subjectUri || action !== "remove") return; 238 + 239 + const sphere = findSphereByAtUri(sphereUri); 240 + if (!sphere) return; 241 + 242 + const role = getActiveMemberRole(sphere.id, did); 243 + if (!isAdminOrOwner(role)) return; 244 + 245 + for (const handler of moderationHandlers) { 246 + if (handler(subjectUri, did)) return; 247 + } 248 + }
+17
packages/core/src/sphere/routes.ts
··· 1 + import { Hono } from "hono"; 2 + import type { AuthEnv } from "../auth/index.ts"; 3 + import { spheresCrudApi } from "./api/spheres.ts"; 4 + import { createModuleRoutes } from "./api/modules.ts"; 5 + import { membersApi } from "./api/members.ts"; 6 + 7 + export { getCurrentSphere } from "./api/spheres.ts"; 8 + 9 + export function createSphereRoutes(availableModules: string[]) { 10 + const app = new Hono<AuthEnv>(); 11 + 12 + app.route("/", spheresCrudApi); 13 + app.route("/", createModuleRoutes(availableModules)); 14 + app.route("/", membersApi); 15 + 16 + return app; 17 + }
+32
packages/core/src/sphere/schemas.ts
··· 1 + import { z } from "zod"; 2 + 3 + const slugPattern = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/; 4 + 5 + export const createSphereSchema = z.object({ 6 + name: z.string().min(1).max(100), 7 + slug: z 8 + .string() 9 + .min(3) 10 + .max(50) 11 + .regex(slugPattern, "Must be lowercase alphanumeric with hyphens") 12 + .optional(), 13 + description: z.string().max(500).optional(), 14 + visibility: z.enum(["public", "private"]).default("public"), 15 + writeAccess: z.enum(["open", "members"]).default("open"), 16 + }); 17 + 18 + export const updateSphereSchema = z.object({ 19 + name: z.string().min(1).max(100).optional(), 20 + description: z.string().max(500).optional(), 21 + visibility: z.enum(["public", "private"]).optional(), 22 + writeAccess: z.enum(["open", "members"]).optional(), 23 + }); 24 + 25 + export const enableModuleSchema = z.object({ 26 + module: z.string().min(1), 27 + }); 28 + 29 + export const inviteMemberSchema = z.object({ 30 + did: z.string().startsWith("did:"), 31 + role: z.enum(["admin", "member"]).default("member"), 32 + });
+48
packages/core/src/types/index.ts
··· 1 + import type { Hono } from "hono"; 2 + import type { BlankSchema } from "hono/types"; 3 + import type { SphereMember } from "../db/schema/spheres.ts"; 4 + 5 + export interface JetstreamCommitEvent { 6 + did: string; 7 + time_us: number; 8 + kind: "commit"; 9 + commit: { 10 + rev: string; 11 + operation: "create" | "update" | "delete"; 12 + collection: string; 13 + rkey: string; 14 + record?: Record<string, unknown>; 15 + cid?: string; 16 + }; 17 + } 18 + 19 + export interface ModuleIndexer { 20 + collections: string[]; 21 + handleCreateOrUpdate(event: JetstreamCommitEvent): void; 22 + handleDelete(event: JetstreamCommitEvent): void; 23 + } 24 + 25 + /** Base type for a Exosphere module */ 26 + export interface ExosphereModule { 27 + /** Unique module identifier */ 28 + name: string; 29 + /** Hono sub-app with module routes — env is flexible so modules can use auth middleware */ 30 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 31 + api: Hono<any, BlankSchema, "/">; 32 + /** Optional indexer for consuming Jetstream events */ 33 + indexer?: ModuleIndexer; 34 + } 35 + 36 + /** Formatted module info as returned by the API (subset of SphereModule DB row). */ 37 + export interface SphereModuleInfo { 38 + name: string; 39 + enabledAt: string; 40 + } 41 + 42 + /** Shape returned by GET /api/spheres/:slug. */ 43 + export interface SphereData { 44 + sphere: import("../db/schema/spheres.ts").Sphere; 45 + modules: SphereModuleInfo[]; 46 + memberCount: number; 47 + role: SphereMember["role"] | null; 48 + }
+4
packages/core/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+7
packages/core/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + include: ["src/**/*.test.ts"], 6 + }, 7 + });
+26
packages/feature-requests/package.json
··· 1 + { 2 + "name": "@exosphere/feature-requests", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + ".": "./src/index.ts", 8 + "./client": "./src/client.ts", 9 + "./client-ssr": "./src/client.ssr.ts", 10 + "./types": "./src/types.ts" 11 + }, 12 + "dependencies": { 13 + "@exosphere/client": "workspace:*", 14 + "@exosphere/core": "workspace:*", 15 + "@preact/signals": "^2.0.0", 16 + "@vanilla-extract/css": "^1.0.0", 17 + "drizzle-orm": "^0.45.1", 18 + "hono": "^4.7.0", 19 + "preact": "^10.25.0", 20 + "zod": "^4.3.6" 21 + }, 22 + "devDependencies": { 23 + "@types/bun": "latest", 24 + "typescript": "^5.7.0" 25 + } 26 + }
+440
packages/feature-requests/src/__tests__/db-operations.test.ts
··· 1 + import { describe, it, expect, vi, beforeEach } from "vitest"; 2 + import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; 3 + import { eq, and } from "drizzle-orm"; 4 + 5 + // Use the shared test-db helper from core (resolves via workspace) 6 + import { createTestDb } from "../../../core/src/__tests__/helpers/test-db.ts"; 7 + 8 + let db: BetterSQLite3Database; 9 + 10 + // Mock getDb so operations use our in-memory test database 11 + vi.mock("@exosphere/core/db", () => ({ 12 + getDb: () => db, 13 + })); 14 + 15 + import { 16 + insertFeatureRequest, 17 + deleteFeatureRequestCascade, 18 + insertVote, 19 + deleteVoteByAuthor, 20 + insertComment, 21 + updateComment, 22 + deleteCommentCascade, 23 + insertCommentVote, 24 + deleteCommentVoteByAuthor, 25 + insertStatusAndUpdateFR, 26 + hideFeatureRequest, 27 + unhideFeatureRequest, 28 + hideComment, 29 + unhideComment, 30 + handleFeatureRequestModeration, 31 + } from "../db/operations.ts"; 32 + import { 33 + featureRequests, 34 + featureRequestVotes, 35 + featureRequestComments, 36 + featureRequestCommentVotes, 37 + featureRequestStatuses, 38 + } from "../db/schema.ts"; 39 + 40 + const AUTHOR_DID = "did:plc:author1"; 41 + const MOD_DID = "did:plc:mod1"; 42 + 43 + function seedFR(overrides: Partial<typeof featureRequests.$inferInsert> & { id: string }) { 44 + const values = { 45 + number: 1, 46 + authorDid: AUTHOR_DID, 47 + title: "Test FR", 48 + description: "A test feature request", 49 + category: "general" as const, 50 + ...overrides, 51 + }; 52 + db.insert(featureRequests).values(values).run(); 53 + return values; 54 + } 55 + 56 + beforeEach(() => { 57 + db = createTestDb(); 58 + }); 59 + 60 + // ---- insertFeatureRequest ---- 61 + 62 + describe("insertFeatureRequest", () => { 63 + it("inserts a feature request with auto-incrementing number", () => { 64 + const fr1 = insertFeatureRequest({ 65 + id: "fr-1", 66 + authorDid: AUTHOR_DID, 67 + title: "First", 68 + description: "Desc", 69 + category: "general", 70 + pdsUri: null, 71 + }); 72 + expect(fr1).toBeDefined(); 73 + expect(fr1!.number).toBe(1); 74 + 75 + const fr2 = insertFeatureRequest({ 76 + id: "fr-2", 77 + authorDid: AUTHOR_DID, 78 + title: "Second", 79 + description: "Desc", 80 + category: "bug", 81 + pdsUri: null, 82 + }); 83 + expect(fr2!.number).toBe(2); 84 + }); 85 + 86 + it("handles conflict (duplicate id) gracefully", () => { 87 + insertFeatureRequest({ 88 + id: "fr-1", 89 + authorDid: AUTHOR_DID, 90 + title: "Original", 91 + description: "Desc", 92 + category: "general", 93 + pdsUri: null, 94 + }); 95 + 96 + // Same id — onConflictDoNothing, returns existing row 97 + const dup = insertFeatureRequest({ 98 + id: "fr-1", 99 + authorDid: AUTHOR_DID, 100 + title: "Duplicate", 101 + description: "Desc", 102 + category: "general", 103 + pdsUri: null, 104 + }); 105 + expect(dup).toBeDefined(); 106 + expect(dup!.title).toBe("Original"); 107 + }); 108 + }); 109 + 110 + // ---- deleteFeatureRequestCascade ---- 111 + 112 + describe("deleteFeatureRequestCascade", () => { 113 + it("deletes a feature request and all related records", () => { 114 + seedFR({ id: "fr-1" }); 115 + db.insert(featureRequestVotes).values({ requestId: "fr-1", authorDid: "did:plc:voter1" }).run(); 116 + db.insert(featureRequestComments) 117 + .values({ id: "c-1", requestId: "fr-1", authorDid: AUTHOR_DID, content: "Hello" }) 118 + .run(); 119 + db.insert(featureRequestCommentVotes) 120 + .values({ commentId: "c-1", authorDid: "did:plc:voter2" }) 121 + .run(); 122 + db.insert(featureRequestStatuses) 123 + .values({ id: "s-1", requestId: "fr-1", authorDid: MOD_DID, status: "approved" }) 124 + .run(); 125 + 126 + deleteFeatureRequestCascade("fr-1"); 127 + 128 + expect(db.select().from(featureRequests).all()).toHaveLength(0); 129 + expect(db.select().from(featureRequestVotes).all()).toHaveLength(0); 130 + expect(db.select().from(featureRequestComments).all()).toHaveLength(0); 131 + expect(db.select().from(featureRequestCommentVotes).all()).toHaveLength(0); 132 + expect(db.select().from(featureRequestStatuses).all()).toHaveLength(0); 133 + }); 134 + }); 135 + 136 + // ---- Votes ---- 137 + 138 + describe("insertVote / deleteVoteByAuthor", () => { 139 + it("inserts a vote", () => { 140 + seedFR({ id: "fr-1" }); 141 + insertVote("fr-1", AUTHOR_DID, null); 142 + 143 + const votes = db.select().from(featureRequestVotes).all(); 144 + expect(votes).toHaveLength(1); 145 + expect(votes[0].authorDid).toBe(AUTHOR_DID); 146 + }); 147 + 148 + it("ignores duplicate votes (same author + request)", () => { 149 + seedFR({ id: "fr-1" }); 150 + insertVote("fr-1", AUTHOR_DID, null); 151 + insertVote("fr-1", AUTHOR_DID, null); 152 + 153 + expect(db.select().from(featureRequestVotes).all()).toHaveLength(1); 154 + }); 155 + 156 + it("deletes a vote by author", () => { 157 + seedFR({ id: "fr-1" }); 158 + insertVote("fr-1", AUTHOR_DID, null); 159 + deleteVoteByAuthor("fr-1", AUTHOR_DID); 160 + 161 + expect(db.select().from(featureRequestVotes).all()).toHaveLength(0); 162 + }); 163 + }); 164 + 165 + // ---- Comments ---- 166 + 167 + describe("insertComment / updateComment / deleteCommentCascade", () => { 168 + it("inserts a comment", () => { 169 + seedFR({ id: "fr-1" }); 170 + insertComment({ 171 + id: "c-1", 172 + requestId: "fr-1", 173 + authorDid: AUTHOR_DID, 174 + content: "Great idea!", 175 + pdsUri: null, 176 + }); 177 + 178 + const comments = db.select().from(featureRequestComments).all(); 179 + expect(comments).toHaveLength(1); 180 + expect(comments[0].content).toBe("Great idea!"); 181 + }); 182 + 183 + it("upserts on conflict (same id updates content)", () => { 184 + seedFR({ id: "fr-1" }); 185 + insertComment({ 186 + id: "c-1", 187 + requestId: "fr-1", 188 + authorDid: AUTHOR_DID, 189 + content: "Original", 190 + pdsUri: null, 191 + }); 192 + insertComment({ 193 + id: "c-1", 194 + requestId: "fr-1", 195 + authorDid: AUTHOR_DID, 196 + content: "Updated via upsert", 197 + pdsUri: null, 198 + }); 199 + 200 + const comment = db 201 + .select() 202 + .from(featureRequestComments) 203 + .where(eq(featureRequestComments.id, "c-1")) 204 + .get(); 205 + expect(comment!.content).toBe("Updated via upsert"); 206 + }); 207 + 208 + it("updates a comment's content", () => { 209 + seedFR({ id: "fr-1" }); 210 + insertComment({ 211 + id: "c-1", 212 + requestId: "fr-1", 213 + authorDid: AUTHOR_DID, 214 + content: "Original", 215 + pdsUri: null, 216 + }); 217 + updateComment("c-1", "Edited content"); 218 + 219 + const comment = db 220 + .select() 221 + .from(featureRequestComments) 222 + .where(eq(featureRequestComments.id, "c-1")) 223 + .get(); 224 + expect(comment!.content).toBe("Edited content"); 225 + }); 226 + 227 + it("cascade-deletes comment and its votes", () => { 228 + seedFR({ id: "fr-1" }); 229 + insertComment({ 230 + id: "c-1", 231 + requestId: "fr-1", 232 + authorDid: AUTHOR_DID, 233 + content: "Comment", 234 + pdsUri: null, 235 + }); 236 + db.insert(featureRequestCommentVotes) 237 + .values({ commentId: "c-1", authorDid: "did:plc:voter1" }) 238 + .run(); 239 + 240 + deleteCommentCascade("c-1"); 241 + 242 + expect(db.select().from(featureRequestComments).all()).toHaveLength(0); 243 + expect(db.select().from(featureRequestCommentVotes).all()).toHaveLength(0); 244 + }); 245 + }); 246 + 247 + // ---- Comment Votes ---- 248 + 249 + describe("insertCommentVote / deleteCommentVoteByAuthor", () => { 250 + it("inserts and deletes a comment vote", () => { 251 + seedFR({ id: "fr-1" }); 252 + insertComment({ 253 + id: "c-1", 254 + requestId: "fr-1", 255 + authorDid: AUTHOR_DID, 256 + content: "Comment", 257 + pdsUri: null, 258 + }); 259 + 260 + insertCommentVote("c-1", "did:plc:voter1", null); 261 + expect(db.select().from(featureRequestCommentVotes).all()).toHaveLength(1); 262 + 263 + deleteCommentVoteByAuthor("c-1", "did:plc:voter1"); 264 + expect(db.select().from(featureRequestCommentVotes).all()).toHaveLength(0); 265 + }); 266 + 267 + it("ignores duplicate comment votes", () => { 268 + seedFR({ id: "fr-1" }); 269 + insertComment({ 270 + id: "c-1", 271 + requestId: "fr-1", 272 + authorDid: AUTHOR_DID, 273 + content: "Comment", 274 + pdsUri: null, 275 + }); 276 + 277 + insertCommentVote("c-1", "did:plc:voter1", null); 278 + insertCommentVote("c-1", "did:plc:voter1", null); 279 + expect(db.select().from(featureRequestCommentVotes).all()).toHaveLength(1); 280 + }); 281 + }); 282 + 283 + // ---- Status ---- 284 + 285 + describe("insertStatusAndUpdateFR", () => { 286 + it("inserts a status record and updates the feature request", () => { 287 + seedFR({ id: "fr-1" }); 288 + 289 + insertStatusAndUpdateFR({ 290 + id: "s-1", 291 + requestId: "fr-1", 292 + authorDid: MOD_DID, 293 + status: "approved", 294 + pdsUri: null, 295 + }); 296 + 297 + const statuses = db.select().from(featureRequestStatuses).all(); 298 + expect(statuses).toHaveLength(1); 299 + expect(statuses[0].status).toBe("approved"); 300 + 301 + const fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 302 + expect(fr!.status).toBe("approved"); 303 + }); 304 + 305 + it("sets duplicateOfId when marking as duplicate", () => { 306 + seedFR({ id: "fr-1" }); 307 + seedFR({ id: "fr-2", number: 2 }); 308 + 309 + insertStatusAndUpdateFR({ 310 + id: "s-1", 311 + requestId: "fr-1", 312 + authorDid: MOD_DID, 313 + status: "duplicate", 314 + pdsUri: null, 315 + duplicateOfId: "fr-2", 316 + }); 317 + 318 + const fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 319 + expect(fr!.status).toBe("duplicate"); 320 + expect(fr!.duplicateOfId).toBe("fr-2"); 321 + }); 322 + 323 + it("clears duplicateOfId when clearDuplicateOfId is set", () => { 324 + seedFR({ id: "fr-1" }); 325 + seedFR({ id: "fr-2", number: 2 }); 326 + 327 + // First mark as duplicate 328 + insertStatusAndUpdateFR({ 329 + id: "s-1", 330 + requestId: "fr-1", 331 + authorDid: MOD_DID, 332 + status: "duplicate", 333 + pdsUri: null, 334 + duplicateOfId: "fr-2", 335 + }); 336 + 337 + // Then clear the duplicate 338 + insertStatusAndUpdateFR({ 339 + id: "s-2", 340 + requestId: "fr-1", 341 + authorDid: MOD_DID, 342 + status: "requested", 343 + pdsUri: null, 344 + clearDuplicateOfId: true, 345 + }); 346 + 347 + const fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 348 + expect(fr!.status).toBe("requested"); 349 + expect(fr!.duplicateOfId).toBeNull(); 350 + }); 351 + }); 352 + 353 + // ---- Moderation ---- 354 + 355 + describe("hideFeatureRequest / unhideFeatureRequest", () => { 356 + it("hides and unhides a feature request", () => { 357 + seedFR({ id: "fr-1" }); 358 + 359 + hideFeatureRequest("fr-1", MOD_DID); 360 + let fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 361 + expect(fr!.hiddenAt).toBeTruthy(); 362 + expect(fr!.moderatedBy).toBe(MOD_DID); 363 + 364 + unhideFeatureRequest("fr-1"); 365 + fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 366 + expect(fr!.hiddenAt).toBeNull(); 367 + expect(fr!.moderatedBy).toBeNull(); 368 + }); 369 + }); 370 + 371 + describe("hideComment / unhideComment", () => { 372 + it("hides and unhides a comment", () => { 373 + seedFR({ id: "fr-1" }); 374 + insertComment({ 375 + id: "c-1", 376 + requestId: "fr-1", 377 + authorDid: AUTHOR_DID, 378 + content: "Comment", 379 + pdsUri: null, 380 + }); 381 + 382 + hideComment("c-1", MOD_DID); 383 + let comment = db 384 + .select() 385 + .from(featureRequestComments) 386 + .where(eq(featureRequestComments.id, "c-1")) 387 + .get(); 388 + expect(comment!.hiddenAt).toBeTruthy(); 389 + expect(comment!.moderatedBy).toBe(MOD_DID); 390 + 391 + unhideComment("c-1"); 392 + comment = db 393 + .select() 394 + .from(featureRequestComments) 395 + .where(eq(featureRequestComments.id, "c-1")) 396 + .get(); 397 + expect(comment!.hiddenAt).toBeNull(); 398 + expect(comment!.moderatedBy).toBeNull(); 399 + }); 400 + }); 401 + 402 + describe("handleFeatureRequestModeration", () => { 403 + it("hides a feature request by pdsUri and returns true", () => { 404 + const pdsUri = "at://did:plc:author1/com.exosphere.featureRequest/fr-1"; 405 + seedFR({ id: "fr-1", pdsUri }); 406 + 407 + const handled = handleFeatureRequestModeration(pdsUri, MOD_DID); 408 + expect(handled).toBe(true); 409 + 410 + const fr = db.select().from(featureRequests).where(eq(featureRequests.id, "fr-1")).get(); 411 + expect(fr!.hiddenAt).toBeTruthy(); 412 + }); 413 + 414 + it("hides a comment by pdsUri and returns true", () => { 415 + seedFR({ id: "fr-1" }); 416 + const commentPdsUri = "at://did:plc:author1/com.exosphere.featureRequestComment/c-1"; 417 + insertComment({ 418 + id: "c-1", 419 + requestId: "fr-1", 420 + authorDid: AUTHOR_DID, 421 + content: "Comment", 422 + pdsUri: commentPdsUri, 423 + }); 424 + 425 + const handled = handleFeatureRequestModeration(commentPdsUri, MOD_DID); 426 + expect(handled).toBe(true); 427 + 428 + const comment = db 429 + .select() 430 + .from(featureRequestComments) 431 + .where(eq(featureRequestComments.id, "c-1")) 432 + .get(); 433 + expect(comment!.hiddenAt).toBeTruthy(); 434 + }); 435 + 436 + it("returns false when pdsUri matches nothing", () => { 437 + const handled = handleFeatureRequestModeration("at://unknown/col/rkey", MOD_DID); 438 + expect(handled).toBe(false); 439 + }); 440 + });
+110
packages/feature-requests/src/__tests__/schemas.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + 3 + import { 4 + createFeatureRequestSchema, 5 + updateStatusSchema, 6 + markAsDuplicateSchema, 7 + } from "../schemas/feature-request.ts"; 8 + import { createCommentSchema, updateCommentSchema } from "../schemas/comment.ts"; 9 + 10 + describe("createFeatureRequestSchema", () => { 11 + const valid = { 12 + title: "Add dark mode", 13 + description: "It would be great to have a dark mode option.", 14 + sphereSlug: "my-sphere", 15 + }; 16 + 17 + it("accepts valid input with defaults", () => { 18 + const result = createFeatureRequestSchema.parse(valid); 19 + expect(result.category).toBe("general"); 20 + }); 21 + 22 + it("accepts an explicit category", () => { 23 + const result = createFeatureRequestSchema.parse({ ...valid, category: "bug" }); 24 + expect(result.category).toBe("bug"); 25 + }); 26 + 27 + it("rejects unknown category", () => { 28 + expect(() => createFeatureRequestSchema.parse({ ...valid, category: "nope" })).toThrow(); 29 + }); 30 + 31 + it("rejects empty title", () => { 32 + expect(() => createFeatureRequestSchema.parse({ ...valid, title: "" })).toThrow(); 33 + }); 34 + 35 + it("rejects title over 200 chars", () => { 36 + expect(() => createFeatureRequestSchema.parse({ ...valid, title: "x".repeat(201) })).toThrow(); 37 + }); 38 + 39 + it("rejects empty description", () => { 40 + expect(() => createFeatureRequestSchema.parse({ ...valid, description: "" })).toThrow(); 41 + }); 42 + 43 + it("rejects description over 10 000 chars", () => { 44 + expect(() => 45 + createFeatureRequestSchema.parse({ ...valid, description: "x".repeat(10001) }), 46 + ).toThrow(); 47 + }); 48 + 49 + it("rejects missing sphereSlug", () => { 50 + const { sphereSlug: _, ...noSlug } = valid; 51 + expect(() => createFeatureRequestSchema.parse(noSlug)).toThrow(); 52 + }); 53 + }); 54 + 55 + describe("updateStatusSchema", () => { 56 + it("accepts a settable status", () => { 57 + const result = updateStatusSchema.parse({ status: "approved", sphereSlug: "s" }); 58 + expect(result.status).toBe("approved"); 59 + }); 60 + 61 + it("rejects 'duplicate' (not a settable status)", () => { 62 + expect(() => updateStatusSchema.parse({ status: "duplicate", sphereSlug: "s" })).toThrow(); 63 + }); 64 + 65 + it("rejects unknown status", () => { 66 + expect(() => updateStatusSchema.parse({ status: "unknown", sphereSlug: "s" })).toThrow(); 67 + }); 68 + }); 69 + 70 + describe("markAsDuplicateSchema", () => { 71 + it("accepts valid input", () => { 72 + const result = markAsDuplicateSchema.parse({ 73 + duplicateOfId: "abc123", 74 + sphereSlug: "s", 75 + }); 76 + expect(result.duplicateOfId).toBe("abc123"); 77 + }); 78 + 79 + it("rejects empty duplicateOfId", () => { 80 + expect(() => markAsDuplicateSchema.parse({ duplicateOfId: "", sphereSlug: "s" })).toThrow(); 81 + }); 82 + }); 83 + 84 + describe("createCommentSchema", () => { 85 + it("accepts valid input", () => { 86 + const result = createCommentSchema.parse({ content: "Nice idea!", sphereSlug: "s" }); 87 + expect(result.content).toBe("Nice idea!"); 88 + }); 89 + 90 + it("rejects empty content", () => { 91 + expect(() => createCommentSchema.parse({ content: "", sphereSlug: "s" })).toThrow(); 92 + }); 93 + 94 + it("rejects content over 5 000 chars", () => { 95 + expect(() => 96 + createCommentSchema.parse({ content: "x".repeat(5001), sphereSlug: "s" }), 97 + ).toThrow(); 98 + }); 99 + }); 100 + 101 + describe("updateCommentSchema", () => { 102 + it("accepts valid input", () => { 103 + const result = updateCommentSchema.parse({ content: "Updated!", sphereSlug: "s" }); 104 + expect(result.content).toBe("Updated!"); 105 + }); 106 + 107 + it("rejects empty content", () => { 108 + expect(() => updateCommentSchema.parse({ content: "", sphereSlug: "s" })).toThrow(); 109 + }); 110 + });
+561
packages/feature-requests/src/api/comments.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { getDb } from "@exosphere/core/db"; 4 + import { eq, and, count, sql, inArray, desc, asc } from "@exosphere/core/db/drizzle"; 5 + import { requireAuth, type AuthEnv } from "@exosphere/core/auth"; 6 + import { getActiveMemberRole, isAdminOrOwner } from "@exosphere/core/sphere"; 7 + import { putPdsRecord, deletePdsRecord, generateRkey } from "@exosphere/core/pds"; 8 + import { spheres } from "@exosphere/core/db/schema"; 9 + import { 10 + featureRequests, 11 + featureRequestComments, 12 + featureRequestCommentVotes, 13 + featureRequestStatuses, 14 + } from "../db/schema.ts"; 15 + import { createCommentSchema, updateCommentSchema } from "../schemas/comment.ts"; 16 + import { 17 + insertComment, 18 + updateComment, 19 + deleteCommentCascade, 20 + insertCommentVote, 21 + deleteCommentVoteByAuthor, 22 + hideComment, 23 + unhideComment, 24 + } from "../db/operations.ts"; 25 + 26 + const COMMENT_COLLECTION = "site.exosphere.featureRequestComment"; 27 + const COMMENT_VOTE_COLLECTION = "site.exosphere.featureRequestCommentVote"; 28 + const MODERATION_COLLECTION = "site.exosphere.moderation"; 29 + 30 + const app = new Hono<AuthEnv>(); 31 + 32 + // ---- Comments ---- 33 + 34 + // List comments for a feature request 35 + app.get("/:id/comments", (c) => { 36 + const requestId = c.req.param("id"); 37 + const sortParam = c.req.query("sort"); 38 + const orderParam = c.req.query("order"); 39 + const sortBy = sortParam === "votes" ? "votes" : "date"; 40 + const orderDir = orderParam === "asc" ? asc : desc; 41 + const db = getDb(); 42 + 43 + // When the request is closed (done/not-planned), hide comments posted after the closing date 44 + const request = db 45 + .select({ status: featureRequests.status }) 46 + .from(featureRequests) 47 + .where(eq(featureRequests.id, requestId)) 48 + .get(); 49 + 50 + let cutoffDate: string | null = null; 51 + if ( 52 + request && 53 + (request.status === "done" || 54 + request.status === "not-planned" || 55 + request.status === "duplicate") 56 + ) { 57 + const closedStatus = db 58 + .select({ createdAt: featureRequestStatuses.createdAt }) 59 + .from(featureRequestStatuses) 60 + .where( 61 + and( 62 + eq(featureRequestStatuses.requestId, requestId), 63 + inArray(featureRequestStatuses.status, ["done", "not-planned", "duplicate"]), 64 + ), 65 + ) 66 + .orderBy(sql`${featureRequestStatuses.createdAt} desc`) 67 + .limit(1) 68 + .get(); 69 + cutoffDate = closedStatus?.createdAt ?? null; 70 + } 71 + 72 + const notHidden = sql`${featureRequestComments.hiddenAt} is null`; 73 + const whereCondition = cutoffDate 74 + ? and( 75 + eq(featureRequestComments.requestId, requestId), 76 + sql`${featureRequestComments.createdAt} <= ${cutoffDate}`, 77 + notHidden, 78 + ) 79 + : and(eq(featureRequestComments.requestId, requestId), notHidden); 80 + 81 + const voteCountCol = count(featureRequestCommentVotes.authorDid); 82 + const rows = db 83 + .select({ 84 + id: featureRequestComments.id, 85 + requestId: featureRequestComments.requestId, 86 + authorDid: featureRequestComments.authorDid, 87 + content: featureRequestComments.content, 88 + pdsUri: featureRequestComments.pdsUri, 89 + createdAt: featureRequestComments.createdAt, 90 + updatedAt: featureRequestComments.updatedAt, 91 + voteCount: voteCountCol, 92 + }) 93 + .from(featureRequestComments) 94 + .leftJoin( 95 + featureRequestCommentVotes, 96 + eq(featureRequestCommentVotes.commentId, featureRequestComments.id), 97 + ) 98 + .where(whereCondition) 99 + .groupBy(featureRequestComments.id) 100 + .orderBy( 101 + sortBy === "votes" ? orderDir(voteCountCol) : orderDir(featureRequestComments.createdAt), 102 + ) 103 + .all(); 104 + 105 + return c.json({ comments: rows }); 106 + }); 107 + 108 + // Create a comment (one top-level comment per user per request) 109 + app.post("/:id/comments", requireAuth, async (c) => { 110 + const requestId = c.req.param("id"); 111 + const body = await c.req.json(); 112 + const result = createCommentSchema.safeParse(body); 113 + if (!result.success) { 114 + return c.json({ error: z.flattenError(result.error) }, 400); 115 + } 116 + 117 + const { content, sphereSlug } = result.data; 118 + const db = getDb(); 119 + const did = c.var.did; 120 + 121 + // Check feature request exists and is visible 122 + const existing = db 123 + .select({ id: featureRequests.id, pdsUri: featureRequests.pdsUri }) 124 + .from(featureRequests) 125 + .where(and(eq(featureRequests.id, requestId), sql`${featureRequests.hiddenAt} is null`)) 126 + .get(); 127 + if (!existing) { 128 + return c.json({ error: "Feature request not found" }, 404); 129 + } 130 + 131 + // Enforce one top-level comment per user per request (ignore moderated comments) 132 + const alreadyCommented = db 133 + .select({ id: featureRequestComments.id }) 134 + .from(featureRequestComments) 135 + .where( 136 + and( 137 + eq(featureRequestComments.requestId, requestId), 138 + eq(featureRequestComments.authorDid, did), 139 + sql`${featureRequestComments.hiddenAt} is null`, 140 + ), 141 + ) 142 + .get(); 143 + if (alreadyCommented) { 144 + return c.json({ error: "You have already commented on this request" }, 409); 145 + } 146 + 147 + const id = generateRkey(); 148 + let pdsUri: string | null = null; 149 + 150 + // Write to PDS for public spheres 151 + const sphere = db 152 + .select({ visibility: spheres.visibility }) 153 + .from(spheres) 154 + .where(eq(spheres.slug, sphereSlug)) 155 + .get(); 156 + 157 + if (sphere?.visibility === "public") { 158 + const session = c.var.session; 159 + const now = new Date().toISOString(); 160 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 161 + method: "POST", 162 + headers: { "Content-Type": "application/json" }, 163 + body: JSON.stringify({ 164 + repo: session.did, 165 + collection: COMMENT_COLLECTION, 166 + rkey: id, 167 + record: { 168 + $type: COMMENT_COLLECTION, 169 + subject: existing.pdsUri, 170 + content, 171 + createdAt: now, 172 + }, 173 + }), 174 + }); 175 + 176 + if (res.ok) { 177 + const data = (await res.json()) as { uri: string }; 178 + pdsUri = data.uri; 179 + } else { 180 + console.error( 181 + "[feature-requests] PDS comment write failed:", 182 + res.status, 183 + await res.text().catch(() => ""), 184 + ); 185 + } 186 + } 187 + 188 + insertComment({ id, requestId, authorDid: did, content, pdsUri }); 189 + 190 + const comment = db 191 + .select() 192 + .from(featureRequestComments) 193 + .where(eq(featureRequestComments.id, id)) 194 + .get(); 195 + 196 + return c.json({ comment }, 201); 197 + }); 198 + 199 + // Update own comment (author only) 200 + app.put("/comments/:id", requireAuth, async (c) => { 201 + const id = c.req.param("id"); 202 + const body = await c.req.json(); 203 + const result = updateCommentSchema.safeParse(body); 204 + if (!result.success) { 205 + return c.json({ error: z.flattenError(result.error) }, 400); 206 + } 207 + 208 + const { content, sphereSlug } = result.data; 209 + const db = getDb(); 210 + const did = c.var.did; 211 + 212 + const comment = db 213 + .select() 214 + .from(featureRequestComments) 215 + .where(eq(featureRequestComments.id, id)) 216 + .get(); 217 + if (!comment) { 218 + return c.json({ error: "Comment not found" }, 404); 219 + } 220 + if (comment.authorDid !== did) { 221 + return c.json({ error: "Forbidden" }, 403); 222 + } 223 + 224 + // Update on PDS if the comment was written there 225 + if (comment.pdsUri) { 226 + const session = c.var.session; 227 + const now = new Date().toISOString(); 228 + 229 + // Look up the parent feature request's pdsUri for the subject field 230 + const parent = db 231 + .select({ pdsUri: featureRequests.pdsUri }) 232 + .from(featureRequests) 233 + .where(eq(featureRequests.id, comment.requestId)) 234 + .get(); 235 + 236 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 237 + method: "POST", 238 + headers: { "Content-Type": "application/json" }, 239 + body: JSON.stringify({ 240 + repo: session.did, 241 + collection: COMMENT_COLLECTION, 242 + rkey: id, 243 + record: { 244 + $type: COMMENT_COLLECTION, 245 + subject: parent?.pdsUri, 246 + content, 247 + createdAt: comment.createdAt, 248 + updatedAt: now, 249 + }, 250 + }), 251 + }); 252 + 253 + if (!res.ok) { 254 + console.error( 255 + "[feature-requests] PDS comment update failed:", 256 + res.status, 257 + await res.text().catch(() => ""), 258 + ); 259 + } 260 + } 261 + 262 + updateComment(id, content); 263 + 264 + const updated = db 265 + .select() 266 + .from(featureRequestComments) 267 + .where(eq(featureRequestComments.id, id)) 268 + .get(); 269 + 270 + return c.json({ comment: updated }); 271 + }); 272 + 273 + // Delete own comment or admin-moderate a comment 274 + app.delete("/comments/:id", requireAuth, async (c) => { 275 + const id = c.req.param("id"); 276 + const db = getDb(); 277 + const did = c.var.did; 278 + 279 + const comment = db 280 + .select() 281 + .from(featureRequestComments) 282 + .where(eq(featureRequestComments.id, id)) 283 + .get(); 284 + if (!comment) { 285 + return c.json({ error: "Comment not found" }, 404); 286 + } 287 + 288 + const isAuthor = comment.authorDid === did; 289 + 290 + if (!isAuthor) { 291 + // Only admin/owner can moderate other users' comments 292 + const sphereSlug = c.req.query("sphereSlug"); 293 + if (!sphereSlug) { 294 + return c.json({ error: "Forbidden" }, 403); 295 + } 296 + const sphere = db 297 + .select({ id: spheres.id, ownerDid: spheres.ownerDid, pdsUri: spheres.pdsUri }) 298 + .from(spheres) 299 + .where(eq(spheres.slug, sphereSlug)) 300 + .get(); 301 + if (!sphere) { 302 + return c.json({ error: "Forbidden" }, 403); 303 + } 304 + const role = getActiveMemberRole(sphere.id, did); 305 + if (!isAdminOrOwner(role)) { 306 + return c.json({ error: "Forbidden" }, 403); 307 + } 308 + 309 + // Already moderated — no-op 310 + if (comment.hiddenAt) { 311 + return c.json({ ok: true }); 312 + } 313 + 314 + // Admin moderation — publish moderation record on admin's PDS, hide locally 315 + if (comment.pdsUri) { 316 + const sphereUri = sphere.pdsUri ?? `at://${sphere.ownerDid}/site.exosphere.sphere/self`; 317 + const session = c.var.session; 318 + const pdsUri = await putPdsRecord(session, MODERATION_COLLECTION, id, { 319 + sphere: sphereUri, 320 + subject: comment.pdsUri, 321 + action: "remove", 322 + createdAt: new Date().toISOString(), 323 + }); 324 + if (!pdsUri) { 325 + console.warn( 326 + "[feature-requests] Moderation PDS record failed for comment %s — hiding locally only", 327 + id, 328 + ); 329 + } 330 + } 331 + 332 + hideComment(id, did); 333 + 334 + return c.json({ ok: true }); 335 + } 336 + 337 + // Author deleting their own comment — remove from their PDS + delete from DB 338 + if (comment.pdsUri) { 339 + const session = c.var.session; 340 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.deleteRecord", { 341 + method: "POST", 342 + headers: { "Content-Type": "application/json" }, 343 + body: JSON.stringify({ 344 + repo: session.did, 345 + collection: COMMENT_COLLECTION, 346 + rkey: id, 347 + }), 348 + }); 349 + 350 + if (!res.ok) { 351 + console.error( 352 + "[feature-requests] PDS comment delete failed:", 353 + res.status, 354 + await res.text().catch(() => ""), 355 + ); 356 + } 357 + } 358 + 359 + deleteCommentCascade(id); 360 + 361 + return c.json({ ok: true }); 362 + }); 363 + 364 + // Admin/owner-only: unhide a moderated comment 365 + app.post("/comments/:id/unhide", requireAuth, async (c) => { 366 + const id = c.req.param("id"); 367 + const sphereSlug = c.req.query("sphereSlug"); 368 + if (!sphereSlug) { 369 + return c.json({ error: "sphereSlug query parameter is required" }, 400); 370 + } 371 + 372 + const db = getDb(); 373 + const did = c.var.did; 374 + 375 + const sphere = db 376 + .select({ id: spheres.id }) 377 + .from(spheres) 378 + .where(eq(spheres.slug, sphereSlug)) 379 + .get(); 380 + if (!sphere) { 381 + return c.json({ error: "Sphere not found" }, 404); 382 + } 383 + 384 + const role = getActiveMemberRole(sphere.id, did); 385 + if (!isAdminOrOwner(role)) { 386 + return c.json({ error: "Forbidden" }, 403); 387 + } 388 + 389 + const comment = db 390 + .select() 391 + .from(featureRequestComments) 392 + .where(eq(featureRequestComments.id, id)) 393 + .get(); 394 + if (!comment) { 395 + return c.json({ error: "Comment not found" }, 404); 396 + } 397 + if (!comment.hiddenAt) { 398 + return c.json({ ok: true }); 399 + } 400 + 401 + // Delete the moderation record from PDS if the current admin is the one who hid it 402 + if (comment.moderatedBy === did) { 403 + const session = c.var.session; 404 + await deletePdsRecord(session, MODERATION_COLLECTION, id); 405 + } 406 + 407 + unhideComment(id); 408 + 409 + return c.json({ ok: true }); 410 + }); 411 + 412 + // ---- Comment Votes ---- 413 + 414 + // Get current user's comment votes (list of comment IDs) 415 + app.get("/comments/votes", requireAuth, (c) => { 416 + const db = getDb(); 417 + const rows = db 418 + .select({ commentId: featureRequestCommentVotes.commentId }) 419 + .from(featureRequestCommentVotes) 420 + .where(eq(featureRequestCommentVotes.authorDid, c.var.did)) 421 + .all(); 422 + return c.json({ votes: rows.map((r) => r.commentId) }); 423 + }); 424 + 425 + // Cast a vote on a comment 426 + app.post("/comments/:id/vote", requireAuth, async (c) => { 427 + const id = c.req.param("id"); 428 + const db = getDb(); 429 + const did = c.var.did; 430 + 431 + const comment = db 432 + .select({ 433 + id: featureRequestComments.id, 434 + pdsUri: featureRequestComments.pdsUri, 435 + }) 436 + .from(featureRequestComments) 437 + .where(eq(featureRequestComments.id, id)) 438 + .get(); 439 + if (!comment) { 440 + return c.json({ error: "Comment not found" }, 404); 441 + } 442 + 443 + const alreadyVoted = db 444 + .select({ commentId: featureRequestCommentVotes.commentId }) 445 + .from(featureRequestCommentVotes) 446 + .where( 447 + and( 448 + eq(featureRequestCommentVotes.commentId, id), 449 + eq(featureRequestCommentVotes.authorDid, did), 450 + ), 451 + ) 452 + .get(); 453 + if (alreadyVoted) { 454 + return c.json({ error: "Already voted" }, 409); 455 + } 456 + 457 + const sphereSlug = c.req.query("sphereSlug"); 458 + let votePdsUri: string | null = null; 459 + 460 + if (sphereSlug) { 461 + const sphere = db 462 + .select({ visibility: spheres.visibility }) 463 + .from(spheres) 464 + .where(eq(spheres.slug, sphereSlug)) 465 + .get(); 466 + 467 + if (sphere?.visibility === "public" && comment.pdsUri) { 468 + const session = c.var.session; 469 + const now = new Date().toISOString(); 470 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 471 + method: "POST", 472 + headers: { "Content-Type": "application/json" }, 473 + body: JSON.stringify({ 474 + repo: session.did, 475 + collection: COMMENT_VOTE_COLLECTION, 476 + rkey: id, 477 + record: { 478 + $type: COMMENT_VOTE_COLLECTION, 479 + subject: comment.pdsUri, 480 + createdAt: now, 481 + }, 482 + }), 483 + }); 484 + 485 + if (res.ok) { 486 + const data = (await res.json()) as { uri: string }; 487 + votePdsUri = data.uri; 488 + } else { 489 + console.error( 490 + "[feature-requests] PDS comment vote write failed:", 491 + res.status, 492 + await res.text().catch(() => ""), 493 + ); 494 + } 495 + } 496 + } 497 + 498 + insertCommentVote(id, did, votePdsUri); 499 + 500 + const result = db 501 + .select({ voteCount: count() }) 502 + .from(featureRequestCommentVotes) 503 + .where(eq(featureRequestCommentVotes.commentId, id)) 504 + .get(); 505 + 506 + return c.json({ voteCount: result?.voteCount ?? 0 }, 201); 507 + }); 508 + 509 + // Remove a vote from a comment 510 + app.delete("/comments/:id/vote", requireAuth, async (c) => { 511 + const id = c.req.param("id"); 512 + const db = getDb(); 513 + const did = c.var.did; 514 + 515 + const vote = db 516 + .select({ pdsUri: featureRequestCommentVotes.pdsUri }) 517 + .from(featureRequestCommentVotes) 518 + .where( 519 + and( 520 + eq(featureRequestCommentVotes.commentId, id), 521 + eq(featureRequestCommentVotes.authorDid, did), 522 + ), 523 + ) 524 + .get(); 525 + if (!vote) { 526 + return c.json({ error: "Vote not found" }, 404); 527 + } 528 + 529 + if (vote.pdsUri) { 530 + const session = c.var.session; 531 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.deleteRecord", { 532 + method: "POST", 533 + headers: { "Content-Type": "application/json" }, 534 + body: JSON.stringify({ 535 + repo: session.did, 536 + collection: COMMENT_VOTE_COLLECTION, 537 + rkey: id, 538 + }), 539 + }); 540 + 541 + if (!res.ok) { 542 + console.error( 543 + "[feature-requests] PDS comment vote delete failed:", 544 + res.status, 545 + await res.text().catch(() => ""), 546 + ); 547 + } 548 + } 549 + 550 + deleteCommentVoteByAuthor(id, did); 551 + 552 + const result = db 553 + .select({ voteCount: count() }) 554 + .from(featureRequestCommentVotes) 555 + .where(eq(featureRequestCommentVotes.commentId, id)) 556 + .get(); 557 + 558 + return c.json({ voteCount: result?.voteCount ?? 0 }); 559 + }); 560 + 561 + export { app as commentsApi };
+404
packages/feature-requests/src/api/requests.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { getDb } from "@exosphere/core/db"; 4 + import { eq, and, count, sql, inArray, like, desc, asc } from "@exosphere/core/db/drizzle"; 5 + import { requireAuth, type AuthEnv } from "@exosphere/core/auth"; 6 + import { getActiveMemberRole, isAdminOrOwner } from "@exosphere/core/sphere"; 7 + import { putPdsRecord, deletePdsRecord, generateRkey } from "@exosphere/core/pds"; 8 + import { spheres } from "@exosphere/core/db/schema"; 9 + import { featureRequests, featureRequestVotes, featureRequestComments } from "../db/schema.ts"; 10 + import { createFeatureRequestSchema, statuses } from "../schemas/feature-request.ts"; 11 + import type { Status } from "../schemas/feature-request.ts"; 12 + import { 13 + insertFeatureRequest, 14 + deleteFeatureRequestCascade, 15 + hideFeatureRequest, 16 + unhideFeatureRequest, 17 + } from "../db/operations.ts"; 18 + 19 + const COLLECTION = "site.exosphere.featureRequest"; 20 + const MODERATION_COLLECTION = "site.exosphere.moderation"; 21 + 22 + const app = new Hono<AuthEnv>(); 23 + 24 + app.get("/:number{[0-9]+}", (c) => { 25 + const number = parseInt(c.req.param("number"), 10); 26 + const db = getDb(); 27 + const row = db 28 + .select({ 29 + id: featureRequests.id, 30 + number: featureRequests.number, 31 + authorDid: featureRequests.authorDid, 32 + title: featureRequests.title, 33 + description: featureRequests.description, 34 + category: featureRequests.category, 35 + status: featureRequests.status, 36 + duplicateOfId: featureRequests.duplicateOfId, 37 + pdsUri: featureRequests.pdsUri, 38 + createdAt: featureRequests.createdAt, 39 + updatedAt: featureRequests.updatedAt, 40 + voteCount: count(featureRequestVotes.authorDid), 41 + commentCount: 42 + sql<number>`(select count(*) from ${featureRequestComments} where ${featureRequestComments.requestId} = ${featureRequests.id} and ${featureRequestComments.hiddenAt} is null)`.as( 43 + "comment_count", 44 + ), 45 + }) 46 + .from(featureRequests) 47 + .leftJoin(featureRequestVotes, eq(featureRequestVotes.requestId, featureRequests.id)) 48 + .where(and(eq(featureRequests.number, number), sql`${featureRequests.hiddenAt} is null`)) 49 + .groupBy(featureRequests.id) 50 + .get(); 51 + 52 + if (!row) { 53 + return c.json({ error: "Feature request not found" }, 404); 54 + } 55 + 56 + // If this FR is a duplicate, fetch parent info 57 + let duplicateOf: { id: string; number: number; title: string } | null = null; 58 + if (row.duplicateOfId) { 59 + const parent = db 60 + .select({ 61 + id: featureRequests.id, 62 + number: featureRequests.number, 63 + title: featureRequests.title, 64 + }) 65 + .from(featureRequests) 66 + .where(eq(featureRequests.id, row.duplicateOfId)) 67 + .get(); 68 + duplicateOf = parent ?? null; 69 + } 70 + 71 + // Count how many FRs are duplicates of this one 72 + const dupCountRow = db 73 + .select({ count: count() }) 74 + .from(featureRequests) 75 + .where(and(eq(featureRequests.duplicateOfId, row.id), sql`${featureRequests.hiddenAt} is null`)) 76 + .get(); 77 + const duplicateCount = dupCountRow?.count ?? 0; 78 + 79 + return c.json({ featureRequest: row, duplicateOf, duplicateCount }); 80 + }); 81 + 82 + app.get("/", (c) => { 83 + const db = getDb(); 84 + const statusParam = c.req.query("status"); 85 + const validStatuses = new Set<string>(statuses); 86 + const statusFilter = statusParam 87 + ? (statusParam.split(",").filter((s) => validStatuses.has(s)) as Status[]) 88 + : null; 89 + 90 + const sortParam = c.req.query("sort"); 91 + const orderParam = c.req.query("order"); 92 + const sortBy = sortParam === "votes" ? "votes" : "date"; 93 + const orderDir = orderParam === "asc" ? asc : desc; 94 + 95 + const conditions = [sql`${featureRequests.hiddenAt} is null`]; 96 + if (statusFilter && statusFilter.length > 0) { 97 + conditions.push(inArray(featureRequests.status, statusFilter)); 98 + } 99 + 100 + const voteCountCol = count(featureRequestVotes.authorDid); 101 + const rows = db 102 + .select({ 103 + id: featureRequests.id, 104 + number: featureRequests.number, 105 + authorDid: featureRequests.authorDid, 106 + title: featureRequests.title, 107 + description: featureRequests.description, 108 + category: featureRequests.category, 109 + status: featureRequests.status, 110 + duplicateOfId: featureRequests.duplicateOfId, 111 + pdsUri: featureRequests.pdsUri, 112 + createdAt: featureRequests.createdAt, 113 + updatedAt: featureRequests.updatedAt, 114 + voteCount: voteCountCol, 115 + commentCount: 116 + sql<number>`(select count(*) from ${featureRequestComments} where ${featureRequestComments.requestId} = ${featureRequests.id} and ${featureRequestComments.hiddenAt} is null)`.as( 117 + "comment_count", 118 + ), 119 + }) 120 + .from(featureRequests) 121 + .leftJoin(featureRequestVotes, eq(featureRequestVotes.requestId, featureRequests.id)) 122 + .where(and(...conditions)) 123 + .groupBy(featureRequests.id) 124 + .orderBy(sortBy === "votes" ? orderDir(voteCountCol) : orderDir(featureRequests.createdAt)) 125 + .all(); 126 + return c.json({ featureRequests: rows }); 127 + }); 128 + 129 + app.post("/", requireAuth, async (c) => { 130 + const body = await c.req.json(); 131 + const result = createFeatureRequestSchema.safeParse(body); 132 + if (!result.success) { 133 + return c.json({ error: z.flattenError(result.error) }, 400); 134 + } 135 + 136 + const { title, description, category, sphereSlug } = result.data; 137 + const db = getDb(); 138 + const id = generateRkey(); 139 + const did = c.var.did; 140 + 141 + // Check sphere visibility 142 + const sphere = db 143 + .select({ visibility: spheres.visibility }) 144 + .from(spheres) 145 + .where(eq(spheres.slug, sphereSlug)) 146 + .get(); 147 + 148 + if (!sphere) { 149 + return c.json({ error: "Sphere not found" }, 404); 150 + } 151 + 152 + let pdsUri: string | null = null; 153 + 154 + // Write to PDS for public spheres 155 + if (sphere.visibility === "public") { 156 + const session = c.var.session; 157 + const now = new Date().toISOString(); 158 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 159 + method: "POST", 160 + headers: { "Content-Type": "application/json" }, 161 + body: JSON.stringify({ 162 + repo: session.did, 163 + collection: COLLECTION, 164 + rkey: id, 165 + record: { 166 + $type: COLLECTION, 167 + title, 168 + description, 169 + category, 170 + sphereSlug, 171 + createdAt: now, 172 + }, 173 + }), 174 + }); 175 + 176 + if (res.ok) { 177 + const data = (await res.json()) as { uri: string }; 178 + pdsUri = data.uri; 179 + } else { 180 + console.error( 181 + "[feature-requests] PDS write failed:", 182 + res.status, 183 + await res.text().catch(() => ""), 184 + ); 185 + } 186 + } 187 + 188 + const row = insertFeatureRequest({ id, authorDid: did, title, description, category, pdsUri }); 189 + 190 + return c.json({ featureRequest: row }, 201); 191 + }); 192 + 193 + // Author-only: delete own feature request (local DB + PDS) 194 + app.delete("/:id", requireAuth, async (c) => { 195 + const id = c.req.param("id"); 196 + const db = getDb(); 197 + const did = c.var.did; 198 + 199 + const existing = db 200 + .select({ 201 + id: featureRequests.id, 202 + authorDid: featureRequests.authorDid, 203 + pdsUri: featureRequests.pdsUri, 204 + }) 205 + .from(featureRequests) 206 + .where(eq(featureRequests.id, id)) 207 + .get(); 208 + if (!existing) { 209 + return c.json({ error: "Feature request not found" }, 404); 210 + } 211 + 212 + if (existing.authorDid !== did) { 213 + return c.json({ error: "Forbidden" }, 403); 214 + } 215 + 216 + // Delete from PDS if the record was written there 217 + if (existing.pdsUri) { 218 + const session = c.var.session; 219 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.deleteRecord", { 220 + method: "POST", 221 + headers: { "Content-Type": "application/json" }, 222 + body: JSON.stringify({ 223 + repo: session.did, 224 + collection: COLLECTION, 225 + rkey: id, 226 + }), 227 + }); 228 + 229 + if (!res.ok) { 230 + console.error( 231 + "[feature-requests] PDS delete failed:", 232 + res.status, 233 + await res.text().catch(() => ""), 234 + ); 235 + } 236 + } 237 + 238 + deleteFeatureRequestCascade(id); 239 + 240 + return c.json({ ok: true }); 241 + }); 242 + 243 + // Admin/owner-only: hide a feature request 244 + app.post("/:id/hide", requireAuth, async (c) => { 245 + const id = c.req.param("id"); 246 + const sphereSlug = c.req.query("sphereSlug"); 247 + if (!sphereSlug) { 248 + return c.json({ error: "sphereSlug query parameter is required" }, 400); 249 + } 250 + 251 + const db = getDb(); 252 + const did = c.var.did; 253 + 254 + const sphere = db 255 + .select({ id: spheres.id, ownerDid: spheres.ownerDid, pdsUri: spheres.pdsUri }) 256 + .from(spheres) 257 + .where(eq(spheres.slug, sphereSlug)) 258 + .get(); 259 + if (!sphere) { 260 + return c.json({ error: "Sphere not found" }, 404); 261 + } 262 + 263 + const role = getActiveMemberRole(sphere.id, did); 264 + if (!isAdminOrOwner(role)) { 265 + return c.json({ error: "Forbidden" }, 403); 266 + } 267 + 268 + const existing = db 269 + .select({ 270 + id: featureRequests.id, 271 + pdsUri: featureRequests.pdsUri, 272 + hiddenAt: featureRequests.hiddenAt, 273 + }) 274 + .from(featureRequests) 275 + .where(eq(featureRequests.id, id)) 276 + .get(); 277 + if (!existing) { 278 + return c.json({ error: "Feature request not found" }, 404); 279 + } 280 + 281 + // Already hidden — no-op 282 + if (existing.hiddenAt) { 283 + return c.json({ ok: true }); 284 + } 285 + 286 + // Publish moderation record on admin's PDS 287 + if (existing.pdsUri) { 288 + const sphereUri = sphere.pdsUri ?? `at://${sphere.ownerDid}/site.exosphere.sphere/self`; 289 + const session = c.var.session; 290 + const pdsUri = await putPdsRecord(session, MODERATION_COLLECTION, id, { 291 + sphere: sphereUri, 292 + subject: existing.pdsUri, 293 + action: "remove", 294 + createdAt: new Date().toISOString(), 295 + }); 296 + if (!pdsUri) { 297 + console.warn( 298 + "[feature-requests] Moderation PDS record failed for feature request %s — hiding locally only", 299 + id, 300 + ); 301 + } 302 + } 303 + 304 + hideFeatureRequest(id, did); 305 + 306 + return c.json({ ok: true }); 307 + }); 308 + 309 + // Admin/owner-only: unhide a feature request 310 + app.post("/:id/unhide", requireAuth, async (c) => { 311 + const id = c.req.param("id"); 312 + const sphereSlug = c.req.query("sphereSlug"); 313 + if (!sphereSlug) { 314 + return c.json({ error: "sphereSlug query parameter is required" }, 400); 315 + } 316 + 317 + const db = getDb(); 318 + const did = c.var.did; 319 + 320 + const sphere = db 321 + .select({ id: spheres.id }) 322 + .from(spheres) 323 + .where(eq(spheres.slug, sphereSlug)) 324 + .get(); 325 + if (!sphere) { 326 + return c.json({ error: "Sphere not found" }, 404); 327 + } 328 + 329 + const role = getActiveMemberRole(sphere.id, did); 330 + if (!isAdminOrOwner(role)) { 331 + return c.json({ error: "Forbidden" }, 403); 332 + } 333 + 334 + const existing = db 335 + .select({ 336 + id: featureRequests.id, 337 + hiddenAt: featureRequests.hiddenAt, 338 + moderatedBy: featureRequests.moderatedBy, 339 + }) 340 + .from(featureRequests) 341 + .where(eq(featureRequests.id, id)) 342 + .get(); 343 + if (!existing) { 344 + return c.json({ error: "Feature request not found" }, 404); 345 + } 346 + 347 + if (!existing.hiddenAt) { 348 + return c.json({ ok: true }); 349 + } 350 + 351 + // Delete the moderation record from PDS if the current admin is the one who hid it 352 + if (existing.moderatedBy === did) { 353 + const session = c.var.session; 354 + await deletePdsRecord(session, MODERATION_COLLECTION, id); 355 + } 356 + 357 + unhideFeatureRequest(id); 358 + 359 + return c.json({ ok: true }); 360 + }); 361 + 362 + // ---- Search ---- 363 + 364 + app.get("/search", (c) => { 365 + const q = c.req.query("q")?.trim(); 366 + const excludeId = c.req.query("excludeId"); 367 + if (!q) { 368 + return c.json({ results: [] }); 369 + } 370 + 371 + const db = getDb(); 372 + const conditions = [sql`${featureRequests.hiddenAt} is null`]; 373 + if (excludeId) { 374 + conditions.push(sql`${featureRequests.id} != ${excludeId}`); 375 + } 376 + 377 + // Search by number if q starts with # or is purely numeric, otherwise by title 378 + const hashMatch = q.startsWith("#") ? q.slice(1) : null; 379 + const numberVal = parseInt(hashMatch ?? q, 10); 380 + if (hashMatch !== null && !isNaN(numberVal)) { 381 + conditions.push(eq(featureRequests.number, numberVal)); 382 + } else if (!isNaN(numberVal) && String(numberVal) === q) { 383 + conditions.push(eq(featureRequests.number, numberVal)); 384 + } else { 385 + const escaped = q.replace(/[%_]/g, "\\$&"); 386 + conditions.push(like(featureRequests.title, `%${escaped}%`)); 387 + } 388 + 389 + const rows = db 390 + .select({ 391 + id: featureRequests.id, 392 + number: featureRequests.number, 393 + title: featureRequests.title, 394 + status: featureRequests.status, 395 + }) 396 + .from(featureRequests) 397 + .where(and(...conditions)) 398 + .limit(10) 399 + .all(); 400 + 401 + return c.json({ results: rows }); 402 + }); 403 + 404 + export { app as requestsApi };
+15
packages/feature-requests/src/api/routes.ts
··· 1 + import { Hono } from "hono"; 2 + import type { AuthEnv } from "@exosphere/core/auth"; 3 + import { requestsApi } from "./requests.ts"; 4 + import { statusesApi } from "./statuses.ts"; 5 + import { votesApi } from "./votes.ts"; 6 + import { commentsApi } from "./comments.ts"; 7 + 8 + const app = new Hono<AuthEnv>(); 9 + 10 + app.route("/", requestsApi); 11 + app.route("/", statusesApi); 12 + app.route("/", votesApi); 13 + app.route("/", commentsApi); 14 + 15 + export { app as featureRequestsApi };
+256
packages/feature-requests/src/api/statuses.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { getDb } from "@exosphere/core/db"; 4 + import { eq, and, count, sql } from "@exosphere/core/db/drizzle"; 5 + import { requireAuth, type AuthEnv } from "@exosphere/core/auth"; 6 + import { getActiveMemberRole, isAdminOrOwner } from "@exosphere/core/sphere"; 7 + import { generateRkey } from "@exosphere/core/pds"; 8 + import { spheres } from "@exosphere/core/db/schema"; 9 + import { featureRequests, featureRequestStatuses } from "../db/schema.ts"; 10 + import { updateStatusSchema, markAsDuplicateSchema } from "../schemas/feature-request.ts"; 11 + import { insertStatusAndUpdateFR } from "../db/operations.ts"; 12 + 13 + const STATUS_COLLECTION = "site.exosphere.featureRequestStatus"; 14 + 15 + const app = new Hono<AuthEnv>(); 16 + 17 + // ---- Duplicates ---- 18 + 19 + // Mark a feature request as duplicate of another 20 + app.post("/:id/duplicate", requireAuth, async (c) => { 21 + const id = c.req.param("id"); 22 + const body = await c.req.json(); 23 + const result = markAsDuplicateSchema.safeParse(body); 24 + if (!result.success) { 25 + return c.json({ error: z.flattenError(result.error) }, 400); 26 + } 27 + 28 + const { duplicateOfId, sphereSlug } = result.data; 29 + const db = getDb(); 30 + const did = c.var.did; 31 + 32 + // Check FR exists, is "requested", and not hidden 33 + const fr = db 34 + .select({ 35 + id: featureRequests.id, 36 + authorDid: featureRequests.authorDid, 37 + status: featureRequests.status, 38 + pdsUri: featureRequests.pdsUri, 39 + }) 40 + .from(featureRequests) 41 + .where(and(eq(featureRequests.id, id), sql`${featureRequests.hiddenAt} is null`)) 42 + .get(); 43 + 44 + if (!fr) { 45 + return c.json({ error: "Feature request not found" }, 404); 46 + } 47 + if (fr.status !== "requested") { 48 + return c.json({ error: "Only requested feature requests can be marked as duplicate" }, 400); 49 + } 50 + 51 + // Permission: admin/owner OR author 52 + const sphere = db 53 + .select({ id: spheres.id, visibility: spheres.visibility }) 54 + .from(spheres) 55 + .where(eq(spheres.slug, sphereSlug)) 56 + .get(); 57 + if (!sphere) { 58 + return c.json({ error: "Sphere not found" }, 404); 59 + } 60 + 61 + const role = getActiveMemberRole(sphere.id, did); 62 + const canMark = isAdminOrOwner(role) || fr.authorDid === did; 63 + if (!canMark) { 64 + return c.json({ error: "Forbidden" }, 403); 65 + } 66 + 67 + // Check target exists and is not the same FR, not hidden 68 + if (duplicateOfId === id) { 69 + return c.json({ error: "Cannot mark as duplicate of itself" }, 400); 70 + } 71 + 72 + const target = db 73 + .select({ id: featureRequests.id }) 74 + .from(featureRequests) 75 + .where(and(eq(featureRequests.id, duplicateOfId), sql`${featureRequests.hiddenAt} is null`)) 76 + .get(); 77 + if (!target) { 78 + return c.json({ error: "Target feature request not found" }, 404); 79 + } 80 + 81 + // Write to PDS for public spheres 82 + let pdsUri: string | null = null; 83 + if (sphere.visibility === "public" && fr.pdsUri) { 84 + const session = c.var.session; 85 + const now = new Date().toISOString(); 86 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 87 + method: "POST", 88 + headers: { "Content-Type": "application/json" }, 89 + body: JSON.stringify({ 90 + repo: session.did, 91 + collection: STATUS_COLLECTION, 92 + rkey: id, 93 + record: { 94 + $type: STATUS_COLLECTION, 95 + subject: fr.pdsUri, 96 + status: "duplicate", 97 + sphereSlug, 98 + createdAt: now, 99 + }, 100 + }), 101 + }); 102 + 103 + if (res.ok) { 104 + const data = (await res.json()) as { uri: string }; 105 + pdsUri = data.uri; 106 + } else { 107 + console.error( 108 + "[feature-requests] PDS duplicate status write failed:", 109 + res.status, 110 + await res.text().catch(() => ""), 111 + ); 112 + } 113 + } 114 + 115 + const statusId = generateRkey(); 116 + insertStatusAndUpdateFR({ 117 + id: statusId, 118 + requestId: id, 119 + authorDid: did, 120 + status: "duplicate", 121 + pdsUri, 122 + duplicateOfId, 123 + }); 124 + 125 + return c.json({ ok: true }); 126 + }); 127 + 128 + // Get FRs that are duplicates of a given FR 129 + app.get("/:id/duplicates", (c) => { 130 + const id = c.req.param("id"); 131 + const db = getDb(); 132 + 133 + const rows = db 134 + .select({ 135 + id: featureRequests.id, 136 + number: featureRequests.number, 137 + title: featureRequests.title, 138 + }) 139 + .from(featureRequests) 140 + .where(and(eq(featureRequests.duplicateOfId, id), sql`${featureRequests.hiddenAt} is null`)) 141 + .all(); 142 + 143 + return c.json({ duplicates: rows }); 144 + }); 145 + 146 + // ---- Statuses ---- 147 + 148 + // Admin/owner-only: set status on a feature request 149 + app.post("/:id/status", requireAuth, async (c) => { 150 + const id = c.req.param("id"); 151 + const body = await c.req.json(); 152 + const result = updateStatusSchema.safeParse(body); 153 + if (!result.success) { 154 + return c.json({ error: z.flattenError(result.error) }, 400); 155 + } 156 + 157 + const { status, sphereSlug } = result.data; 158 + const db = getDb(); 159 + const did = c.var.did; 160 + 161 + const existing = db 162 + .select({ 163 + id: featureRequests.id, 164 + status: featureRequests.status, 165 + pdsUri: featureRequests.pdsUri, 166 + }) 167 + .from(featureRequests) 168 + .where(eq(featureRequests.id, id)) 169 + .get(); 170 + if (!existing) { 171 + return c.json({ error: "Feature request not found" }, 404); 172 + } 173 + 174 + const sphere = db 175 + .select({ id: spheres.id, visibility: spheres.visibility }) 176 + .from(spheres) 177 + .where(eq(spheres.slug, sphereSlug)) 178 + .get(); 179 + if (!sphere) { 180 + return c.json({ error: "Sphere not found" }, 404); 181 + } 182 + 183 + const role = getActiveMemberRole(sphere.id, did); 184 + if (!isAdminOrOwner(role)) { 185 + return c.json({ error: "Forbidden" }, 403); 186 + } 187 + 188 + let pdsUri: string | null = null; 189 + 190 + // Write to PDS for public spheres 191 + if (sphere.visibility === "public" && existing.pdsUri) { 192 + const session = c.var.session; 193 + const now = new Date().toISOString(); 194 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 195 + method: "POST", 196 + headers: { "Content-Type": "application/json" }, 197 + body: JSON.stringify({ 198 + repo: session.did, 199 + collection: STATUS_COLLECTION, 200 + rkey: id, 201 + record: { 202 + $type: STATUS_COLLECTION, 203 + subject: existing.pdsUri, 204 + status, 205 + sphereSlug, 206 + createdAt: now, 207 + }, 208 + }), 209 + }); 210 + 211 + if (res.ok) { 212 + const data = (await res.json()) as { uri: string }; 213 + pdsUri = data.uri; 214 + } else { 215 + console.error( 216 + "[feature-requests] PDS status write failed:", 217 + res.status, 218 + await res.text().catch(() => ""), 219 + ); 220 + } 221 + } 222 + 223 + const statusId = generateRkey(); 224 + insertStatusAndUpdateFR({ 225 + id: statusId, 226 + requestId: id, 227 + authorDid: did, 228 + status, 229 + pdsUri, 230 + clearDuplicateOfId: existing.status === "duplicate", 231 + }); 232 + 233 + return c.json({ status }); 234 + }); 235 + 236 + // Get status change history for a feature request 237 + app.get("/:id/statuses", (c) => { 238 + const id = c.req.param("id"); 239 + const db = getDb(); 240 + 241 + const rows = db 242 + .select({ 243 + id: featureRequestStatuses.id, 244 + authorDid: featureRequestStatuses.authorDid, 245 + status: featureRequestStatuses.status, 246 + createdAt: featureRequestStatuses.createdAt, 247 + }) 248 + .from(featureRequestStatuses) 249 + .where(eq(featureRequestStatuses.requestId, id)) 250 + .orderBy(sql`${featureRequestStatuses.createdAt} desc`) 251 + .all(); 252 + 253 + return c.json({ statuses: rows }); 254 + }); 255 + 256 + export { app as statusesApi };
+153
packages/feature-requests/src/api/votes.ts
··· 1 + import { Hono } from "hono"; 2 + import { getDb } from "@exosphere/core/db"; 3 + import { eq, and, count, sql } from "@exosphere/core/db/drizzle"; 4 + import { requireAuth, type AuthEnv } from "@exosphere/core/auth"; 5 + import { spheres } from "@exosphere/core/db/schema"; 6 + import { featureRequests, featureRequestVotes } from "../db/schema.ts"; 7 + import { insertVote, deleteVoteByAuthor } from "../db/operations.ts"; 8 + 9 + const VOTE_COLLECTION = "site.exosphere.featureRequestVote"; 10 + 11 + const app = new Hono<AuthEnv>(); 12 + 13 + // Get current user's votes (list of request IDs) 14 + app.get("/votes", requireAuth, (c) => { 15 + const db = getDb(); 16 + const rows = db 17 + .select({ requestId: featureRequestVotes.requestId }) 18 + .from(featureRequestVotes) 19 + .where(eq(featureRequestVotes.authorDid, c.var.did)) 20 + .all(); 21 + return c.json({ votes: rows.map((r) => r.requestId) }); 22 + }); 23 + 24 + // Cast a vote on a feature request 25 + app.post("/:id/vote", requireAuth, async (c) => { 26 + const id = c.req.param("id"); 27 + const db = getDb(); 28 + const did = c.var.did; 29 + 30 + const existing = db 31 + .select({ 32 + id: featureRequests.id, 33 + pdsUri: featureRequests.pdsUri, 34 + }) 35 + .from(featureRequests) 36 + .where(and(eq(featureRequests.id, id), sql`${featureRequests.hiddenAt} is null`)) 37 + .get(); 38 + if (!existing) { 39 + return c.json({ error: "Feature request not found" }, 404); 40 + } 41 + 42 + // Check if already voted 43 + const alreadyVoted = db 44 + .select({ requestId: featureRequestVotes.requestId }) 45 + .from(featureRequestVotes) 46 + .where(and(eq(featureRequestVotes.requestId, id), eq(featureRequestVotes.authorDid, did))) 47 + .get(); 48 + if (alreadyVoted) { 49 + return c.json({ error: "Already voted" }, 409); 50 + } 51 + 52 + const sphereSlug = c.req.query("sphereSlug"); 53 + let votePdsUri: string | null = null; 54 + 55 + // Write to PDS for public spheres 56 + if (sphereSlug) { 57 + const sphere = db 58 + .select({ visibility: spheres.visibility }) 59 + .from(spheres) 60 + .where(eq(spheres.slug, sphereSlug)) 61 + .get(); 62 + 63 + if (sphere?.visibility === "public" && existing.pdsUri) { 64 + const session = c.var.session; 65 + const now = new Date().toISOString(); 66 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.putRecord", { 67 + method: "POST", 68 + headers: { "Content-Type": "application/json" }, 69 + body: JSON.stringify({ 70 + repo: session.did, 71 + collection: VOTE_COLLECTION, 72 + rkey: id, 73 + record: { 74 + $type: VOTE_COLLECTION, 75 + subject: existing.pdsUri, 76 + createdAt: now, 77 + }, 78 + }), 79 + }); 80 + 81 + if (res.ok) { 82 + const data = (await res.json()) as { uri: string }; 83 + votePdsUri = data.uri; 84 + } else { 85 + console.error( 86 + "[feature-requests] PDS vote write failed:", 87 + res.status, 88 + await res.text().catch(() => ""), 89 + ); 90 + } 91 + } 92 + } 93 + 94 + insertVote(id, did, votePdsUri); 95 + 96 + const result = db 97 + .select({ voteCount: count() }) 98 + .from(featureRequestVotes) 99 + .where(eq(featureRequestVotes.requestId, id)) 100 + .get(); 101 + 102 + return c.json({ voteCount: result?.voteCount ?? 0 }, 201); 103 + }); 104 + 105 + // Remove a vote from a feature request 106 + app.delete("/:id/vote", requireAuth, async (c) => { 107 + const id = c.req.param("id"); 108 + const db = getDb(); 109 + const did = c.var.did; 110 + 111 + const vote = db 112 + .select({ pdsUri: featureRequestVotes.pdsUri }) 113 + .from(featureRequestVotes) 114 + .where(and(eq(featureRequestVotes.requestId, id), eq(featureRequestVotes.authorDid, did))) 115 + .get(); 116 + if (!vote) { 117 + return c.json({ error: "Vote not found" }, 404); 118 + } 119 + 120 + // Delete from PDS if the vote was written there 121 + if (vote.pdsUri) { 122 + const session = c.var.session; 123 + const res = await session.fetchHandler("/xrpc/com.atproto.repo.deleteRecord", { 124 + method: "POST", 125 + headers: { "Content-Type": "application/json" }, 126 + body: JSON.stringify({ 127 + repo: session.did, 128 + collection: VOTE_COLLECTION, 129 + rkey: id, 130 + }), 131 + }); 132 + 133 + if (!res.ok) { 134 + console.error( 135 + "[feature-requests] PDS vote delete failed:", 136 + res.status, 137 + await res.text().catch(() => ""), 138 + ); 139 + } 140 + } 141 + 142 + deleteVoteByAuthor(id, did); 143 + 144 + const result = db 145 + .select({ voteCount: count() }) 146 + .from(featureRequestVotes) 147 + .where(eq(featureRequestVotes.requestId, id)) 148 + .get(); 149 + 150 + return c.json({ voteCount: result?.voteCount ?? 0 }); 151 + }); 152 + 153 + export { app as votesApi };
+18
packages/feature-requests/src/client.ssr.ts
··· 1 + import type { ClientModule } from "@exosphere/client/types"; 2 + import { 3 + FeatureRequestsActivePage, 4 + FeatureRequestsDonePage, 5 + FeatureRequestsNotPlannedPage, 6 + } from "./ui/pages/feature-requests.tsx"; 7 + import { FeatureRequestPage } from "./ui/pages/feature-request.tsx"; 8 + 9 + export const featureRequestsModule: ClientModule = { 10 + name: "infuse", 11 + // Static paths before parameterized — preact-iso matches first hit 12 + routes: [ 13 + { path: "/infuse", component: FeatureRequestsActivePage }, 14 + { path: "/infuse/done", component: FeatureRequestsDonePage }, 15 + { path: "/infuse/not-planned", component: FeatureRequestsNotPlannedPage }, 16 + { path: "/infuse/:number", component: FeatureRequestPage }, 17 + ], 18 + };
+29
packages/feature-requests/src/client.ts
··· 1 + import { lazy } from "@exosphere/client/router"; 2 + import type { ClientModule } from "@exosphere/client/types"; 3 + 4 + const FeatureRequestsActivePage = lazy(() => 5 + import("./ui/pages/feature-requests.tsx").then((m) => m.FeatureRequestsActivePage), 6 + ); 7 + 8 + const FeatureRequestPage = lazy(() => 9 + import("./ui/pages/feature-request.tsx").then((m) => m.FeatureRequestPage), 10 + ); 11 + 12 + const FeatureRequestsDonePage = lazy(() => 13 + import("./ui/pages/feature-requests.tsx").then((m) => m.FeatureRequestsDonePage), 14 + ); 15 + 16 + const FeatureRequestsNotPlannedPage = lazy(() => 17 + import("./ui/pages/feature-requests.tsx").then((m) => m.FeatureRequestsNotPlannedPage), 18 + ); 19 + 20 + export const featureRequestsModule: ClientModule = { 21 + name: "infuse", 22 + // Static paths before parameterized — preact-iso matches first hit 23 + routes: [ 24 + { path: "/infuse", component: FeatureRequestsActivePage }, 25 + { path: "/infuse/done", component: FeatureRequestsDonePage }, 26 + { path: "/infuse/not-planned", component: FeatureRequestsNotPlannedPage }, 27 + { path: "/infuse/:number", component: FeatureRequestPage }, 28 + ], 29 + };
+249
packages/feature-requests/src/db/operations.ts
··· 1 + import { getDb } from "@exosphere/core/db"; 2 + import { eq, and, inArray, max } from "@exosphere/core/db/drizzle"; 3 + import type { ModerationHandler } from "@exosphere/core/sphere"; 4 + import { 5 + featureRequests, 6 + featureRequestVotes, 7 + featureRequestComments, 8 + featureRequestCommentVotes, 9 + featureRequestStatuses, 10 + } from "./schema.ts"; 11 + import type { Category, Status } from "../schemas/feature-request.ts"; 12 + 13 + // ---- Feature Requests ---- 14 + 15 + export function insertFeatureRequest(params: { 16 + id: string; 17 + authorDid: string; 18 + title: string; 19 + description: string; 20 + category: Category; 21 + pdsUri: string | null; 22 + }): typeof featureRequests.$inferSelect | undefined { 23 + const db = getDb(); 24 + return db.transaction((tx) => { 25 + const lastNumber = tx 26 + .select({ maxNumber: max(featureRequests.number) }) 27 + .from(featureRequests) 28 + .get(); 29 + const number = (lastNumber?.maxNumber ?? 0) + 1; 30 + 31 + tx.insert(featureRequests) 32 + .values({ 33 + id: params.id, 34 + number, 35 + authorDid: params.authorDid, 36 + title: params.title, 37 + description: params.description, 38 + category: params.category, 39 + pdsUri: params.pdsUri, 40 + }) 41 + .onConflictDoNothing() 42 + .run(); 43 + 44 + return tx.select().from(featureRequests).where(eq(featureRequests.id, params.id)).get(); 45 + }); 46 + } 47 + 48 + export function deleteFeatureRequestCascade(id: string): void { 49 + const db = getDb(); 50 + db.transaction((tx) => { 51 + const commentIds = tx 52 + .select({ id: featureRequestComments.id }) 53 + .from(featureRequestComments) 54 + .where(eq(featureRequestComments.requestId, id)) 55 + .all() 56 + .map((r) => r.id); 57 + if (commentIds.length > 0) { 58 + tx.delete(featureRequestCommentVotes) 59 + .where(inArray(featureRequestCommentVotes.commentId, commentIds)) 60 + .run(); 61 + } 62 + tx.delete(featureRequestComments).where(eq(featureRequestComments.requestId, id)).run(); 63 + tx.delete(featureRequestStatuses).where(eq(featureRequestStatuses.requestId, id)).run(); 64 + tx.delete(featureRequestVotes).where(eq(featureRequestVotes.requestId, id)).run(); 65 + tx.delete(featureRequests).where(eq(featureRequests.id, id)).run(); 66 + }); 67 + } 68 + 69 + // ---- Votes ---- 70 + 71 + export function insertVote(requestId: string, authorDid: string, pdsUri: string | null): void { 72 + getDb() 73 + .insert(featureRequestVotes) 74 + .values({ requestId, authorDid, pdsUri }) 75 + .onConflictDoNothing() 76 + .run(); 77 + } 78 + 79 + export function deleteVoteByAuthor(requestId: string, authorDid: string): void { 80 + getDb() 81 + .delete(featureRequestVotes) 82 + .where( 83 + and( 84 + eq(featureRequestVotes.requestId, requestId), 85 + eq(featureRequestVotes.authorDid, authorDid), 86 + ), 87 + ) 88 + .run(); 89 + } 90 + 91 + // ---- Comments ---- 92 + 93 + export function insertComment(params: { 94 + id: string; 95 + requestId: string; 96 + authorDid: string; 97 + content: string; 98 + pdsUri: string | null; 99 + }): void { 100 + getDb() 101 + .insert(featureRequestComments) 102 + .values(params) 103 + .onConflictDoUpdate({ 104 + target: featureRequestComments.id, 105 + set: { content: params.content, updatedAt: new Date().toISOString() }, 106 + }) 107 + .run(); 108 + } 109 + 110 + export function updateComment(id: string, content: string): void { 111 + getDb() 112 + .update(featureRequestComments) 113 + .set({ content, updatedAt: new Date().toISOString() }) 114 + .where(eq(featureRequestComments.id, id)) 115 + .run(); 116 + } 117 + 118 + export function deleteCommentCascade(id: string): void { 119 + const db = getDb(); 120 + db.delete(featureRequestCommentVotes).where(eq(featureRequestCommentVotes.commentId, id)).run(); 121 + db.delete(featureRequestComments).where(eq(featureRequestComments.id, id)).run(); 122 + } 123 + 124 + // ---- Comment Votes ---- 125 + 126 + export function insertCommentVote( 127 + commentId: string, 128 + authorDid: string, 129 + pdsUri: string | null, 130 + ): void { 131 + getDb() 132 + .insert(featureRequestCommentVotes) 133 + .values({ commentId, authorDid, pdsUri }) 134 + .onConflictDoNothing() 135 + .run(); 136 + } 137 + 138 + export function deleteCommentVoteByAuthor(commentId: string, authorDid: string): void { 139 + getDb() 140 + .delete(featureRequestCommentVotes) 141 + .where( 142 + and( 143 + eq(featureRequestCommentVotes.commentId, commentId), 144 + eq(featureRequestCommentVotes.authorDid, authorDid), 145 + ), 146 + ) 147 + .run(); 148 + } 149 + 150 + // ---- Status ---- 151 + 152 + export function insertStatusAndUpdateFR(params: { 153 + id: string; 154 + requestId: string; 155 + authorDid: string; 156 + status: Status; 157 + pdsUri: string | null; 158 + duplicateOfId?: string | null; 159 + clearDuplicateOfId?: boolean; 160 + }): void { 161 + const db = getDb(); 162 + db.insert(featureRequestStatuses) 163 + .values({ 164 + id: params.id, 165 + requestId: params.requestId, 166 + authorDid: params.authorDid, 167 + status: params.status, 168 + pdsUri: params.pdsUri, 169 + }) 170 + .onConflictDoNothing() 171 + .run(); 172 + 173 + const updateFields: Record<string, unknown> = { 174 + status: params.status, 175 + updatedAt: new Date().toISOString(), 176 + }; 177 + if (params.duplicateOfId !== undefined) { 178 + updateFields.duplicateOfId = params.duplicateOfId; 179 + } 180 + if (params.clearDuplicateOfId) { 181 + updateFields.duplicateOfId = null; 182 + } 183 + 184 + db.update(featureRequests) 185 + .set(updateFields) 186 + .where(eq(featureRequests.id, params.requestId)) 187 + .run(); 188 + } 189 + 190 + // ---- Moderation ---- 191 + 192 + export function hideFeatureRequest(id: string, moderatorDid: string): void { 193 + getDb() 194 + .update(featureRequests) 195 + .set({ hiddenAt: new Date().toISOString(), moderatedBy: moderatorDid }) 196 + .where(eq(featureRequests.id, id)) 197 + .run(); 198 + } 199 + 200 + export function unhideFeatureRequest(id: string): void { 201 + getDb() 202 + .update(featureRequests) 203 + .set({ hiddenAt: null, moderatedBy: null }) 204 + .where(eq(featureRequests.id, id)) 205 + .run(); 206 + } 207 + 208 + export function hideComment(id: string, moderatorDid: string): void { 209 + getDb() 210 + .update(featureRequestComments) 211 + .set({ hiddenAt: new Date().toISOString(), moderatedBy: moderatorDid }) 212 + .where(eq(featureRequestComments.id, id)) 213 + .run(); 214 + } 215 + 216 + export function unhideComment(id: string): void { 217 + getDb() 218 + .update(featureRequestComments) 219 + .set({ hiddenAt: null, moderatedBy: null }) 220 + .where(eq(featureRequestComments.id, id)) 221 + .run(); 222 + } 223 + 224 + /** Moderation handler for feature requests and comments. Returns true if it handled the subject. */ 225 + export const handleFeatureRequestModeration: ModerationHandler = (subjectUri, moderatorDid) => { 226 + const db = getDb(); 227 + 228 + const fr = db 229 + .select({ id: featureRequests.id }) 230 + .from(featureRequests) 231 + .where(eq(featureRequests.pdsUri, subjectUri)) 232 + .get(); 233 + if (fr) { 234 + hideFeatureRequest(fr.id, moderatorDid); 235 + return true; 236 + } 237 + 238 + const comment = db 239 + .select({ id: featureRequestComments.id }) 240 + .from(featureRequestComments) 241 + .where(eq(featureRequestComments.pdsUri, subjectUri)) 242 + .get(); 243 + if (comment) { 244 + hideComment(comment.id, moderatorDid); 245 + return true; 246 + } 247 + 248 + return false; 249 + };
+120
packages/feature-requests/src/db/schema.ts
··· 1 + import { sqliteTable, text, integer, primaryKey, index } from "drizzle-orm/sqlite-core"; 2 + import { sql } from "drizzle-orm"; 3 + import type { InferSelectModel } from "drizzle-orm"; 4 + import { categories, statuses } from "../schemas/feature-request.ts"; 5 + 6 + export const featureRequests = sqliteTable( 7 + "feature_requests", 8 + { 9 + id: text("id").primaryKey(), 10 + number: integer("number").notNull().unique(), 11 + authorDid: text("author_did").notNull(), 12 + title: text("title").notNull(), 13 + description: text("description").notNull(), 14 + category: text("category", { enum: [...categories] }) 15 + .notNull() 16 + .default("general"), 17 + status: text("status", { enum: [...statuses] }) 18 + .notNull() 19 + .default("requested"), 20 + duplicateOfId: text("duplicate_of_id"), 21 + pdsUri: text("pds_uri"), 22 + hiddenAt: text("hidden_at"), 23 + moderatedBy: text("moderated_by"), 24 + createdAt: text("created_at") 25 + .notNull() 26 + .default(sql`(datetime('now'))`), 27 + updatedAt: text("updated_at") 28 + .notNull() 29 + .default(sql`(datetime('now'))`), 30 + }, 31 + (table) => [ 32 + index("idx_feature_requests_status").on(table.status), 33 + index("idx_feature_requests_created").on(table.createdAt), 34 + index("idx_feature_requests_category").on(table.category), 35 + ], 36 + ); 37 + 38 + export const featureRequestVotes = sqliteTable( 39 + "feature_request_votes", 40 + { 41 + requestId: text("request_id") 42 + .notNull() 43 + .references(() => featureRequests.id), 44 + authorDid: text("author_did").notNull(), 45 + pdsUri: text("pds_uri"), 46 + createdAt: text("created_at") 47 + .notNull() 48 + .default(sql`(datetime('now'))`), 49 + }, 50 + (table) => [ 51 + primaryKey({ columns: [table.requestId, table.authorDid] }), 52 + index("idx_feature_request_votes_request").on(table.requestId), 53 + ], 54 + ); 55 + 56 + export const featureRequestComments = sqliteTable( 57 + "feature_request_comments", 58 + { 59 + id: text("id").primaryKey(), 60 + requestId: text("request_id") 61 + .notNull() 62 + .references(() => featureRequests.id), 63 + authorDid: text("author_did").notNull(), 64 + content: text("content").notNull(), 65 + pdsUri: text("pds_uri"), 66 + createdAt: text("created_at") 67 + .notNull() 68 + .default(sql`(datetime('now'))`), 69 + updatedAt: text("updated_at") 70 + .notNull() 71 + .default(sql`(datetime('now'))`), 72 + hiddenAt: text("hidden_at"), 73 + moderatedBy: text("moderated_by"), 74 + }, 75 + (table) => [ 76 + index("idx_feature_request_comments_request").on(table.requestId), 77 + index("idx_feature_request_comments_author_request").on(table.authorDid, table.requestId), 78 + ], 79 + ); 80 + 81 + export const featureRequestCommentVotes = sqliteTable( 82 + "feature_request_comment_votes", 83 + { 84 + commentId: text("comment_id") 85 + .notNull() 86 + .references(() => featureRequestComments.id), 87 + authorDid: text("author_did").notNull(), 88 + pdsUri: text("pds_uri"), 89 + createdAt: text("created_at") 90 + .notNull() 91 + .default(sql`(datetime('now'))`), 92 + }, 93 + (table) => [ 94 + primaryKey({ columns: [table.commentId, table.authorDid] }), 95 + index("idx_feature_request_comment_votes_comment").on(table.commentId), 96 + ], 97 + ); 98 + 99 + export const featureRequestStatuses = sqliteTable( 100 + "feature_request_statuses", 101 + { 102 + id: text("id").primaryKey(), 103 + requestId: text("request_id") 104 + .notNull() 105 + .references(() => featureRequests.id), 106 + authorDid: text("author_did").notNull(), 107 + status: text("status", { enum: [...statuses] }).notNull(), 108 + pdsUri: text("pds_uri"), 109 + createdAt: text("created_at") 110 + .notNull() 111 + .default(sql`(datetime('now'))`), 112 + }, 113 + (table) => [index("idx_feature_request_statuses_request").on(table.requestId)], 114 + ); 115 + 116 + export type FeatureRequest = InferSelectModel<typeof featureRequests>; 117 + export type FeatureRequestVote = InferSelectModel<typeof featureRequestVotes>; 118 + export type FeatureRequestComment = InferSelectModel<typeof featureRequestComments>; 119 + export type FeatureRequestCommentVote = InferSelectModel<typeof featureRequestCommentVotes>; 120 + export type FeatureRequestStatus = InferSelectModel<typeof featureRequestStatuses>;
+9
packages/feature-requests/src/index.ts
··· 1 + import type { ExosphereModule } from "@exosphere/core/types"; 2 + import { featureRequestsApi } from "./api/routes.ts"; 3 + import { featureRequestsIndexer } from "./indexer.ts"; 4 + 5 + export const featureRequestsModule: ExosphereModule = { 6 + name: "feature-requests", 7 + api: featureRequestsApi, 8 + indexer: featureRequestsIndexer, 9 + };
+194
packages/feature-requests/src/indexer.ts
··· 1 + import type { ModuleIndexer, JetstreamCommitEvent } from "@exosphere/core/types"; 2 + import { buildAtUri, parseAtUri } from "@exosphere/core/indexer"; 3 + import { registerModerationHandler, getActiveMemberRole } from "@exosphere/core/sphere"; 4 + import { getDb } from "@exosphere/core/db"; 5 + import { eq, and } from "@exosphere/core/db/drizzle"; 6 + import { spheres } from "@exosphere/core/db/schema"; 7 + import { featureRequests, featureRequestComments } from "./db/schema.ts"; 8 + import { categories, statuses } from "./schemas/feature-request.ts"; 9 + import type { Category, Status } from "./schemas/feature-request.ts"; 10 + import { 11 + insertFeatureRequest, 12 + deleteFeatureRequestCascade, 13 + insertVote, 14 + deleteVoteByAuthor, 15 + insertComment, 16 + deleteCommentCascade, 17 + insertCommentVote, 18 + deleteCommentVoteByAuthor, 19 + insertStatusAndUpdateFR, 20 + handleFeatureRequestModeration, 21 + } from "./db/operations.ts"; 22 + 23 + // Register this module's moderation handler with core 24 + registerModerationHandler(handleFeatureRequestModeration); 25 + 26 + const COLLECTION = "site.exosphere.featureRequest"; 27 + const VOTE_COLLECTION = "site.exosphere.featureRequestVote"; 28 + const COMMENT_COLLECTION = "site.exosphere.featureRequestComment"; 29 + const COMMENT_VOTE_COLLECTION = "site.exosphere.featureRequestCommentVote"; 30 + const STATUS_COLLECTION = "site.exosphere.featureRequestStatus"; 31 + 32 + function findSphereForAccess(sphereSlug: string, did: string): boolean { 33 + const db = getDb(); 34 + const sphere = db 35 + .select({ id: spheres.id, writeAccess: spheres.writeAccess }) 36 + .from(spheres) 37 + .where(eq(spheres.slug, sphereSlug)) 38 + .get(); 39 + if (!sphere) return false; 40 + if (sphere.writeAccess === "open") return true; 41 + const role = getActiveMemberRole(sphere.id, did); 42 + return role !== null; 43 + } 44 + 45 + export const featureRequestsIndexer: ModuleIndexer = { 46 + collections: [ 47 + COLLECTION, 48 + VOTE_COLLECTION, 49 + COMMENT_COLLECTION, 50 + COMMENT_VOTE_COLLECTION, 51 + STATUS_COLLECTION, 52 + ], 53 + 54 + handleCreateOrUpdate(event: JetstreamCommitEvent) { 55 + const { did, commit } = event; 56 + const { collection, rkey, record } = commit; 57 + if (!record) return; 58 + 59 + const pdsUri = buildAtUri(did, collection, rkey); 60 + 61 + if (collection === COLLECTION) { 62 + const sphereSlug = record.sphereSlug as string; 63 + if (sphereSlug && !findSphereForAccess(sphereSlug, did)) return; 64 + 65 + const rawCategory = record.category as string; 66 + const category: Category = (categories as readonly string[]).includes(rawCategory) 67 + ? (rawCategory as Category) 68 + : "general"; 69 + 70 + insertFeatureRequest({ 71 + id: rkey, 72 + authorDid: did, 73 + title: (record.title as string) ?? "", 74 + description: (record.description as string) ?? "", 75 + category, 76 + pdsUri, 77 + }); 78 + return; 79 + } 80 + 81 + // Vote, comment, comment vote, and status all reference a subject via AT URI 82 + const subjectUri = record.subject as string; 83 + if (!subjectUri) return; 84 + const parsed = parseAtUri(subjectUri); 85 + if (!parsed) return; 86 + 87 + if (collection === VOTE_COLLECTION) { 88 + const fr = getDb() 89 + .select({ id: featureRequests.id }) 90 + .from(featureRequests) 91 + .where(eq(featureRequests.id, parsed.rkey)) 92 + .get(); 93 + if (!fr) { 94 + console.warn("[indexer] Vote references unknown feature request rkey=%s", parsed.rkey); 95 + return; 96 + } 97 + insertVote(fr.id, did, pdsUri); 98 + return; 99 + } 100 + 101 + if (collection === COMMENT_COLLECTION) { 102 + const fr = getDb() 103 + .select({ id: featureRequests.id }) 104 + .from(featureRequests) 105 + .where(eq(featureRequests.id, parsed.rkey)) 106 + .get(); 107 + if (!fr) { 108 + console.warn("[indexer] Comment references unknown feature request rkey=%s", parsed.rkey); 109 + return; 110 + } 111 + insertComment({ 112 + id: rkey, 113 + requestId: fr.id, 114 + authorDid: did, 115 + content: (record.content as string) ?? "", 116 + pdsUri, 117 + }); 118 + return; 119 + } 120 + 121 + if (collection === COMMENT_VOTE_COLLECTION) { 122 + const comment = getDb() 123 + .select({ id: featureRequestComments.id }) 124 + .from(featureRequestComments) 125 + .where(eq(featureRequestComments.id, parsed.rkey)) 126 + .get(); 127 + if (!comment) { 128 + console.warn("[indexer] Comment vote references unknown comment rkey=%s", parsed.rkey); 129 + return; 130 + } 131 + insertCommentVote(comment.id, did, pdsUri); 132 + return; 133 + } 134 + 135 + if (collection === STATUS_COLLECTION) { 136 + const fr = getDb() 137 + .select({ id: featureRequests.id }) 138 + .from(featureRequests) 139 + .where(eq(featureRequests.id, parsed.rkey)) 140 + .get(); 141 + if (!fr) { 142 + console.warn("[indexer] Status references unknown feature request rkey=%s", parsed.rkey); 143 + return; 144 + } 145 + 146 + const rawStatus = record.status as string; 147 + if (!rawStatus || !(statuses as readonly string[]).includes(rawStatus)) return; 148 + 149 + insertStatusAndUpdateFR({ 150 + id: rkey, 151 + requestId: fr.id, 152 + authorDid: did, 153 + status: rawStatus as Status, 154 + pdsUri, 155 + }); 156 + } 157 + }, 158 + 159 + handleDelete(event: JetstreamCommitEvent) { 160 + const { did, commit } = event; 161 + const { collection, rkey } = commit; 162 + 163 + if (collection === COLLECTION) { 164 + const fr = getDb() 165 + .select({ id: featureRequests.id }) 166 + .from(featureRequests) 167 + .where(and(eq(featureRequests.id, rkey), eq(featureRequests.authorDid, did))) 168 + .get(); 169 + if (!fr) return; 170 + deleteFeatureRequestCascade(rkey); 171 + return; 172 + } 173 + 174 + if (collection === VOTE_COLLECTION) { 175 + deleteVoteByAuthor(rkey, did); 176 + return; 177 + } 178 + 179 + if (collection === COMMENT_COLLECTION) { 180 + const comment = getDb() 181 + .select({ id: featureRequestComments.id }) 182 + .from(featureRequestComments) 183 + .where(and(eq(featureRequestComments.id, rkey), eq(featureRequestComments.authorDid, did))) 184 + .get(); 185 + if (!comment) return; 186 + deleteCommentCascade(rkey); 187 + return; 188 + } 189 + 190 + if (collection === COMMENT_VOTE_COLLECTION) { 191 + deleteCommentVoteByAuthor(rkey, did); 192 + } 193 + }, 194 + };
+14
packages/feature-requests/src/schemas/comment.ts
··· 1 + import { z } from "zod"; 2 + 3 + export const createCommentSchema = z.object({ 4 + content: z.string().min(1).max(5000), 5 + sphereSlug: z.string().min(1), 6 + }); 7 + 8 + export const updateCommentSchema = z.object({ 9 + content: z.string().min(1).max(5000), 10 + sphereSlug: z.string().min(1), 11 + }); 12 + 13 + export type CreateComment = z.infer<typeof createCommentSchema>; 14 + export type UpdateComment = z.infer<typeof updateCommentSchema>;
+63
packages/feature-requests/src/schemas/feature-request.ts
··· 1 + import { z } from "zod"; 2 + 3 + export const categories = ["general", "enhancement", "bug", "integration", "ui-ux"] as const; 4 + 5 + export type Category = (typeof categories)[number]; 6 + 7 + export const categoryLabels: Record<Category, string> = { 8 + general: "General", 9 + enhancement: "Enhancement", 10 + bug: "Bug", 11 + integration: "Integration", 12 + "ui-ux": "UI / UX", 13 + }; 14 + 15 + export const statuses = [ 16 + "requested", 17 + "not-planned", 18 + "approved", 19 + "in-progress", 20 + "done", 21 + "duplicate", 22 + ] as const; 23 + 24 + export type Status = (typeof statuses)[number]; 25 + 26 + /** Statuses that can be set via the status dropdown (excludes "duplicate"). */ 27 + export const settableStatuses = [ 28 + "requested", 29 + "not-planned", 30 + "approved", 31 + "in-progress", 32 + "done", 33 + ] as const; 34 + 35 + export type SettableStatus = (typeof settableStatuses)[number]; 36 + 37 + export const statusLabels: Record<Status, string> = { 38 + requested: "Requested", 39 + "not-planned": "Not planned", 40 + approved: "Approved", 41 + "in-progress": "In progress", 42 + done: "Done", 43 + duplicate: "Duplicate", 44 + }; 45 + 46 + export const updateStatusSchema = z.object({ 47 + status: z.enum(settableStatuses), 48 + sphereSlug: z.string().min(1), 49 + }); 50 + 51 + export const markAsDuplicateSchema = z.object({ 52 + duplicateOfId: z.string().min(1), 53 + sphereSlug: z.string().min(1), 54 + }); 55 + 56 + export const createFeatureRequestSchema = z.object({ 57 + title: z.string().min(1).max(200), 58 + description: z.string().min(1).max(10000), 59 + category: z.enum(categories).default("general"), 60 + sphereSlug: z.string().min(1), 61 + }); 62 + 63 + export type CreateFeatureRequest = z.infer<typeof createFeatureRequestSchema>;
+22
packages/feature-requests/src/types.ts
··· 1 + export type { FeatureRequest, FeatureRequestComment, FeatureRequestStatus } from "./db/schema.ts"; 2 + export type { Category, Status, SettableStatus } from "./schemas/feature-request.ts"; 3 + export { 4 + categoryLabels, 5 + categories, 6 + statuses, 7 + settableStatuses, 8 + statusLabels, 9 + } from "./schemas/feature-request.ts"; 10 + 11 + import type { FeatureRequest, FeatureRequestComment } from "./db/schema.ts"; 12 + 13 + /** Shape returned by the GET /feature-requests list endpoint (includes vote count). */ 14 + export type FeatureRequestListItem = FeatureRequest & { 15 + voteCount: number; 16 + commentCount: number; 17 + }; 18 + 19 + /** Shape returned by the GET comments endpoint (includes vote count). */ 20 + export type FeatureRequestCommentListItem = FeatureRequestComment & { 21 + voteCount: number; 22 + };
+19
packages/feature-requests/src/ui/api/comment-votes.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + 3 + export function getMyCommentVotes() { 4 + return apiFetch<{ votes: string[] }>("/api/feature-requests/comments/votes"); 5 + } 6 + 7 + export function voteComment(id: string, sphereSlug: string) { 8 + return apiFetch<{ voteCount: number }>( 9 + `/api/feature-requests/comments/${encodeURIComponent(id)}/vote?sphereSlug=${encodeURIComponent(sphereSlug)}`, 10 + { method: "POST" }, 11 + ); 12 + } 13 + 14 + export function unvoteComment(id: string) { 15 + return apiFetch<{ voteCount: number }>( 16 + `/api/feature-requests/comments/${encodeURIComponent(id)}/vote`, 17 + { method: "DELETE" }, 18 + ); 19 + }
+43
packages/feature-requests/src/ui/api/comments.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + import type { FeatureRequestComment, FeatureRequestCommentListItem } from "../../types.ts"; 3 + 4 + export type { FeatureRequestComment, FeatureRequestCommentListItem }; 5 + 6 + export function getComments(requestId: string, sort?: "date" | "votes", order?: "asc" | "desc") { 7 + const params = new URLSearchParams(); 8 + if (sort) params.set("sort", sort); 9 + if (order) params.set("order", order); 10 + const qs = params.toString(); 11 + return apiFetch<{ comments: FeatureRequestCommentListItem[] }>( 12 + `/api/feature-requests/${encodeURIComponent(requestId)}/comments${qs ? `?${qs}` : ""}`, 13 + ); 14 + } 15 + 16 + export function createComment(requestId: string, content: string, sphereSlug: string) { 17 + return apiFetch<{ comment: FeatureRequestComment }>( 18 + `/api/feature-requests/${encodeURIComponent(requestId)}/comments`, 19 + { 20 + method: "POST", 21 + headers: { "Content-Type": "application/json" }, 22 + body: JSON.stringify({ content, sphereSlug }), 23 + }, 24 + ); 25 + } 26 + 27 + export function updateComment(id: string, content: string, sphereSlug: string) { 28 + return apiFetch<{ comment: FeatureRequestComment }>( 29 + `/api/feature-requests/comments/${encodeURIComponent(id)}`, 30 + { 31 + method: "PUT", 32 + headers: { "Content-Type": "application/json" }, 33 + body: JSON.stringify({ content, sphereSlug }), 34 + }, 35 + ); 36 + } 37 + 38 + export function deleteComment(id: string, sphereSlug: string) { 39 + return apiFetch<{ ok: true }>( 40 + `/api/feature-requests/comments/${encodeURIComponent(id)}?sphereSlug=${encodeURIComponent(sphereSlug)}`, 41 + { method: "DELETE" }, 42 + ); 43 + }
+58
packages/feature-requests/src/ui/api/feature-requests.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + import type { FeatureRequest, FeatureRequestListItem } from "../../types.ts"; 3 + 4 + export type { FeatureRequest, FeatureRequestListItem }; 5 + 6 + export function getFeatureRequest(number: number) { 7 + return apiFetch<{ 8 + featureRequest: FeatureRequestListItem; 9 + duplicateOf: { id: string; number: number; title: string } | null; 10 + duplicateCount: number; 11 + }>(`/api/feature-requests/${number}`); 12 + } 13 + 14 + export type SortBy = "date" | "votes"; 15 + export type SortOrder = "asc" | "desc"; 16 + 17 + export function getFeatureRequests(statuses?: string[], sort?: SortBy, order?: SortOrder) { 18 + const params = new URLSearchParams(); 19 + if (statuses?.length) params.set("status", statuses.join(",")); 20 + if (sort) params.set("sort", sort); 21 + if (order) params.set("order", order); 22 + const qs = params.toString(); 23 + const url = `/api/feature-requests${qs ? `?${qs}` : ""}`; 24 + return apiFetch<{ featureRequests: FeatureRequestListItem[] }>(url); 25 + } 26 + 27 + export function createFeatureRequest(body: { 28 + title: string; 29 + description: string; 30 + category: string; 31 + sphereSlug: string; 32 + }) { 33 + return apiFetch<{ featureRequest: FeatureRequest }>("/api/feature-requests", { 34 + method: "POST", 35 + headers: { "Content-Type": "application/json" }, 36 + body: JSON.stringify(body), 37 + }); 38 + } 39 + 40 + export function deleteFeatureRequest(id: string) { 41 + return apiFetch<{ ok: true }>(`/api/feature-requests/${encodeURIComponent(id)}`, { 42 + method: "DELETE", 43 + }); 44 + } 45 + 46 + export function hideFeatureRequest(id: string, sphereSlug: string) { 47 + return apiFetch<{ ok: true }>( 48 + `/api/feature-requests/${encodeURIComponent(id)}/hide?sphereSlug=${encodeURIComponent(sphereSlug)}`, 49 + { method: "POST" }, 50 + ); 51 + } 52 + 53 + export function unhideFeatureRequest(id: string, sphereSlug: string) { 54 + return apiFetch<{ ok: true }>( 55 + `/api/feature-requests/${encodeURIComponent(id)}/unhide?sphereSlug=${encodeURIComponent(sphereSlug)}`, 56 + { method: "POST" }, 57 + ); 58 + }
+9
packages/feature-requests/src/ui/api/index.ts
··· 1 + export { statuses, settableStatuses, statusLabels } from "../../types.ts"; 2 + export type { Status, SettableStatus } from "../../types.ts"; 3 + 4 + export * from "./feature-requests.ts"; 5 + export * from "./votes.ts"; 6 + export * from "./comments.ts"; 7 + export * from "./comment-votes.ts"; 8 + export * from "./status.ts"; 9 + export * from "./search.ts";
+23
packages/feature-requests/src/ui/api/search.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + 3 + export function searchFeatureRequests(q: string, excludeId: string) { 4 + return apiFetch<{ 5 + results: Array<{ id: string; number: number; title: string; status: string }>; 6 + }>( 7 + `/api/feature-requests/search?q=${encodeURIComponent(q)}&excludeId=${encodeURIComponent(excludeId)}`, 8 + ); 9 + } 10 + 11 + export function markAsDuplicate(id: string, duplicateOfId: string, sphereSlug: string) { 12 + return apiFetch<{ ok: true }>(`/api/feature-requests/${encodeURIComponent(id)}/duplicate`, { 13 + method: "POST", 14 + headers: { "Content-Type": "application/json" }, 15 + body: JSON.stringify({ duplicateOfId, sphereSlug }), 16 + }); 17 + } 18 + 19 + export function getDuplicates(requestId: string) { 20 + return apiFetch<{ 21 + duplicates: Array<{ id: string; number: number; title: string }>; 22 + }>(`/api/feature-requests/${encodeURIComponent(requestId)}/duplicates`); 23 + }
+23
packages/feature-requests/src/ui/api/status.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + import type { FeatureRequestStatus } from "../../types.ts"; 3 + 4 + export type { FeatureRequestStatus }; 5 + 6 + export function updateFeatureRequestStatus(id: string, status: string, sphereSlug: string) { 7 + return apiFetch<{ status: string }>(`/api/feature-requests/${encodeURIComponent(id)}/status`, { 8 + method: "POST", 9 + headers: { "Content-Type": "application/json" }, 10 + body: JSON.stringify({ status, sphereSlug }), 11 + }); 12 + } 13 + 14 + export function getStatusHistory(requestId: string) { 15 + return apiFetch<{ 16 + statuses: Array<{ 17 + id: string; 18 + authorDid: string; 19 + status: string; 20 + createdAt: string; 21 + }>; 22 + }>(`/api/feature-requests/${encodeURIComponent(requestId)}/statuses`); 23 + }
+18
packages/feature-requests/src/ui/api/votes.ts
··· 1 + import { apiFetch } from "@exosphere/client/api"; 2 + 3 + export function voteFeatureRequest(id: string, sphereSlug: string) { 4 + return apiFetch<{ voteCount: number }>( 5 + `/api/feature-requests/${encodeURIComponent(id)}/vote?sphereSlug=${encodeURIComponent(sphereSlug)}`, 6 + { method: "POST" }, 7 + ); 8 + } 9 + 10 + export function unvoteFeatureRequest(id: string) { 11 + return apiFetch<{ voteCount: number }>(`/api/feature-requests/${encodeURIComponent(id)}/vote`, { 12 + method: "DELETE", 13 + }); 14 + } 15 + 16 + export function getMyVotes() { 17 + return apiFetch<{ votes: string[] }>("/api/feature-requests/votes"); 18 + }
+96
packages/feature-requests/src/ui/components/merge-dropdown.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { useRef } from "preact/hooks"; 3 + import * as ui from "@exosphere/client/ui.css"; 4 + import { searchFeatureRequests, markAsDuplicate, statusLabels, type Status } from "../api/index.ts"; 5 + 6 + export function MergeDropdown({ 7 + frId, 8 + sphereSlug, 9 + onMarked, 10 + }: { 11 + frId: string; 12 + sphereSlug: string; 13 + onMarked: () => void; 14 + }) { 15 + const query = useSignal(""); 16 + const results = useSignal<Array<{ id: string; number: number; title: string; status: string }>>( 17 + [], 18 + ); 19 + const loading = useSignal(false); 20 + const submitting = useSignal(false); 21 + const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); 22 + 23 + const handleInput = (value: string) => { 24 + query.value = value; 25 + if (timerRef.current) clearTimeout(timerRef.current); 26 + if (!value.trim()) { 27 + results.value = []; 28 + return; 29 + } 30 + timerRef.current = setTimeout(async () => { 31 + loading.value = true; 32 + try { 33 + const res = await searchFeatureRequests(value.trim(), frId); 34 + results.value = res.results; 35 + } catch { 36 + results.value = []; 37 + } finally { 38 + loading.value = false; 39 + } 40 + }, 300); 41 + }; 42 + 43 + const handleSelect = async (targetId: string) => { 44 + if (submitting.value) return; 45 + submitting.value = true; 46 + try { 47 + await markAsDuplicate(frId, targetId, sphereSlug); 48 + onMarked(); 49 + } catch (err) { 50 + console.error("Failed to mark as duplicate:", err); 51 + } finally { 52 + submitting.value = false; 53 + } 54 + }; 55 + 56 + return ( 57 + <div class={ui.stackSm}> 58 + <label class={ui.label}>Mark as duplicate of:</label> 59 + <input 60 + class={ui.input} 61 + type="text" 62 + placeholder="Search by title or #number..." 63 + value={query.value} 64 + onInput={(e) => handleInput((e.target as HTMLInputElement).value)} 65 + /> 66 + {loading.value && <p class={ui.muted}>Searching...</p>} 67 + {results.value.length > 0 && ( 68 + <div class={ui.stackSm}> 69 + {results.value.map((r) => ( 70 + <div key={r.id} style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}> 71 + <span style={{ flex: 1 }}> 72 + <span class={ui.muted}>#{r.number}</span> {r.title}{" "} 73 + {r.status !== "requested" && ( 74 + <span class={ui.badge} data-status={r.status}> 75 + {statusLabels[r.status as Status] ?? r.status} 76 + </span> 77 + )} 78 + </span> 79 + <button 80 + class={ui.buttonInline} 81 + style={{ paddingBlock: "4px", paddingInline: "0.5rem", fontSize: "0.75rem" }} 82 + disabled={submitting.value} 83 + onClick={() => handleSelect(r.id)} 84 + > 85 + Select 86 + </button> 87 + </div> 88 + ))} 89 + </div> 90 + )} 91 + {!loading.value && query.value.trim() && results.value.length === 0 && ( 92 + <p class={ui.muted}>No results found.</p> 93 + )} 94 + </div> 95 + ); 96 + }
+115
packages/feature-requests/src/ui/components/request-card.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import * as ui from "@exosphere/client/ui.css"; 3 + import * as frUi from "../ui.css.ts"; 4 + import { statusLabels, type FeatureRequestListItem, type Status } from "../api/index.ts"; 5 + import { categoryLabels, type Category } from "../../schemas/feature-request.ts"; 6 + import { formatDate } from "@exosphere/client/format"; 7 + 8 + export function categoryLabel(value: string): string { 9 + return categoryLabels[value as Category] ?? value; 10 + } 11 + 12 + export function RequestCard({ 13 + fr, 14 + isAuthor = false, 15 + isAdminOrOwner = false, 16 + hasVoted, 17 + isAuthenticated, 18 + isDetail = false, 19 + onDelete, 20 + onHide, 21 + onVote, 22 + onUnvote, 23 + }: { 24 + fr: FeatureRequestListItem; 25 + isAuthor?: boolean; 26 + isAdminOrOwner?: boolean; 27 + hasVoted: boolean; 28 + isAuthenticated: boolean; 29 + isDetail?: boolean; 30 + onDelete?: (id: string) => void; 31 + onHide?: (id: string) => void; 32 + onVote: (id: string) => void; 33 + onUnvote: (id: string) => void; 34 + }) { 35 + const voting = useSignal(false); 36 + 37 + const handleVote = async () => { 38 + if (voting.value) return; 39 + voting.value = true; 40 + try { 41 + if (hasVoted) { 42 + onUnvote(fr.id); 43 + } else { 44 + onVote(fr.id); 45 + } 46 + } finally { 47 + voting.value = false; 48 + } 49 + }; 50 + 51 + const voteClasses = `${frUi.voteButton}${hasVoted ? ` ${frUi.voteButtonActive}` : ""}`; 52 + 53 + return ( 54 + <div class={`${ui.card} ${frUi.cardWithVote}`}> 55 + {isAuthenticated ? ( 56 + <button 57 + class={voteClasses} 58 + onClick={handleVote} 59 + disabled={voting.value} 60 + title={hasVoted ? "Remove vote" : "Upvote"} 61 + > 62 + <span>{hasVoted ? "\u25B2" : "\u25B3"}</span> 63 + {fr.voteCount} 64 + </button> 65 + ) : ( 66 + <span class={frUi.voteCount}> 67 + <span>{"\u25B3"}</span> 68 + {fr.voteCount} 69 + </span> 70 + )} 71 + <div class={`${frUi.cardContent} ${ui.stack}`}> 72 + <div class={ui.row}> 73 + {isDetail ? ( 74 + <h1 class={ui.cardTitle}> 75 + <span class={ui.muted}>#{fr.number}</span> {fr.title} 76 + </h1> 77 + ) : ( 78 + <h3 class={ui.cardTitle}> 79 + <a href={`/infuse/${fr.number}`}> 80 + <span class={ui.muted}>#{fr.number}</span> {fr.title} 81 + </a> 82 + </h3> 83 + )} 84 + <div class={ui.cluster}> 85 + <span class={ui.badge}>{categoryLabel(fr.category)}</span> 86 + {fr.status !== "requested" && ( 87 + <span class={ui.badge} data-status={fr.status}> 88 + {statusLabels[fr.status as Status] ?? fr.status} 89 + </span> 90 + )} 91 + </div> 92 + </div> 93 + {!isDetail && <p class={ui.description}>{fr.description}</p>} 94 + <div class={ui.metaRow}> 95 + <span class={ui.muted}>{formatDate(fr.createdAt)}</span> 96 + {fr.commentCount > 0 && ( 97 + <span class={ui.muted}> 98 + {fr.commentCount} {fr.commentCount === 1 ? "comment" : "comments"} 99 + </span> 100 + )} 101 + {isAuthor && onDelete && ( 102 + <button class={ui.buttonDangerInline} onClick={() => onDelete(fr.id)}> 103 + Delete 104 + </button> 105 + )} 106 + {isAdminOrOwner && !isAuthor && onHide && ( 107 + <button class={ui.buttonDangerInline} onClick={() => onHide(fr.id)}> 108 + Hide 109 + </button> 110 + )} 111 + </div> 112 + </div> 113 + </div> 114 + ); 115 + }
+44
packages/feature-requests/src/ui/components/sort-controls.tsx
··· 1 + import type { Signal } from "@preact/signals"; 2 + import * as ui from "@exosphere/client/ui.css"; 3 + import * as frUi from "../ui.css.ts"; 4 + import type { SortBy, SortOrder } from "../api/index.ts"; 5 + 6 + export function SortControls({ 7 + id, 8 + sortBy, 9 + sortOrder, 10 + }: { 11 + id: string; 12 + sortBy: Signal<SortBy>; 13 + sortOrder: Signal<SortOrder>; 14 + }) { 15 + const toggleOrder = () => { 16 + sortOrder.value = sortOrder.value === "desc" ? "asc" : "desc"; 17 + }; 18 + 19 + return ( 20 + <div class={frUi.sortRow}> 21 + <label class={ui.muted} htmlFor={id}> 22 + Sort by 23 + </label> 24 + <select 25 + id={id} 26 + class={frUi.sortSelect} 27 + value={sortBy.value} 28 + onChange={(e) => (sortBy.value = (e.target as HTMLSelectElement).value as SortBy)} 29 + > 30 + <option value="date">Date</option> 31 + <option value="votes">Votes</option> 32 + </select> 33 + <button type="button" class={frUi.sortButton} onClick={toggleOrder}> 34 + {sortBy.value === "date" 35 + ? sortOrder.value === "desc" 36 + ? "↓ Newest first" 37 + : "↑ Oldest first" 38 + : sortOrder.value === "desc" 39 + ? "↓ Most voted" 40 + : "↑ Least voted"} 41 + </button> 42 + </div> 43 + ); 44 + }
+44
packages/feature-requests/src/ui/hooks/use-sort-params.ts
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { useEffect, useRef } from "preact/hooks"; 3 + import { useLocation } from "@exosphere/client/router"; 4 + import type { SortBy, SortOrder } from "../api/index.ts"; 5 + 6 + const DEFAULT_SORT: SortBy = "date"; 7 + const DEFAULT_ORDER: SortOrder = "desc"; 8 + const SORT_VALUES: string[] = ["date", "votes"]; 9 + const ORDER_VALUES: string[] = ["asc", "desc"]; 10 + 11 + export function useSortParams(prefix?: string) { 12 + const sortKey = prefix ? `${prefix}sort` : "sort"; 13 + const orderKey = prefix ? `${prefix}order` : "order"; 14 + const { query, path } = useLocation(); 15 + 16 + const initSort = SORT_VALUES.includes(query[sortKey]) ? (query[sortKey] as SortBy) : DEFAULT_SORT; 17 + const initOrder = ORDER_VALUES.includes(query[orderKey]) 18 + ? (query[orderKey] as SortOrder) 19 + : DEFAULT_ORDER; 20 + 21 + const sortBy = useSignal<SortBy>(initSort); 22 + const sortOrder = useSignal<SortOrder>(initOrder); 23 + const isFirstRender = useRef(true); 24 + 25 + useEffect(() => { 26 + if (isFirstRender.current) { 27 + isFirstRender.current = false; 28 + return; 29 + } 30 + 31 + const params = new URLSearchParams(window.location.search); 32 + 33 + if (sortBy.value === DEFAULT_SORT) params.delete(sortKey); 34 + else params.set(sortKey, sortBy.value); 35 + 36 + if (sortOrder.value === DEFAULT_ORDER) params.delete(orderKey); 37 + else params.set(orderKey, sortOrder.value); 38 + 39 + const qs = params.toString(); 40 + history.replaceState(null, "", path + (qs ? `?${qs}` : "")); 41 + }, [sortBy.value, sortOrder.value]); 42 + 43 + return { sortBy, sortOrder }; 44 + }
+673
packages/feature-requests/src/ui/pages/feature-request.tsx
··· 1 + import { useSignal, useComputed } from "@preact/signals"; 2 + import { auth } from "@exosphere/client/auth"; 3 + import { useLocation, useRoute } from "@exosphere/client/router"; 4 + import { sphereState, sphereSlug } from "@exosphere/client/sphere"; 5 + import { useQuery } from "@exosphere/client/hooks"; 6 + import * as ui from "@exosphere/client/ui.css"; 7 + import * as frUi from "../ui.css.ts"; 8 + import { ApiError } from "@exosphere/client/api"; 9 + import { ssrPageData } from "@exosphere/client/ssr-data"; 10 + import { 11 + getFeatureRequest, 12 + deleteFeatureRequest, 13 + hideFeatureRequest, 14 + voteFeatureRequest, 15 + unvoteFeatureRequest, 16 + getMyVotes, 17 + getComments, 18 + createComment, 19 + updateComment, 20 + deleteComment, 21 + voteComment, 22 + unvoteComment, 23 + getMyCommentVotes, 24 + updateFeatureRequestStatus, 25 + getStatusHistory, 26 + getDuplicates, 27 + settableStatuses, 28 + statusLabels, 29 + type Status, 30 + type FeatureRequestCommentListItem, 31 + } from "../api/index.ts"; 32 + import { MergeDropdown } from "../components/merge-dropdown.tsx"; 33 + import { RequestCard } from "../components/request-card.tsx"; 34 + import { SortControls } from "../components/sort-controls.tsx"; 35 + import { useSortParams } from "../hooks/use-sort-params.ts"; 36 + import { CollapsibleSection } from "@exosphere/client/components/collapsible-section"; 37 + import { useEffect } from "preact/hooks"; 38 + import { formatDate } from "@exosphere/client/format"; 39 + 40 + const fullDateOpts: Intl.DateTimeFormatOptions = { 41 + year: "numeric", 42 + month: "short", 43 + day: "numeric", 44 + }; 45 + 46 + function statusLabel(value: string): string { 47 + return statusLabels[value as Status] ?? value; 48 + } 49 + 50 + function CommentForm({ 51 + requestId, 52 + sphereSlug, 53 + onCreated, 54 + }: { 55 + requestId: string; 56 + sphereSlug: string; 57 + onCreated: (comment: FeatureRequestCommentListItem) => void; 58 + }) { 59 + const content = useSignal(""); 60 + const submitting = useSignal(false); 61 + const error = useSignal(""); 62 + 63 + const handleSubmit = async (e: Event) => { 64 + e.preventDefault(); 65 + if (submitting.value || !content.value.trim()) return; 66 + submitting.value = true; 67 + error.value = ""; 68 + try { 69 + const res = await createComment(requestId, content.value.trim(), sphereSlug); 70 + content.value = ""; 71 + onCreated({ ...res.comment, voteCount: 0 }); 72 + } catch (err: unknown) { 73 + error.value = 74 + err instanceof ApiError && err.status === 409 75 + ? "You have already commented on this request." 76 + : "Failed to post comment."; 77 + } finally { 78 + submitting.value = false; 79 + } 80 + }; 81 + 82 + return ( 83 + <form onSubmit={handleSubmit} class={ui.stackSm}> 84 + <textarea 85 + class={ui.textarea} 86 + placeholder="Leave a comment..." 87 + value={content.value} 88 + onInput={(e) => { 89 + content.value = (e.target as HTMLTextAreaElement).value; 90 + }} 91 + onKeyDown={(e) => { 92 + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { 93 + e.preventDefault(); 94 + (e.target as HTMLTextAreaElement).form?.requestSubmit(); 95 + } 96 + }} 97 + rows={3} 98 + /> 99 + {error.value && <p class={ui.errorText}>{error.value}</p>} 100 + <div> 101 + <button 102 + type="submit" 103 + class={ui.button} 104 + disabled={submitting.value || !content.value.trim()} 105 + > 106 + {submitting.value ? "Posting..." : "Comment"} 107 + </button> 108 + </div> 109 + </form> 110 + ); 111 + } 112 + 113 + function CommentItem({ 114 + comment, 115 + isAuthor, 116 + isAdminOrOwner, 117 + isAuthenticated, 118 + hasVoted, 119 + onDelete, 120 + onUpdate, 121 + onVote, 122 + onUnvote, 123 + }: { 124 + comment: FeatureRequestCommentListItem; 125 + isAuthor: boolean; 126 + isAdminOrOwner: boolean; 127 + isAuthenticated: boolean; 128 + hasVoted: boolean; 129 + onDelete: (id: string) => void; 130 + onUpdate: (id: string, content: string) => Promise<void>; 131 + onVote: (id: string) => Promise<void>; 132 + onUnvote: (id: string) => Promise<void>; 133 + }) { 134 + const editing = useSignal(false); 135 + const editContent = useSignal(comment.content); 136 + const saving = useSignal(false); 137 + const voting = useSignal(false); 138 + 139 + const handleSave = async () => { 140 + if (saving.value || !editContent.value.trim()) return; 141 + saving.value = true; 142 + try { 143 + await onUpdate(comment.id, editContent.value.trim()); 144 + editing.value = false; 145 + } finally { 146 + saving.value = false; 147 + } 148 + }; 149 + 150 + const handleCancel = () => { 151 + editContent.value = comment.content; 152 + editing.value = false; 153 + }; 154 + 155 + const handleVote = async () => { 156 + if (voting.value) return; 157 + voting.value = true; 158 + try { 159 + if (hasVoted) { 160 + await onUnvote(comment.id); 161 + } else { 162 + await onVote(comment.id); 163 + } 164 + } finally { 165 + voting.value = false; 166 + } 167 + }; 168 + 169 + const voteClasses = `${frUi.commentVoteButton}${hasVoted ? ` ${frUi.commentVoteButtonActive}` : ""}`; 170 + 171 + return ( 172 + <div class={ui.cardFlat}> 173 + <div class={ui.metaRow}> 174 + <span class={ui.muted}>{comment.authorDid}</span> 175 + <span class={ui.muted}>{formatDate(comment.createdAt, fullDateOpts)}</span> 176 + {comment.createdAt !== comment.updatedAt && <span class={ui.muted}>(edited)</span>} 177 + {isAuthor && !editing.value && ( 178 + <button class={ui.buttonInline} onClick={() => (editing.value = true)}> 179 + Edit 180 + </button> 181 + )} 182 + {(isAuthor || isAdminOrOwner) && !editing.value && ( 183 + <button class={ui.buttonDangerInline} onClick={() => onDelete(comment.id)}> 184 + Delete 185 + </button> 186 + )} 187 + </div> 188 + {editing.value ? ( 189 + <div class={`${ui.stackSm} ${frUi.commentEditArea}`}> 190 + <textarea 191 + class={ui.textarea} 192 + value={editContent.value} 193 + onInput={(e) => { 194 + editContent.value = (e.target as HTMLTextAreaElement).value; 195 + }} 196 + onKeyDown={(e) => { 197 + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { 198 + e.preventDefault(); 199 + handleSave(); 200 + } 201 + if (e.key === "Escape") { 202 + handleCancel(); 203 + } 204 + }} 205 + rows={3} 206 + /> 207 + <div class={ui.row}> 208 + <button 209 + class={ui.button} 210 + onClick={handleSave} 211 + disabled={saving.value || !editContent.value.trim()} 212 + > 213 + {saving.value ? "Saving..." : "Save"} 214 + </button> 215 + <button class={ui.buttonInline} onClick={handleCancel}> 216 + Cancel 217 + </button> 218 + </div> 219 + </div> 220 + ) : ( 221 + <div class={frUi.commentBody}>{comment.content}</div> 222 + )} 223 + <div class={frUi.commentVoteRow}> 224 + {isAuthenticated ? ( 225 + <button 226 + class={voteClasses} 227 + onClick={handleVote} 228 + title={hasVoted ? "Remove vote" : "Upvote"} 229 + > 230 + <span>{hasVoted ? "\u25B2" : "\u25B3"}</span> 231 + {comment.voteCount} 232 + </button> 233 + ) : comment.voteCount > 0 ? ( 234 + <span class={ui.muted}> 235 + {"\u25B3"} {comment.voteCount} 236 + </span> 237 + ) : null} 238 + </div> 239 + </div> 240 + ); 241 + } 242 + 243 + function StatusHistory({ requestId, version }: { requestId: string; version: number }) { 244 + const { data, pending, loading } = useQuery( 245 + () => getStatusHistory(requestId), 246 + [requestId, version], 247 + ); 248 + 249 + if (pending) return loading ? <p class={ui.muted}>Loading history...</p> : null; 250 + if (!data || data.statuses.length === 0) return null; 251 + 252 + return ( 253 + <CollapsibleSection title="Status history"> 254 + <div class={ui.stackSm}> 255 + {data.statuses.map((entry) => ( 256 + <div key={entry.id} class={ui.metaRow}> 257 + <span class={ui.muted}>{entry.authorDid}</span> 258 + <span> 259 + set status to <strong>{statusLabel(entry.status)}</strong> 260 + </span> 261 + <span class={ui.muted}>{formatDate(entry.createdAt, fullDateOpts)}</span> 262 + </div> 263 + ))} 264 + </div> 265 + </CollapsibleSection> 266 + ); 267 + } 268 + 269 + function CommentsSection({ 270 + requestId, 271 + sphereSlug, 272 + status, 273 + isAuthenticated, 274 + isAdminOrOwner, 275 + currentDid, 276 + }: { 277 + requestId: string; 278 + sphereSlug: string; 279 + status: string; 280 + isAuthenticated: boolean; 281 + isAdminOrOwner: boolean; 282 + currentDid: string | null; 283 + }) { 284 + const prefetchedComments = ssrPageData.value?.["feature-request-comments"] as 285 + | Awaited<ReturnType<typeof getComments>> 286 + | undefined; 287 + 288 + const comments = useSignal<FeatureRequestCommentListItem[]>(prefetchedComments?.comments ?? []); 289 + const votedCommentIds = useSignal<Set<string>>(new Set()); 290 + 291 + const { sortBy: commentSortBy, sortOrder: commentSortOrder } = useSortParams("comments_"); 292 + const { data, pending, loading } = useQuery( 293 + () => getComments(requestId, commentSortBy.value, commentSortOrder.value), 294 + [requestId, commentSortBy.value, commentSortOrder.value], 295 + prefetchedComments ? { initialData: prefetchedComments } : undefined, 296 + ); 297 + 298 + useEffect(() => { 299 + if (data) { 300 + comments.value = data.comments; 301 + } 302 + }, [data]); 303 + 304 + const commentVotesQuery = useQuery( 305 + () => (isAuthenticated ? getMyCommentVotes() : Promise.resolve(null)), 306 + [isAuthenticated], 307 + ); 308 + useEffect(() => { 309 + if (commentVotesQuery.data) { 310 + votedCommentIds.value = new Set(commentVotesQuery.data.votes); 311 + } 312 + }, [commentVotesQuery.data]); 313 + 314 + const hasCommented = useComputed( 315 + () => isAuthenticated && comments.value.some((c) => c.authorDid === currentDid), 316 + ); 317 + 318 + const handleCreated = (comment: FeatureRequestCommentListItem) => { 319 + comments.value = [...comments.value, comment]; 320 + }; 321 + 322 + const handleUpdate = async (id: string, content: string) => { 323 + const res = await updateComment(id, content, sphereSlug); 324 + comments.value = comments.value.map((c) => 325 + c.id === id ? { ...res.comment, voteCount: c.voteCount } : c, 326 + ); 327 + }; 328 + 329 + const handleDelete = async (id: string) => { 330 + try { 331 + await deleteComment(id, sphereSlug); 332 + comments.value = comments.value.filter((c) => c.id !== id); 333 + } catch (err) { 334 + console.error("Failed to delete comment:", err); 335 + } 336 + }; 337 + 338 + const handleCommentVote = async (id: string) => { 339 + const prev = votedCommentIds.value; 340 + votedCommentIds.value = new Set([...prev, id]); 341 + comments.value = comments.value.map((c) => 342 + c.id === id ? { ...c, voteCount: c.voteCount + 1 } : c, 343 + ); 344 + 345 + try { 346 + await voteComment(id, sphereSlug); 347 + } catch { 348 + votedCommentIds.value = prev; 349 + comments.value = comments.value.map((c) => 350 + c.id === id ? { ...c, voteCount: c.voteCount - 1 } : c, 351 + ); 352 + } 353 + }; 354 + 355 + const handleCommentUnvote = async (id: string) => { 356 + const prev = votedCommentIds.value; 357 + const next = new Set(prev); 358 + next.delete(id); 359 + votedCommentIds.value = next; 360 + comments.value = comments.value.map((c) => 361 + c.id === id ? { ...c, voteCount: c.voteCount - 1 } : c, 362 + ); 363 + 364 + try { 365 + await unvoteComment(id); 366 + } catch { 367 + votedCommentIds.value = prev; 368 + comments.value = comments.value.map((c) => 369 + c.id === id ? { ...c, voteCount: c.voteCount + 1 } : c, 370 + ); 371 + } 372 + }; 373 + 374 + const commentTitle = `Comments${comments.value.length > 0 ? ` (${comments.value.length})` : ""}`; 375 + 376 + return ( 377 + <CollapsibleSection title={commentTitle} defaultExpanded> 378 + <SortControls id="comment-sort-by" sortBy={commentSortBy} sortOrder={commentSortOrder} /> 379 + {pending && comments.value.length === 0 ? ( 380 + loading ? ( 381 + <p class={ui.muted}>Loading comments...</p> 382 + ) : null 383 + ) : comments.value.length === 0 ? ( 384 + <p class={ui.muted}>No comments yet.</p> 385 + ) : ( 386 + <div class={`${ui.stack}${pending ? ` ${frUi.pendingFade}` : ""}`}> 387 + {comments.value.map((comment) => ( 388 + <CommentItem 389 + key={comment.id} 390 + comment={comment} 391 + isAuthor={currentDid === comment.authorDid} 392 + isAdminOrOwner={isAdminOrOwner} 393 + isAuthenticated={isAuthenticated} 394 + hasVoted={votedCommentIds.value.has(comment.id)} 395 + onDelete={handleDelete} 396 + onUpdate={handleUpdate} 397 + onVote={handleCommentVote} 398 + onUnvote={handleCommentUnvote} 399 + /> 400 + ))} 401 + </div> 402 + )} 403 + 404 + {isAuthenticated && 405 + !hasCommented.value && 406 + status !== "done" && 407 + status !== "not-planned" && 408 + status !== "duplicate" && ( 409 + <CommentForm requestId={requestId} sphereSlug={sphereSlug} onCreated={handleCreated} /> 410 + )} 411 + </CollapsibleSection> 412 + ); 413 + } 414 + 415 + function MergedRequestsSection({ 416 + requestId, 417 + duplicateCount, 418 + canMerge, 419 + sphereSlug, 420 + onMarked, 421 + }: { 422 + requestId: string; 423 + duplicateCount: number; 424 + canMerge: boolean; 425 + sphereSlug: string; 426 + onMarked: () => void; 427 + }) { 428 + const duplicates = useSignal<Array<{ id: string; number: number; title: string }>>([]); 429 + const loaded = useSignal(false); 430 + const loading = useSignal(false); 431 + 432 + const handleToggle = async (expanded: boolean) => { 433 + if (expanded && !loaded.value && duplicateCount > 0) { 434 + loading.value = true; 435 + try { 436 + const res = await getDuplicates(requestId); 437 + duplicates.value = res.duplicates; 438 + loaded.value = true; 439 + } catch { 440 + duplicates.value = []; 441 + } finally { 442 + loading.value = false; 443 + } 444 + } 445 + }; 446 + 447 + const mergedTitle = `Duplicated requests (${duplicateCount})`; 448 + 449 + return ( 450 + <CollapsibleSection title={mergedTitle} onToggle={handleToggle}> 451 + {loading.value && <p class={ui.muted}>Loading...</p>} 452 + {duplicates.value.length > 0 && ( 453 + <div class={ui.stackSm}> 454 + {duplicates.value.map((d) => ( 455 + <div key={d.id}> 456 + <a href={`/infuse/${d.number}`}> 457 + <span class={ui.muted}>#{d.number}</span> {d.title} 458 + </a> 459 + </div> 460 + ))} 461 + </div> 462 + )} 463 + {canMerge && <MergeDropdown frId={requestId} sphereSlug={sphereSlug} onMarked={onMarked} />} 464 + </CollapsibleSection> 465 + ); 466 + } 467 + 468 + export function FeatureRequestPage() { 469 + const { params } = useRoute(); 470 + const { route } = useLocation(); 471 + const number = parseInt(params.number, 10); 472 + const slug = sphereSlug.value!; 473 + const votedIds = useSignal<Set<string>>(new Set()); 474 + const voteCountAdjust = useSignal(0); 475 + const statusVersion = useSignal(0); 476 + const prefetched = ssrPageData.value?.["feature-request"] as 477 + | Awaited<ReturnType<typeof getFeatureRequest>> 478 + | undefined; 479 + const { data, pending, loading, error, refetch } = useQuery( 480 + () => getFeatureRequest(number), 481 + [number], 482 + prefetched ? { initialData: prefetched } : undefined, 483 + ); 484 + 485 + const isAuthenticated = auth.value.authenticated; 486 + const currentDid = isAuthenticated ? auth.value.did : null; 487 + 488 + const votesQuery = useQuery( 489 + () => (isAuthenticated ? getMyVotes() : Promise.resolve(null)), 490 + [isAuthenticated], 491 + ); 492 + useEffect(() => { 493 + if (votesQuery.data) { 494 + votedIds.value = new Set(votesQuery.data.votes); 495 + } 496 + }, [votesQuery.data]); 497 + 498 + useEffect(() => { 499 + voteCountAdjust.value = 0; 500 + }, [data]); 501 + 502 + const isAdminOrOwner = (() => { 503 + const sphereData = sphereState.value.data; 504 + if (!isAuthenticated || !sphereData) return false; 505 + const role = sphereData.role; 506 + if (role === "owner" || role === "admin") return true; 507 + return sphereData.sphere.ownerDid === auth.value.did; 508 + })(); 509 + 510 + const fr = data?.featureRequest; 511 + 512 + const handleDelete = async () => { 513 + if (!fr) return; 514 + try { 515 + await deleteFeatureRequest(fr.id); 516 + route("/infuse"); 517 + } catch (err) { 518 + console.error("Failed to delete feature request:", err); 519 + } 520 + }; 521 + 522 + const handleHide = async () => { 523 + if (!fr) return; 524 + try { 525 + await hideFeatureRequest(fr.id, slug); 526 + route("/infuse"); 527 + } catch (err) { 528 + console.error("Failed to hide feature request:", err); 529 + } 530 + }; 531 + 532 + const handleVote = async () => { 533 + if (!fr) return; 534 + const prev = votedIds.value; 535 + votedIds.value = new Set([...prev, fr.id]); 536 + voteCountAdjust.value++; 537 + 538 + try { 539 + await voteFeatureRequest(fr.id, slug); 540 + } catch { 541 + votedIds.value = prev; 542 + voteCountAdjust.value--; 543 + } 544 + }; 545 + 546 + const handleUnvote = async () => { 547 + if (!fr) return; 548 + const prev = votedIds.value; 549 + const next = new Set(prev); 550 + next.delete(fr.id); 551 + votedIds.value = next; 552 + voteCountAdjust.value--; 553 + 554 + try { 555 + await unvoteFeatureRequest(fr.id); 556 + } catch { 557 + votedIds.value = prev; 558 + voteCountAdjust.value++; 559 + } 560 + }; 561 + 562 + const handleStatusChange = async (status: string) => { 563 + if (!fr) return; 564 + const prev = fr.status; 565 + fr.status = status as typeof fr.status; 566 + try { 567 + await updateFeatureRequestStatus(fr.id, status, slug); 568 + statusVersion.value++; 569 + // Refetch when changing from "duplicate" to clear duplicate info 570 + if (prev === "duplicate") { 571 + refetch(); 572 + } 573 + } catch { 574 + fr.status = prev; 575 + } 576 + }; 577 + 578 + return ( 579 + <div class={ui.container}> 580 + <div class={ui.section}> 581 + <div> 582 + <a href="/infuse" class={ui.muted}> 583 + &larr; All requests 584 + </a> 585 + </div> 586 + 587 + {pending ? ( 588 + loading ? ( 589 + <p class={ui.muted}>Loading...</p> 590 + ) : null 591 + ) : error ? ( 592 + <p class={ui.errorText}>{error}</p> 593 + ) : !fr ? ( 594 + <p class={ui.muted}>Feature request not found.</p> 595 + ) : ( 596 + <div class={frUi.detailContent}> 597 + <div class={ui.stackLg}> 598 + <RequestCard 599 + fr={{ ...fr, voteCount: fr.voteCount + voteCountAdjust.value }} 600 + isAuthor={currentDid === fr.authorDid} 601 + isAdminOrOwner={isAdminOrOwner} 602 + hasVoted={votedIds.value.has(fr.id)} 603 + isAuthenticated={isAuthenticated} 604 + isDetail 605 + onDelete={() => handleDelete()} 606 + onHide={() => handleHide()} 607 + onVote={() => handleVote()} 608 + onUnvote={() => handleUnvote()} 609 + /> 610 + 611 + {isAdminOrOwner && ( 612 + <div class={ui.metaRow}> 613 + <span class={ui.muted}>Status:</span> 614 + <select 615 + class={frUi.sortSelect} 616 + value={fr.status} 617 + onChange={(e) => handleStatusChange((e.target as HTMLSelectElement).value)} 618 + > 619 + {fr.status === "duplicate" && ( 620 + <option value="duplicate">{statusLabels.duplicate}</option> 621 + )} 622 + {settableStatuses.map((s) => ( 623 + <option key={s} value={s}> 624 + {statusLabels[s]} 625 + </option> 626 + ))} 627 + </select> 628 + </div> 629 + )} 630 + 631 + {data.duplicateOf && ( 632 + <p class={ui.muted}> 633 + Duplicate of{" "} 634 + <a href={`/infuse/${data.duplicateOf.number}`}> 635 + #{data.duplicateOf.number} &mdash; {data.duplicateOf.title} 636 + </a> 637 + </p> 638 + )} 639 + </div> 640 + 641 + <div class={frUi.descriptionBlock}>{fr.description}</div> 642 + 643 + <CommentsSection 644 + requestId={fr.id} 645 + sphereSlug={slug} 646 + status={fr.status} 647 + isAuthenticated={isAuthenticated} 648 + isAdminOrOwner={isAdminOrOwner} 649 + currentDid={currentDid} 650 + /> 651 + 652 + <StatusHistory requestId={fr.id} version={statusVersion.value} /> 653 + 654 + {((currentDid === fr.authorDid || isAdminOrOwner) && fr.status === "requested") || 655 + data.duplicateCount > 0 ? ( 656 + <MergedRequestsSection 657 + requestId={fr.id} 658 + duplicateCount={data.duplicateCount} 659 + canMerge={ 660 + (currentDid === fr.authorDid || isAdminOrOwner) && 661 + data.duplicateCount === 0 && 662 + fr.status === "requested" 663 + } 664 + sphereSlug={slug} 665 + onMarked={() => refetch()} 666 + /> 667 + ) : null} 668 + </div> 669 + )} 670 + </div> 671 + </div> 672 + ); 673 + }
+339
packages/feature-requests/src/ui/pages/feature-requests.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { auth } from "@exosphere/client/auth"; 3 + import { sphereState, sphereSlug } from "@exosphere/client/sphere"; 4 + import { useQuery } from "@exosphere/client/hooks"; 5 + import * as ui from "@exosphere/client/ui.css"; 6 + import * as frUi from "../ui.css.ts"; 7 + import { ssrPageData } from "@exosphere/client/ssr-data"; 8 + import { 9 + getFeatureRequests, 10 + createFeatureRequest, 11 + deleteFeatureRequest, 12 + hideFeatureRequest, 13 + voteFeatureRequest, 14 + unvoteFeatureRequest, 15 + getMyVotes, 16 + } from "../api/index.ts"; 17 + import { categories, categoryLabels } from "../../schemas/feature-request.ts"; 18 + import { RequestCard } from "../components/request-card.tsx"; 19 + import { SortControls } from "../components/sort-controls.tsx"; 20 + import { useSortParams } from "../hooks/use-sort-params.ts"; 21 + import { useEffect } from "preact/hooks"; 22 + 23 + const categoryOptions = categories.map((value) => ({ 24 + value, 25 + label: categoryLabels[value], 26 + })); 27 + 28 + function SubmitForm({ slug, onCreated }: { slug: string; onCreated: () => void }) { 29 + const title = useSignal(""); 30 + const description = useSignal(""); 31 + const category = useSignal("general"); 32 + const error = useSignal(""); 33 + const submitting = useSignal(false); 34 + 35 + const handleKeyDown = (e: KeyboardEvent) => { 36 + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { 37 + e.preventDefault(); 38 + submit(); 39 + } 40 + }; 41 + 42 + const submit = async (e?: Event) => { 43 + e?.preventDefault(); 44 + if (!title.value.trim()) { 45 + error.value = "Title is required."; 46 + return; 47 + } 48 + if (!description.value.trim()) { 49 + error.value = "Description is required."; 50 + return; 51 + } 52 + 53 + submitting.value = true; 54 + error.value = ""; 55 + 56 + try { 57 + await createFeatureRequest({ 58 + title: title.value.trim(), 59 + description: description.value.trim(), 60 + category: category.value, 61 + sphereSlug: slug, 62 + }); 63 + title.value = ""; 64 + description.value = ""; 65 + category.value = "general"; 66 + onCreated(); 67 + } catch (err) { 68 + error.value = err instanceof Error ? err.message : "Something went wrong."; 69 + } finally { 70 + submitting.value = false; 71 + } 72 + }; 73 + 74 + return ( 75 + <form onSubmit={submit} onKeyDown={handleKeyDown} class={ui.formStack}> 76 + <div> 77 + <label class={ui.label} htmlFor="fr-title"> 78 + Title 79 + </label> 80 + <input 81 + id="fr-title" 82 + class={ui.input} 83 + type="text" 84 + maxLength={200} 85 + placeholder="Brief summary of the request" 86 + value={title.value} 87 + onInput={(e) => (title.value = (e.target as HTMLInputElement).value)} 88 + /> 89 + </div> 90 + 91 + <div> 92 + <label class={ui.label} htmlFor="fr-category"> 93 + Category 94 + </label> 95 + <select 96 + id="fr-category" 97 + class={ui.select} 98 + value={category.value} 99 + onChange={(e) => (category.value = (e.target as HTMLSelectElement).value)} 100 + > 101 + {categoryOptions.map((c) => ( 102 + <option key={c.value} value={c.value}> 103 + {c.label} 104 + </option> 105 + ))} 106 + </select> 107 + </div> 108 + 109 + <div> 110 + <label class={ui.label} htmlFor="fr-description"> 111 + Description 112 + </label> 113 + <textarea 114 + id="fr-description" 115 + class={ui.textarea} 116 + maxLength={10000} 117 + placeholder="Describe the feature and why it would be useful" 118 + value={description.value} 119 + onInput={(e) => (description.value = (e.target as HTMLTextAreaElement).value)} 120 + /> 121 + </div> 122 + 123 + {error.value && <p class={ui.errorText}>{error.value}</p>} 124 + 125 + <div> 126 + <button type="submit" class={ui.button} disabled={submitting.value}> 127 + {submitting.value ? "Submitting..." : "Submit request"} 128 + </button> 129 + </div> 130 + </form> 131 + ); 132 + } 133 + 134 + const activeStatuses = ["requested", "approved", "in-progress"]; 135 + 136 + type ActiveTab = "requests" | "done" | "not-planned"; 137 + 138 + export function FeatureRequestsDonePage() { 139 + return <FeatureRequestsPage statuses={["done"]} activeTab="done" />; 140 + } 141 + 142 + export function FeatureRequestsNotPlannedPage() { 143 + return <FeatureRequestsPage statuses={["not-planned"]} activeTab="not-planned" />; 144 + } 145 + 146 + export function FeatureRequestsActivePage() { 147 + return <FeatureRequestsPage />; 148 + } 149 + 150 + function FeatureRequestsPage({ 151 + statuses = activeStatuses, 152 + activeTab = "requests", 153 + }: { 154 + statuses?: string[]; 155 + activeTab?: ActiveTab; 156 + } = {}) { 157 + const slug = sphereSlug.value!; 158 + const showForm = useSignal(false); 159 + const votedIds = useSignal<Set<string>>(new Set()); 160 + const { sortBy, sortOrder } = useSortParams(); 161 + const prefetchKey = 162 + activeTab === "requests" ? "feature-requests" : `feature-requests-${statuses.join(",")}`; 163 + const prefetched = ssrPageData.value?.[prefetchKey] as 164 + | Awaited<ReturnType<typeof getFeatureRequests>> 165 + | undefined; 166 + const { data, pending, loading, error, refetch } = useQuery( 167 + () => getFeatureRequests(statuses, sortBy.value, sortOrder.value), 168 + [activeTab, sortBy.value, sortOrder.value], 169 + prefetched ? { initialData: prefetched } : undefined, 170 + ); 171 + 172 + const isAuthenticated = auth.value.authenticated; 173 + const currentDid = isAuthenticated ? auth.value.did : null; 174 + 175 + // Fetch user's votes when authenticated 176 + const votesQuery = useQuery( 177 + () => (isAuthenticated ? getMyVotes() : Promise.resolve(null)), 178 + [isAuthenticated], 179 + ); 180 + useEffect(() => { 181 + if (votesQuery.data) { 182 + votedIds.value = new Set(votesQuery.data.votes); 183 + } 184 + }, [votesQuery.data]); 185 + 186 + const isAdminOrOwner = (() => { 187 + const sphereData = sphereState.value.data; 188 + if (!isAuthenticated || !sphereData) return false; 189 + const role = sphereData.role; 190 + if (role === "owner" || role === "admin") return true; 191 + return sphereData.sphere.ownerDid === auth.value.did; 192 + })(); 193 + 194 + const onCreated = () => { 195 + showForm.value = false; 196 + refetch(); 197 + }; 198 + 199 + const handleDelete = async (id: string) => { 200 + try { 201 + await deleteFeatureRequest(id); 202 + refetch(); 203 + } catch (err) { 204 + console.error("Failed to delete feature request:", err); 205 + } 206 + }; 207 + 208 + const handleHide = async (id: string) => { 209 + try { 210 + await hideFeatureRequest(id, slug); 211 + refetch(); 212 + } catch (err) { 213 + console.error("Failed to hide feature request:", err); 214 + } 215 + }; 216 + 217 + const handleVote = async (id: string) => { 218 + // Optimistic update 219 + const prev = votedIds.value; 220 + votedIds.value = new Set([...prev, id]); 221 + if (data) { 222 + const fr = data.featureRequests.find((r) => r.id === id); 223 + if (fr) fr.voteCount++; 224 + } 225 + 226 + try { 227 + await voteFeatureRequest(id, slug); 228 + } catch { 229 + // Revert on failure 230 + votedIds.value = prev; 231 + if (data) { 232 + const fr = data.featureRequests.find((r) => r.id === id); 233 + if (fr) fr.voteCount--; 234 + } 235 + } 236 + }; 237 + 238 + const handleUnvote = async (id: string) => { 239 + // Optimistic update 240 + const prev = votedIds.value; 241 + const next = new Set(prev); 242 + next.delete(id); 243 + votedIds.value = next; 244 + if (data) { 245 + const fr = data.featureRequests.find((r) => r.id === id); 246 + if (fr) fr.voteCount--; 247 + } 248 + 249 + try { 250 + await unvoteFeatureRequest(id); 251 + } catch { 252 + // Revert on failure 253 + votedIds.value = prev; 254 + if (data) { 255 + const fr = data.featureRequests.find((r) => r.id === id); 256 + if (fr) fr.voteCount++; 257 + } 258 + } 259 + }; 260 + 261 + return ( 262 + <div class={ui.container}> 263 + <div class={ui.section}> 264 + <div class={frUi.titleRow}> 265 + <h1 class={ui.pageTitle}>Infuse</h1> 266 + {isAuthenticated && !showForm.value && ( 267 + <button class={ui.button} onClick={() => (showForm.value = true)}> 268 + New request 269 + </button> 270 + )} 271 + </div> 272 + 273 + <nav class={ui.tabNav}> 274 + {activeTab === "requests" ? ( 275 + <span class={ui.tabNavActive}>Requests</span> 276 + ) : ( 277 + <a href="/infuse" class={ui.tabNavLink}> 278 + Requests 279 + </a> 280 + )} 281 + {activeTab === "done" ? ( 282 + <span class={ui.tabNavActive}>Done</span> 283 + ) : ( 284 + <a href="/infuse/done" class={ui.tabNavLink}> 285 + Done 286 + </a> 287 + )} 288 + {activeTab === "not-planned" ? ( 289 + <span class={ui.tabNavActive}>Not planned</span> 290 + ) : ( 291 + <a href="/infuse/not-planned" class={ui.tabNavLink}> 292 + Not planned 293 + </a> 294 + )} 295 + </nav> 296 + 297 + {showForm.value && ( 298 + <div class={ui.card}> 299 + <SubmitForm slug={slug} onCreated={onCreated} /> 300 + </div> 301 + )} 302 + </div> 303 + 304 + <div class={ui.section}> 305 + <SortControls id="sort-by" sortBy={sortBy} sortOrder={sortOrder} /> 306 + 307 + {pending && !data ? ( 308 + loading ? ( 309 + <p class={ui.muted}>Loading...</p> 310 + ) : null 311 + ) : error ? ( 312 + <p class={ui.errorText}>{error}</p> 313 + ) : !data || data.featureRequests.length === 0 ? ( 314 + <p class={ui.muted}> 315 + No feature requests yet. 316 + {isAuthenticated && " Be the first to submit one."} 317 + </p> 318 + ) : ( 319 + <div class={ui.stackSm} style={pending ? { opacity: 0.6 } : undefined}> 320 + {data.featureRequests.map((fr) => ( 321 + <RequestCard 322 + key={fr.id} 323 + fr={fr} 324 + isAuthor={currentDid === fr.authorDid} 325 + isAdminOrOwner={isAdminOrOwner} 326 + hasVoted={votedIds.value.has(fr.id)} 327 + isAuthenticated={isAuthenticated} 328 + onDelete={handleDelete} 329 + onHide={handleHide} 330 + onVote={handleVote} 331 + onUnvote={handleUnvote} 332 + /> 333 + ))} 334 + </div> 335 + )} 336 + </div> 337 + </div> 338 + ); 339 + }
+174
packages/feature-requests/src/ui/ui.css.ts
··· 1 + import { style } from "@vanilla-extract/css"; 2 + import { vars } from "@exosphere/client/theme.css"; 3 + 4 + // ---- Vote buttons ---- 5 + 6 + const voteBase = { 7 + background: "none", 8 + border: `1px solid ${vars.color.border}`, 9 + borderRadius: vars.radius.sm, 10 + padding: vars.space.sm, 11 + width: "60px", 12 + height: "66px", 13 + fontSize: "1rem", 14 + fontWeight: 600, 15 + color: vars.color.textMuted, 16 + display: "flex", 17 + flexDirection: "column", 18 + alignItems: "center", 19 + justifyContent: "center", 20 + gap: vars.space.xs, 21 + boxSizing: "border-box", 22 + lineHeight: 1, 23 + } as const; 24 + 25 + export const voteButton = style({ 26 + ...voteBase, 27 + fontFamily: vars.font.body, 28 + cursor: "pointer", 29 + transition: "border-color 0.15s, color 0.15s, background-color 0.15s", 30 + ":hover": { 31 + borderColor: vars.color.primary, 32 + color: vars.color.primary, 33 + }, 34 + ":disabled": { opacity: 0.5, cursor: "not-allowed" }, 35 + }); 36 + 37 + export const voteButtonActive = style({ 38 + borderColor: vars.color.primary, 39 + color: vars.color.primary, 40 + backgroundColor: vars.color.primaryLight, 41 + }); 42 + 43 + export const voteCount = style({ 44 + ...voteBase, 45 + }); 46 + 47 + // ---- Card with vote layout ---- 48 + 49 + export const cardWithVote = style({ 50 + display: "flex", 51 + gap: vars.space.md, 52 + }); 53 + 54 + export const cardContent = style({ 55 + flex: 1, 56 + minInlineSize: 0, 57 + }); 58 + 59 + export const detailContent = style({ 60 + display: "flex", 61 + flexDirection: "column", 62 + gap: vars.space.xl, 63 + }); 64 + 65 + export const descriptionBlock = style({ 66 + fontSize: "0.9375rem", 67 + lineHeight: 1.7, 68 + whiteSpace: "pre-wrap", 69 + paddingBlockEnd: vars.space.xl, 70 + borderBlockEnd: `1px solid ${vars.color.border}`, 71 + }); 72 + 73 + // ---- Title row ---- 74 + 75 + export const titleRow = style({ 76 + display: "flex", 77 + alignItems: "center", 78 + justifyContent: "space-between", 79 + minBlockSize: "44px", 80 + }); 81 + 82 + // ---- Sort controls ---- 83 + 84 + export const sortRow = style({ 85 + display: "flex", 86 + alignItems: "center", 87 + gap: vars.space.sm, 88 + }); 89 + 90 + export const sortSelect = style({ 91 + boxSizing: "border-box", 92 + paddingBlock: "4px", 93 + paddingInline: vars.space.sm, 94 + borderRadius: vars.radius.sm, 95 + border: `1px solid ${vars.color.border}`, 96 + fontSize: "0.8125rem", 97 + color: vars.color.text, 98 + backgroundColor: vars.color.surface, 99 + fontFamily: vars.font.body, 100 + outline: "none", 101 + transition: "border-color 0.15s", 102 + ":focus": { 103 + borderColor: vars.color.primary, 104 + }, 105 + }); 106 + 107 + export const sortButton = style({ 108 + background: "none", 109 + border: `1px solid ${vars.color.border}`, 110 + borderRadius: vars.radius.sm, 111 + paddingBlock: "4px", 112 + paddingInline: vars.space.sm, 113 + fontSize: "0.8125rem", 114 + fontFamily: vars.font.body, 115 + color: vars.color.textMuted, 116 + cursor: "pointer", 117 + display: "inline-flex", 118 + alignItems: "center", 119 + transition: "border-color 0.15s, color 0.15s", 120 + ":hover": { 121 + borderColor: vars.color.primary, 122 + color: vars.color.primary, 123 + }, 124 + }); 125 + 126 + // ---- Comment vote buttons ---- 127 + 128 + export const commentVoteButton = style({ 129 + background: "none", 130 + border: `1px solid ${vars.color.border}`, 131 + borderRadius: vars.radius.sm, 132 + paddingBlock: "2px", 133 + paddingInline: vars.space.sm, 134 + fontSize: "0.8125rem", 135 + fontWeight: 500, 136 + fontFamily: vars.font.body, 137 + color: vars.color.textMuted, 138 + cursor: "pointer", 139 + display: "inline-flex", 140 + alignItems: "center", 141 + gap: "4px", 142 + lineHeight: 1.4, 143 + transition: "border-color 0.15s, color 0.15s, background-color 0.15s", 144 + ":hover": { 145 + borderColor: vars.color.primary, 146 + color: vars.color.primary, 147 + }, 148 + ":disabled": { opacity: 0.5, cursor: "not-allowed" }, 149 + }); 150 + 151 + export const commentVoteButtonActive = style({ 152 + borderColor: vars.color.primary, 153 + color: vars.color.primary, 154 + backgroundColor: vars.color.primaryLight, 155 + }); 156 + 157 + export const commentBody = style({ 158 + fontSize: "0.9375rem", 159 + lineHeight: 1.6, 160 + whiteSpace: "pre-wrap", 161 + marginBlockStart: vars.space.sm, 162 + }); 163 + 164 + export const commentVoteRow = style({ 165 + marginBlockStart: "6px", 166 + }); 167 + 168 + export const commentEditArea = style({ 169 + marginBlockStart: vars.space.sm, 170 + }); 171 + 172 + export const pendingFade = style({ 173 + opacity: 0.6, 174 + });
+4
packages/feature-requests/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+7
packages/feature-requests/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + include: ["src/**/*.test.ts"], 6 + }, 7 + });
+25
packages/feeds/package.json
··· 1 + { 2 + "name": "@exosphere/feeds", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + ".": "./src/index.ts", 8 + "./client": "./src/client.ts", 9 + "./client-ssr": "./src/client.ssr.ts", 10 + "./ui": "./src/ui/index.ts" 11 + }, 12 + "dependencies": { 13 + "@exosphere/client": "workspace:*", 14 + "@exosphere/core": "workspace:*", 15 + "@preact/signals": "^2.0.0", 16 + "drizzle-orm": "^0.45.1", 17 + "hono": "^4.7.0", 18 + "preact": "^10.25.0", 19 + "zod": "^4.3.6" 20 + }, 21 + "devDependencies": { 22 + "@types/bun": "latest", 23 + "typescript": "^5.7.0" 24 + } 25 + }
+23
packages/feeds/src/api/routes.ts
··· 1 + import { Hono } from "hono"; 2 + import { z } from "zod"; 3 + import { createPostSchema } from "../schemas/post.ts"; 4 + 5 + const app = new Hono(); 6 + 7 + app.get("/", (c) => { 8 + // TODO: fetch posts from db 9 + return c.json({ posts: [] }); 10 + }); 11 + 12 + app.post("/", async (c) => { 13 + const body = await c.req.json(); 14 + const result = createPostSchema.safeParse(body); 15 + if (!result.success) { 16 + return c.json({ error: z.flattenError(result.error) }, 400); 17 + } 18 + 19 + // TODO: write to PDS + index in SQLite 20 + return c.json({ post: result.data }, 201); 21 + }); 22 + 23 + export { app as feedsApi };
+7
packages/feeds/src/client.ssr.ts
··· 1 + import type { ClientModule } from "@exosphere/client/types"; 2 + import { FeedsPage } from "./ui/pages/feeds.tsx"; 3 + 4 + export const feedsModule: ClientModule = { 5 + name: "feeds", 6 + routes: [{ path: "/feeds", component: FeedsPage }], 7 + };
+9
packages/feeds/src/client.ts
··· 1 + import { lazy } from "@exosphere/client/router"; 2 + import type { ClientModule } from "@exosphere/client/types"; 3 + 4 + const FeedsPage = lazy(() => import("./ui/pages/feeds.tsx").then((m) => m.FeedsPage)); 5 + 6 + export const feedsModule: ClientModule = { 7 + name: "feeds", 8 + routes: [{ path: "/feeds", component: FeedsPage }], 9 + };
+23
packages/feeds/src/db/schema.ts
··· 1 + import { sqliteTable, text, index } from "drizzle-orm/sqlite-core"; 2 + import { sql } from "drizzle-orm"; 3 + 4 + export const feedPosts = sqliteTable( 5 + "feed_posts", 6 + { 7 + id: text("id").primaryKey(), 8 + authorDid: text("author_did").notNull(), 9 + content: text("content").notNull(), 10 + parentId: text("parent_id"), 11 + pdsUri: text("pds_uri"), 12 + createdAt: text("created_at") 13 + .notNull() 14 + .default(sql`(datetime('now'))`), 15 + updatedAt: text("updated_at") 16 + .notNull() 17 + .default(sql`(datetime('now'))`), 18 + }, 19 + (table) => [ 20 + index("idx_feed_posts_parent").on(table.parentId), 21 + index("idx_feed_posts_created").on(table.createdAt), 22 + ], 23 + );
+7
packages/feeds/src/index.ts
··· 1 + import type { ExosphereModule } from "@exosphere/core/types"; 2 + import { feedsApi } from "./api/routes.ts"; 3 + 4 + export const feedsModule: ExosphereModule = { 5 + name: "feeds", 6 + api: feedsApi, 7 + };
+8
packages/feeds/src/schemas/post.ts
··· 1 + import { z } from "zod"; 2 + 3 + export const createPostSchema = z.object({ 4 + content: z.string().min(1).max(10000), 5 + parentId: z.string().optional(), 6 + }); 7 + 8 + export type CreatePost = z.infer<typeof createPostSchema>;
+36
packages/feeds/src/ui/feed.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { useEffect } from "preact/hooks"; 3 + 4 + interface Post { 5 + id: string; 6 + content: string; 7 + author_did: string; 8 + created_at: string; 9 + } 10 + 11 + export function Feed() { 12 + const posts = useSignal<Post[]>([]); 13 + 14 + useEffect(() => { 15 + fetch("/api/feeds") 16 + .then((r) => r.json()) 17 + .then((data) => { 18 + posts.value = data.posts; 19 + }); 20 + }, []); 21 + 22 + return ( 23 + <section> 24 + <h2>Feed</h2> 25 + {posts.value.length === 0 ? ( 26 + <p>No posts yet.</p> 27 + ) : ( 28 + <ul> 29 + {posts.value.map((post) => ( 30 + <li key={post.id}>{post.content}</li> 31 + ))} 32 + </ul> 33 + )} 34 + </section> 35 + ); 36 + }
+1
packages/feeds/src/ui/index.ts
··· 1 + export { Feed } from "./feed.tsx";
+12
packages/feeds/src/ui/pages/feeds.tsx
··· 1 + import * as ui from "@exosphere/client/ui.css"; 2 + 3 + export function FeedsPage() { 4 + return ( 5 + <div class={ui.container}> 6 + <div class={ui.section}> 7 + <h1 class={ui.pageTitle}>Feeds</h1> 8 + <p class={ui.muted}>Coming soon.</p> 9 + </div> 10 + </div> 11 + ); 12 + }
+4
packages/feeds/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+20
packages/indexer/package.json
··· 1 + { 2 + "name": "@exosphere/indexer", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + ".": "./src/index.ts", 8 + "./modules": "./src/modules.ts" 9 + }, 10 + "dependencies": { 11 + "@exosphere/core": "workspace:*", 12 + "@exosphere/feature-requests": "workspace:*", 13 + "@exosphere/feeds": "workspace:*", 14 + "drizzle-orm": "^0.45.1" 15 + }, 16 + "devDependencies": { 17 + "@types/bun": "latest", 18 + "typescript": "^5.7.0" 19 + } 20 + }
+70
packages/indexer/src/cursor.ts
··· 1 + import { eq, sql } from "drizzle-orm"; 2 + import { getDb } from "@exosphere/core/db"; 3 + import { indexerCursor } from "@exosphere/core/db/schema"; 4 + 5 + const FLUSH_INTERVAL_MS = 5_000; 6 + const FLUSH_EVENT_COUNT = 500; 7 + 8 + let pendingCursor: number | null = null; 9 + let eventsSinceFlush = 0; 10 + let flushTimer: ReturnType<typeof setInterval> | null = null; 11 + let tableEnsured = false; 12 + 13 + function ensureTable(): void { 14 + if (tableEnsured) return; 15 + getDb().run(sql`CREATE TABLE IF NOT EXISTS indexer_cursor ( 16 + id TEXT PRIMARY KEY DEFAULT 'jetstream', 17 + cursor INTEGER NOT NULL, 18 + updated_at TEXT NOT NULL DEFAULT (datetime('now')) 19 + )`); 20 + tableEnsured = true; 21 + } 22 + 23 + export function getCursor(): number | null { 24 + ensureTable(); 25 + const row = getDb() 26 + .select({ cursor: indexerCursor.cursor }) 27 + .from(indexerCursor) 28 + .where(eq(indexerCursor.id, "jetstream")) 29 + .get(); 30 + return row?.cursor ?? null; 31 + } 32 + 33 + function flushCursor(): void { 34 + if (pendingCursor === null) return; 35 + ensureTable(); 36 + const cursor = pendingCursor; 37 + pendingCursor = null; 38 + eventsSinceFlush = 0; 39 + 40 + getDb() 41 + .insert(indexerCursor) 42 + .values({ id: "jetstream", cursor, updatedAt: new Date().toISOString() }) 43 + .onConflictDoUpdate({ 44 + target: indexerCursor.id, 45 + set: { cursor, updatedAt: new Date().toISOString() }, 46 + }) 47 + .run(); 48 + } 49 + 50 + export function saveCursor(timeUs: number): void { 51 + pendingCursor = timeUs; 52 + eventsSinceFlush++; 53 + 54 + if (eventsSinceFlush >= FLUSH_EVENT_COUNT) { 55 + flushCursor(); 56 + } 57 + } 58 + 59 + export function startCursorFlushing(): void { 60 + if (flushTimer) return; 61 + flushTimer = setInterval(flushCursor, FLUSH_INTERVAL_MS); 62 + } 63 + 64 + export function stopCursorFlushing(): void { 65 + if (flushTimer) { 66 + clearInterval(flushTimer); 67 + flushTimer = null; 68 + } 69 + flushCursor(); 70 + }
+2
packages/indexer/src/index.ts
··· 1 + export { startJetstream } from "./start.ts"; 2 + export { stopCursorFlushing } from "./cursor.ts";
+10
packages/indexer/src/main.ts
··· 1 + import { startJetstream } from "./start.ts"; 2 + import { modules, coreIndexer } from "./modules.ts"; 3 + 4 + if (process.env.PDS_URL) { 5 + console.log("[indexer] PDS_URL is set — local dev mode, nothing to do. Exiting."); 6 + process.exit(0); 7 + } 8 + 9 + console.log("[indexer] Starting standalone Jetstream consumer..."); 10 + startJetstream(modules, coreIndexer);
+7
packages/indexer/src/modules.ts
··· 1 + import type { ExosphereModule } from "@exosphere/core/types"; 2 + import { coreIndexer } from "@exosphere/core/sphere"; 3 + import { feedsModule } from "@exosphere/feeds"; 4 + import { featureRequestsModule } from "@exosphere/feature-requests"; 5 + 6 + export const modules: ExosphereModule[] = [feedsModule, featureRequestsModule]; 7 + export { coreIndexer };
+94
packages/indexer/src/start.ts
··· 1 + import type { ExosphereModule, JetstreamCommitEvent, ModuleIndexer } from "@exosphere/core/types"; 2 + import { getCursor, saveCursor, startCursorFlushing, stopCursorFlushing } from "./cursor.ts"; 3 + 4 + const DEFAULT_JETSTREAM_URL = "wss://jetstream2.us-east.bsky.network/subscribe"; 5 + const MAX_BACKOFF_MS = 60_000; 6 + 7 + export function startJetstream(modules: ExosphereModule[], coreIndexer?: ModuleIndexer): void { 8 + // Disabled in local dev mode 9 + if (process.env.PDS_URL) { 10 + console.log("[indexer] PDS_URL is set — skipping Jetstream consumer (local dev mode)"); 11 + return; 12 + } 13 + 14 + // Collect indexers from modules + core 15 + const indexers: ModuleIndexer[] = []; 16 + if (coreIndexer) indexers.push(coreIndexer); 17 + for (const mod of modules) { 18 + if (mod.indexer) indexers.push(mod.indexer); 19 + } 20 + 21 + if (indexers.length === 0) { 22 + console.log("[indexer] No indexers registered — skipping Jetstream consumer"); 23 + return; 24 + } 25 + 26 + // Build collection → indexer dispatch map 27 + const dispatch = new Map<string, ModuleIndexer>(); 28 + const allCollections: string[] = []; 29 + for (const indexer of indexers) { 30 + for (const collection of indexer.collections) { 31 + dispatch.set(collection, indexer); 32 + allCollections.push(collection); 33 + } 34 + } 35 + 36 + const jetstreamUrl = process.env.JETSTREAM_URL ?? DEFAULT_JETSTREAM_URL; 37 + let backoffMs = 1_000; 38 + 39 + function connect() { 40 + const params = new URLSearchParams(); 41 + for (const c of allCollections) { 42 + params.append("wantedCollections", c); 43 + } 44 + const cursor = getCursor(); 45 + if (cursor !== null) { 46 + params.set("cursor", String(cursor)); 47 + } 48 + 49 + const url = `${jetstreamUrl}?${params.toString()}`; 50 + console.log(`[indexer] Connecting to Jetstream (${allCollections.length} collections)...`); 51 + 52 + const ws = new WebSocket(url); 53 + 54 + ws.addEventListener("open", () => { 55 + console.log("[indexer] Connected to Jetstream"); 56 + backoffMs = 1_000; 57 + startCursorFlushing(); 58 + }); 59 + 60 + ws.addEventListener("message", (event) => { 61 + try { 62 + const data = JSON.parse(String(event.data)) as JetstreamCommitEvent; 63 + if (data.kind !== "commit") return; 64 + 65 + const indexer = dispatch.get(data.commit.collection); 66 + if (!indexer) return; 67 + 68 + if (data.commit.operation === "create" || data.commit.operation === "update") { 69 + indexer.handleCreateOrUpdate(data); 70 + } else if (data.commit.operation === "delete") { 71 + indexer.handleDelete(data); 72 + } 73 + 74 + saveCursor(data.time_us); 75 + } catch (err) { 76 + console.error("[indexer] Error processing Jetstream event:", err); 77 + } 78 + }); 79 + 80 + ws.addEventListener("close", () => { 81 + console.log(`[indexer] Jetstream connection closed — reconnecting in ${backoffMs}ms`); 82 + stopCursorFlushing(); 83 + setTimeout(connect, backoffMs); 84 + backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS); 85 + }); 86 + 87 + ws.addEventListener("error", (err) => { 88 + console.error("[indexer] Jetstream WebSocket error:", err); 89 + ws.close(); 90 + }); 91 + } 92 + 93 + connect(); 94 + }
+4
packages/indexer/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+16
packages/mcp/package.json
··· 1 + { 2 + "name": "@exosphere/mcp", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "exports": { 7 + ".": "./src/index.ts" 8 + }, 9 + "dependencies": { 10 + "hono": "^4.7.0" 11 + }, 12 + "devDependencies": { 13 + "@types/bun": "latest", 14 + "typescript": "^5.7.0" 15 + } 16 + }
+66
packages/mcp/src/__tests__/protocol.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { 3 + jsonRpcResult, 4 + jsonRpcError, 5 + PROTOCOL_VERSION, 6 + SERVER_INFO, 7 + METHOD_NOT_FOUND, 8 + INVALID_PARAMS, 9 + PARSE_ERROR, 10 + } from "../protocol.ts"; 11 + 12 + describe("jsonRpcResult", () => { 13 + it("builds a valid JSON-RPC success response", () => { 14 + const res = jsonRpcResult(1, { foo: "bar" }); 15 + expect(res).toEqual({ 16 + jsonrpc: "2.0", 17 + id: 1, 18 + result: { foo: "bar" }, 19 + }); 20 + }); 21 + 22 + it("accepts a string id", () => { 23 + const res = jsonRpcResult("req-1", "ok"); 24 + expect(res.id).toBe("req-1"); 25 + expect(res.result).toBe("ok"); 26 + }); 27 + 28 + it("accepts null result", () => { 29 + const res = jsonRpcResult(1, null); 30 + expect(res.result).toBeNull(); 31 + expect(res.error).toBeUndefined(); 32 + }); 33 + }); 34 + 35 + describe("jsonRpcError", () => { 36 + it("builds a valid JSON-RPC error response", () => { 37 + const res = jsonRpcError(2, METHOD_NOT_FOUND, "Not found"); 38 + expect(res).toEqual({ 39 + jsonrpc: "2.0", 40 + id: 2, 41 + error: { code: METHOD_NOT_FOUND, message: "Not found" }, 42 + }); 43 + }); 44 + 45 + it("does not include a result field", () => { 46 + const res = jsonRpcError(1, PARSE_ERROR, "bad"); 47 + expect(res.result).toBeUndefined(); 48 + }); 49 + }); 50 + 51 + describe("constants", () => { 52 + it("exposes the protocol version", () => { 53 + expect(PROTOCOL_VERSION).toBe("2025-03-26"); 54 + }); 55 + 56 + it("exposes server info", () => { 57 + expect(SERVER_INFO.name).toBe("exosphere"); 58 + expect(SERVER_INFO.version).toBeDefined(); 59 + }); 60 + 61 + it("exposes standard JSON-RPC error codes", () => { 62 + expect(PARSE_ERROR).toBe(-32700); 63 + expect(METHOD_NOT_FOUND).toBe(-32601); 64 + expect(INVALID_PARAMS).toBe(-32602); 65 + }); 66 + });
+382
packages/mcp/src/__tests__/routes.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { createMcpRoutes } from "../routes.ts"; 4 + import { PROTOCOL_VERSION, SERVER_INFO } from "../protocol.ts"; 5 + import { tools } from "../tools/index.ts"; 6 + 7 + /** Helper: build a mock API backend and mount MCP routes on it. */ 8 + function createTestApp() { 9 + const api = new Hono(); 10 + 11 + // Mock sphere endpoint 12 + api.get("/api/spheres/current", (c) => 13 + c.json({ 14 + sphere: { name: "Test Sphere", slug: "test", visibility: "public" }, 15 + modules: ["feature-requests"], 16 + memberCount: 5, 17 + role: null, 18 + }), 19 + ); 20 + 21 + // Mock feature request list 22 + api.get("/api/feature-requests", (c) => { 23 + const status = c.req.query("status"); 24 + const items = [ 25 + { id: "fr1", number: 1, title: "Dark mode", status: "requested", voteCount: 3 }, 26 + { id: "fr2", number: 2, title: "API keys", status: "done", voteCount: 7 }, 27 + ]; 28 + const filtered = status ? items.filter((i) => status.split(",").includes(i.status)) : items; 29 + return c.json({ featureRequests: filtered }); 30 + }); 31 + 32 + // Mock get feature request by number 33 + api.get("/api/feature-requests/:number{[0-9]+}", (c) => { 34 + const num = parseInt(c.req.param("number"), 10); 35 + if (num === 1) { 36 + return c.json({ 37 + featureRequest: { 38 + id: "fr1", 39 + number: 1, 40 + title: "Dark mode", 41 + description: "Add dark mode support", 42 + status: "requested", 43 + voteCount: 3, 44 + commentCount: 1, 45 + }, 46 + duplicateOf: null, 47 + duplicateCount: 0, 48 + }); 49 + } 50 + return c.json({ error: "Feature request not found" }, 404); 51 + }); 52 + 53 + // Mock search 54 + api.get("/api/feature-requests/search", (c) => { 55 + const q = c.req.query("q") ?? ""; 56 + const results = 57 + q === "dark" 58 + ? [{ id: "fr1", number: 1, title: "Dark mode", status: "requested" }] 59 + : []; 60 + return c.json({ results }); 61 + }); 62 + 63 + // Mock comments 64 + api.get("/api/feature-requests/:id/comments", (c) => 65 + c.json({ 66 + comments: [ 67 + { id: "c1", authorDid: "did:plc:user1", content: "Great idea!", voteCount: 2 }, 68 + ], 69 + }), 70 + ); 71 + 72 + // Mock statuses 73 + api.get("/api/feature-requests/:id/statuses", (c) => 74 + c.json({ 75 + statuses: [ 76 + { id: "s1", authorDid: "did:plc:admin", status: "approved", createdAt: "2025-01-01" }, 77 + ], 78 + }), 79 + ); 80 + 81 + api.route("/mcp", createMcpRoutes((path) => api.request(path))); 82 + return api; 83 + } 84 + 85 + function mcpRequest(app: Hono, body: unknown) { 86 + return app.request("/mcp", { 87 + method: "POST", 88 + headers: { "Content-Type": "application/json" }, 89 + body: JSON.stringify(body), 90 + }); 91 + } 92 + 93 + // ---- Protocol tests ---- 94 + 95 + describe("MCP protocol", () => { 96 + const app = createTestApp(); 97 + 98 + describe("initialize", () => { 99 + it("returns server info and capabilities", async () => { 100 + const res = await mcpRequest(app, { 101 + jsonrpc: "2.0", 102 + id: 1, 103 + method: "initialize", 104 + params: { 105 + protocolVersion: PROTOCOL_VERSION, 106 + capabilities: {}, 107 + clientInfo: { name: "test", version: "1.0" }, 108 + }, 109 + }); 110 + 111 + expect(res.status).toBe(200); 112 + const json = await res.json(); 113 + expect(json.jsonrpc).toBe("2.0"); 114 + expect(json.id).toBe(1); 115 + expect(json.result.protocolVersion).toBe(PROTOCOL_VERSION); 116 + expect(json.result.capabilities.tools).toEqual({}); 117 + expect(json.result.serverInfo).toEqual(SERVER_INFO); 118 + }); 119 + }); 120 + 121 + describe("notifications", () => { 122 + it("returns 202 for notifications (no id)", async () => { 123 + const res = await mcpRequest(app, { 124 + jsonrpc: "2.0", 125 + method: "notifications/initialized", 126 + }); 127 + expect(res.status).toBe(202); 128 + }); 129 + }); 130 + 131 + describe("tools/list", () => { 132 + it("returns all registered tools", async () => { 133 + const res = await mcpRequest(app, { jsonrpc: "2.0", id: 2, method: "tools/list" }); 134 + const json = await res.json(); 135 + 136 + expect(json.result.tools).toHaveLength(tools.length); 137 + const names = json.result.tools.map((t: { name: string }) => t.name); 138 + expect(names).toContain("get_sphere"); 139 + expect(names).toContain("list_feature_requests"); 140 + expect(names).toContain("get_feature_request"); 141 + expect(names).toContain("search_feature_requests"); 142 + expect(names).toContain("get_feature_request_comments"); 143 + expect(names).toContain("get_feature_request_statuses"); 144 + }); 145 + 146 + it("includes inputSchema for each tool", async () => { 147 + const res = await mcpRequest(app, { jsonrpc: "2.0", id: 3, method: "tools/list" }); 148 + const json = await res.json(); 149 + 150 + for (const tool of json.result.tools) { 151 + expect(tool.inputSchema).toBeDefined(); 152 + expect(tool.inputSchema.type).toBe("object"); 153 + } 154 + }); 155 + }); 156 + 157 + describe("unknown method", () => { 158 + it("returns method not found error", async () => { 159 + const res = await mcpRequest(app, { jsonrpc: "2.0", id: 99, method: "unknown/method" }); 160 + const json = await res.json(); 161 + 162 + expect(json.error).toBeDefined(); 163 + expect(json.error.code).toBe(-32601); 164 + expect(json.error.message).toContain("unknown/method"); 165 + }); 166 + }); 167 + 168 + describe("tools/call with unknown tool", () => { 169 + it("returns invalid params error", async () => { 170 + const res = await mcpRequest(app, { 171 + jsonrpc: "2.0", 172 + id: 10, 173 + method: "tools/call", 174 + params: { name: "nonexistent_tool", arguments: {} }, 175 + }); 176 + const json = await res.json(); 177 + 178 + expect(json.error).toBeDefined(); 179 + expect(json.error.code).toBe(-32602); 180 + }); 181 + }); 182 + 183 + describe("batch requests", () => { 184 + it("handles a batch of requests", async () => { 185 + const res = await mcpRequest(app, [ 186 + { jsonrpc: "2.0", id: 1, method: "initialize", params: {} }, 187 + { jsonrpc: "2.0", id: 2, method: "tools/list" }, 188 + ]); 189 + const json = await res.json(); 190 + 191 + expect(Array.isArray(json)).toBe(true); 192 + expect(json).toHaveLength(2); 193 + expect(json[0].id).toBe(1); 194 + expect(json[1].id).toBe(2); 195 + }); 196 + 197 + it("returns 202 for batch of only notifications", async () => { 198 + const res = await mcpRequest(app, [ 199 + { jsonrpc: "2.0", method: "notifications/initialized" }, 200 + ]); 201 + expect(res.status).toBe(202); 202 + }); 203 + }); 204 + 205 + describe("GET and DELETE", () => { 206 + it("returns 405 for GET", async () => { 207 + const res = await app.request("/mcp"); 208 + expect(res.status).toBe(405); 209 + }); 210 + 211 + it("returns 405 for DELETE", async () => { 212 + const res = await app.request("/mcp", { method: "DELETE" }); 213 + expect(res.status).toBe(405); 214 + }); 215 + }); 216 + }); 217 + 218 + // ---- Tool tests ---- 219 + 220 + describe("tools/call", () => { 221 + const app = createTestApp(); 222 + 223 + describe("get_sphere", () => { 224 + it("returns sphere info", async () => { 225 + const res = await mcpRequest(app, { 226 + jsonrpc: "2.0", 227 + id: 1, 228 + method: "tools/call", 229 + params: { name: "get_sphere", arguments: {} }, 230 + }); 231 + const json = await res.json(); 232 + const content = JSON.parse(json.result.content[0].text); 233 + 234 + expect(content.sphere.name).toBe("Test Sphere"); 235 + expect(content.modules).toContain("feature-requests"); 236 + expect(content.memberCount).toBe(5); 237 + }); 238 + }); 239 + 240 + describe("list_feature_requests", () => { 241 + it("returns all feature requests without filters", async () => { 242 + const res = await mcpRequest(app, { 243 + jsonrpc: "2.0", 244 + id: 1, 245 + method: "tools/call", 246 + params: { name: "list_feature_requests", arguments: {} }, 247 + }); 248 + const json = await res.json(); 249 + const content = JSON.parse(json.result.content[0].text); 250 + 251 + expect(content.featureRequests).toHaveLength(2); 252 + }); 253 + 254 + it("passes status filter to the API", async () => { 255 + const res = await mcpRequest(app, { 256 + jsonrpc: "2.0", 257 + id: 1, 258 + method: "tools/call", 259 + params: { name: "list_feature_requests", arguments: { status: "requested" } }, 260 + }); 261 + const json = await res.json(); 262 + const content = JSON.parse(json.result.content[0].text); 263 + 264 + expect(content.featureRequests).toHaveLength(1); 265 + expect(content.featureRequests[0].title).toBe("Dark mode"); 266 + }); 267 + }); 268 + 269 + describe("get_feature_request", () => { 270 + it("returns a feature request by number", async () => { 271 + const res = await mcpRequest(app, { 272 + jsonrpc: "2.0", 273 + id: 1, 274 + method: "tools/call", 275 + params: { name: "get_feature_request", arguments: { number: 1 } }, 276 + }); 277 + const json = await res.json(); 278 + const content = JSON.parse(json.result.content[0].text); 279 + 280 + expect(content.featureRequest.title).toBe("Dark mode"); 281 + expect(content.featureRequest.voteCount).toBe(3); 282 + }); 283 + 284 + it("returns error for nonexistent feature request", async () => { 285 + const res = await mcpRequest(app, { 286 + jsonrpc: "2.0", 287 + id: 1, 288 + method: "tools/call", 289 + params: { name: "get_feature_request", arguments: { number: 999 } }, 290 + }); 291 + const json = await res.json(); 292 + 293 + expect(json.result.isError).toBe(true); 294 + expect(json.result.content[0].text).toContain("not found"); 295 + }); 296 + }); 297 + 298 + describe("search_feature_requests", () => { 299 + it("returns matching results", async () => { 300 + const res = await mcpRequest(app, { 301 + jsonrpc: "2.0", 302 + id: 1, 303 + method: "tools/call", 304 + params: { name: "search_feature_requests", arguments: { query: "dark" } }, 305 + }); 306 + const json = await res.json(); 307 + const content = JSON.parse(json.result.content[0].text); 308 + 309 + expect(content.results).toHaveLength(1); 310 + expect(content.results[0].title).toBe("Dark mode"); 311 + }); 312 + 313 + it("returns empty results for no match", async () => { 314 + const res = await mcpRequest(app, { 315 + jsonrpc: "2.0", 316 + id: 1, 317 + method: "tools/call", 318 + params: { name: "search_feature_requests", arguments: { query: "zzzzz" } }, 319 + }); 320 + const json = await res.json(); 321 + const content = JSON.parse(json.result.content[0].text); 322 + 323 + expect(content.results).toHaveLength(0); 324 + }); 325 + }); 326 + 327 + describe("get_feature_request_comments", () => { 328 + it("returns comments for a feature request", async () => { 329 + const res = await mcpRequest(app, { 330 + jsonrpc: "2.0", 331 + id: 1, 332 + method: "tools/call", 333 + params: { name: "get_feature_request_comments", arguments: { number: 1 } }, 334 + }); 335 + const json = await res.json(); 336 + const content = JSON.parse(json.result.content[0].text); 337 + 338 + expect(content.comments).toHaveLength(1); 339 + expect(content.comments[0].content).toBe("Great idea!"); 340 + }); 341 + 342 + it("returns error when feature request not found", async () => { 343 + const res = await mcpRequest(app, { 344 + jsonrpc: "2.0", 345 + id: 1, 346 + method: "tools/call", 347 + params: { name: "get_feature_request_comments", arguments: { number: 999 } }, 348 + }); 349 + const json = await res.json(); 350 + 351 + expect(json.result.isError).toBe(true); 352 + }); 353 + }); 354 + 355 + describe("get_feature_request_statuses", () => { 356 + it("returns status history for a feature request", async () => { 357 + const res = await mcpRequest(app, { 358 + jsonrpc: "2.0", 359 + id: 1, 360 + method: "tools/call", 361 + params: { name: "get_feature_request_statuses", arguments: { number: 1 } }, 362 + }); 363 + const json = await res.json(); 364 + const content = JSON.parse(json.result.content[0].text); 365 + 366 + expect(content.statuses).toHaveLength(1); 367 + expect(content.statuses[0].status).toBe("approved"); 368 + }); 369 + 370 + it("returns error when feature request not found", async () => { 371 + const res = await mcpRequest(app, { 372 + jsonrpc: "2.0", 373 + id: 1, 374 + method: "tools/call", 375 + params: { name: "get_feature_request_statuses", arguments: { number: 999 } }, 376 + }); 377 + const json = await res.json(); 378 + 379 + expect(json.result.isError).toBe(true); 380 + }); 381 + }); 382 + });
+1
packages/mcp/src/index.ts
··· 1 + export { createMcpRoutes } from "./routes.ts";
+36
packages/mcp/src/protocol.ts
··· 1 + /** MCP Streamable HTTP protocol types and helpers (JSON-RPC 2.0). */ 2 + 3 + export interface JsonRpcRequest { 4 + jsonrpc: "2.0"; 5 + id?: string | number; 6 + method: string; 7 + params?: Record<string, unknown>; 8 + } 9 + 10 + export interface JsonRpcResponse { 11 + jsonrpc: "2.0"; 12 + id: string | number; 13 + result?: unknown; 14 + error?: { code: number; message: string; data?: unknown }; 15 + } 16 + 17 + export function jsonRpcResult(id: string | number, result: unknown): JsonRpcResponse { 18 + return { jsonrpc: "2.0", id, result }; 19 + } 20 + 21 + export function jsonRpcError(id: string | number, code: number, message: string): JsonRpcResponse { 22 + return { jsonrpc: "2.0", id, error: { code, message } }; 23 + } 24 + 25 + // Standard JSON-RPC error codes 26 + export const PARSE_ERROR = -32700; 27 + export const INVALID_REQUEST = -32600; 28 + export const METHOD_NOT_FOUND = -32601; 29 + export const INVALID_PARAMS = -32602; 30 + 31 + export const PROTOCOL_VERSION = "2025-03-26"; 32 + 33 + export const SERVER_INFO = { 34 + name: "exosphere", 35 + version: "0.0.1", 36 + };
+98
packages/mcp/src/routes.ts
··· 1 + import { Hono } from "hono"; 2 + import { 3 + type JsonRpcRequest, 4 + type JsonRpcResponse, 5 + jsonRpcResult, 6 + jsonRpcError, 7 + METHOD_NOT_FOUND, 8 + INVALID_PARAMS, 9 + PROTOCOL_VERSION, 10 + SERVER_INFO, 11 + } from "./protocol.ts"; 12 + import { tools, type ApiFetch } from "./tools/index.ts"; 13 + 14 + async function handleMessage( 15 + msg: JsonRpcRequest, 16 + apiFetch: ApiFetch, 17 + ): Promise<JsonRpcResponse | null> { 18 + // Notifications (no id) don't produce responses 19 + if (msg.id === undefined || msg.id === null) { 20 + return null; 21 + } 22 + 23 + switch (msg.method) { 24 + case "initialize": 25 + return jsonRpcResult(msg.id, { 26 + protocolVersion: PROTOCOL_VERSION, 27 + capabilities: { tools: {} }, 28 + serverInfo: SERVER_INFO, 29 + }); 30 + 31 + case "tools/list": 32 + return jsonRpcResult(msg.id, { 33 + tools: tools.map((t) => ({ 34 + name: t.name, 35 + description: t.description, 36 + inputSchema: t.inputSchema, 37 + })), 38 + }); 39 + 40 + case "tools/call": { 41 + const name = msg.params?.name as string; 42 + const args = (msg.params?.arguments ?? {}) as Record<string, unknown>; 43 + const tool = tools.find((t) => t.name === name); 44 + if (!tool) { 45 + return jsonRpcError(msg.id, INVALID_PARAMS, `Unknown tool: ${name}`); 46 + } 47 + try { 48 + const result = await tool.handler(args, apiFetch); 49 + return jsonRpcResult(msg.id, result); 50 + } catch (err) { 51 + const message = err instanceof Error ? err.message : "Tool execution failed"; 52 + return jsonRpcResult(msg.id, { 53 + content: [{ type: "text", text: message }], 54 + isError: true, 55 + }); 56 + } 57 + } 58 + 59 + default: 60 + return jsonRpcError(msg.id, METHOD_NOT_FOUND, `Method not found: ${msg.method}`); 61 + } 62 + } 63 + 64 + /** 65 + * Create MCP Streamable HTTP routes (stateless mode). 66 + * @param apiFetch - function to make internal API calls (typically `(path) => app.request(path)`) 67 + */ 68 + export function createMcpRoutes(apiFetch: ApiFetch) { 69 + const mcp = new Hono(); 70 + 71 + mcp.post("/", async (c) => { 72 + const body = (await c.req.json()) as JsonRpcRequest | JsonRpcRequest[]; 73 + 74 + // Batch request 75 + if (Array.isArray(body)) { 76 + const responses: JsonRpcResponse[] = []; 77 + for (const msg of body) { 78 + const res = await handleMessage(msg, apiFetch); 79 + if (res) responses.push(res); 80 + } 81 + if (responses.length === 0) return c.body(null, 202); 82 + return c.json(responses); 83 + } 84 + 85 + // Single request 86 + const response = await handleMessage(body, apiFetch); 87 + if (!response) return c.body(null, 202); 88 + return c.json(response); 89 + }); 90 + 91 + // SSE endpoint — not supported in stateless mode 92 + mcp.get("/", (c) => c.json({ error: "SSE not supported in stateless mode" }, 405)); 93 + 94 + // Session termination — not applicable in stateless mode 95 + mcp.delete("/", (c) => c.json({ error: "Sessions not supported in stateless mode" }, 405)); 96 + 97 + return mcp; 98 + }
+149
packages/mcp/src/tools/feature-requests.ts
··· 1 + import type { McpTool, ApiFetch } from "./types.ts"; 2 + 3 + /** Resolve a feature request number to its internal ID via the API. */ 4 + async function resolveFeatureRequestId( 5 + number: number, 6 + apiFetch: ApiFetch, 7 + ): Promise<{ id: string } | { error: string }> { 8 + const res = await apiFetch(`/api/feature-requests/${number}`); 9 + if (!res.ok) return { error: "Feature request not found" }; 10 + const data = (await res.json()) as { featureRequest: { id: string } }; 11 + return { id: data.featureRequest.id }; 12 + } 13 + 14 + export const featureRequestTools: McpTool[] = [ 15 + { 16 + name: "list_feature_requests", 17 + description: 18 + "List feature requests with optional filtering by status and sorting. Returns title, description, category, status, vote count, and comment count for each.", 19 + inputSchema: { 20 + type: "object", 21 + properties: { 22 + status: { 23 + type: "string", 24 + description: 25 + "Comma-separated statuses to filter by: requested, approved, in-progress, done, not-planned, duplicate", 26 + }, 27 + sort: { 28 + type: "string", 29 + enum: ["date", "votes"], 30 + description: "Sort by date or votes (default: date)", 31 + }, 32 + order: { 33 + type: "string", 34 + enum: ["asc", "desc"], 35 + description: "Sort order (default: desc)", 36 + }, 37 + }, 38 + }, 39 + handler: async (args, apiFetch) => { 40 + const params = new URLSearchParams(); 41 + if (args.status) params.set("status", String(args.status)); 42 + if (args.sort) params.set("sort", String(args.sort)); 43 + if (args.order) params.set("order", String(args.order)); 44 + const qs = params.toString(); 45 + const res = await apiFetch(`/api/feature-requests${qs ? `?${qs}` : ""}`); 46 + const data = await res.json(); 47 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 48 + }, 49 + }, 50 + 51 + { 52 + name: "get_feature_request", 53 + description: 54 + "Get a single feature request by its number, including vote count, comment count, and duplicate info", 55 + inputSchema: { 56 + type: "object", 57 + properties: { 58 + number: { type: "integer", description: "The feature request number" }, 59 + }, 60 + required: ["number"], 61 + }, 62 + handler: async (args, apiFetch) => { 63 + const res = await apiFetch(`/api/feature-requests/${args.number}`); 64 + if (!res.ok) { 65 + return { content: [{ type: "text", text: "Feature request not found" }], isError: true }; 66 + } 67 + const data = await res.json(); 68 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 69 + }, 70 + }, 71 + 72 + { 73 + name: "search_feature_requests", 74 + description: 75 + "Search feature requests by title or by number. Use '#42' or just '42' to search by number, or any text to search by title.", 76 + inputSchema: { 77 + type: "object", 78 + properties: { 79 + query: { type: "string", description: "Search query (title text or #number)" }, 80 + }, 81 + required: ["query"], 82 + }, 83 + handler: async (args, apiFetch) => { 84 + const q = encodeURIComponent(String(args.query)); 85 + const res = await apiFetch(`/api/feature-requests/search?q=${q}`); 86 + const data = await res.json(); 87 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 88 + }, 89 + }, 90 + 91 + { 92 + name: "get_feature_request_comments", 93 + description: "Get comments on a feature request, identified by its number", 94 + inputSchema: { 95 + type: "object", 96 + properties: { 97 + number: { type: "integer", description: "The feature request number" }, 98 + sort: { 99 + type: "string", 100 + enum: ["date", "votes"], 101 + description: "Sort by date or votes (default: date)", 102 + }, 103 + order: { 104 + type: "string", 105 + enum: ["asc", "desc"], 106 + description: "Sort order (default: desc)", 107 + }, 108 + }, 109 + required: ["number"], 110 + }, 111 + handler: async (args, apiFetch) => { 112 + const resolved = await resolveFeatureRequestId(Number(args.number), apiFetch); 113 + if ("error" in resolved) { 114 + return { content: [{ type: "text", text: resolved.error }], isError: true }; 115 + } 116 + 117 + const params = new URLSearchParams(); 118 + if (args.sort) params.set("sort", String(args.sort)); 119 + if (args.order) params.set("order", String(args.order)); 120 + const qs = params.toString(); 121 + const res = await apiFetch(`/api/feature-requests/${resolved.id}/comments${qs ? `?${qs}` : ""}`); 122 + const data = await res.json(); 123 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 124 + }, 125 + }, 126 + 127 + { 128 + name: "get_feature_request_statuses", 129 + description: 130 + "Get the status change history of a feature request, identified by its number", 131 + inputSchema: { 132 + type: "object", 133 + properties: { 134 + number: { type: "integer", description: "The feature request number" }, 135 + }, 136 + required: ["number"], 137 + }, 138 + handler: async (args, apiFetch) => { 139 + const resolved = await resolveFeatureRequestId(Number(args.number), apiFetch); 140 + if ("error" in resolved) { 141 + return { content: [{ type: "text", text: resolved.error }], isError: true }; 142 + } 143 + 144 + const res = await apiFetch(`/api/feature-requests/${resolved.id}/statuses`); 145 + const data = await res.json(); 146 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 147 + }, 148 + }, 149 + ];
+7
packages/mcp/src/tools/index.ts
··· 1 + import type { McpTool } from "./types.ts"; 2 + import { sphereTools } from "./sphere.ts"; 3 + import { featureRequestTools } from "./feature-requests.ts"; 4 + 5 + export type { McpTool, ApiFetch } from "./types.ts"; 6 + 7 + export const tools: McpTool[] = [...sphereTools, ...featureRequestTools];
+18
packages/mcp/src/tools/sphere.ts
··· 1 + import type { McpTool } from "./types.ts"; 2 + 3 + export const sphereTools: McpTool[] = [ 4 + { 5 + name: "get_sphere", 6 + description: 7 + "Get information about the current sphere (community), including its name, description, visibility, enabled modules, and member count", 8 + inputSchema: { type: "object", properties: {} }, 9 + handler: async (_args, apiFetch) => { 10 + const res = await apiFetch("/api/spheres/current"); 11 + if (!res.ok) { 12 + return { content: [{ type: "text", text: "No sphere configured" }], isError: true }; 13 + } 14 + const data = await res.json(); 15 + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; 16 + }, 17 + }, 18 + ];
+25
packages/mcp/src/tools/types.ts
··· 1 + export interface JsonSchemaProperty { 2 + type: string; 3 + description?: string; 4 + enum?: string[]; 5 + } 6 + 7 + export interface ToolInputSchema { 8 + type: "object"; 9 + properties: Record<string, JsonSchemaProperty>; 10 + required?: string[]; 11 + } 12 + 13 + export interface ToolResult { 14 + content: Array<{ type: "text"; text: string }>; 15 + isError?: boolean; 16 + } 17 + 18 + export type ApiFetch = (path: string) => Response | Promise<Response>; 19 + 20 + export interface McpTool { 21 + name: string; 22 + description: string; 23 + inputSchema: ToolInputSchema; 24 + handler: (args: Record<string, unknown>, apiFetch: ApiFetch) => Promise<ToolResult>; 25 + }
+4
packages/mcp/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "include": ["src"] 4 + }
+7
packages/mcp/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + include: ["src/**/*.test.ts"], 6 + }, 7 + });
+8
railway.toml
··· 1 + [build] 2 + dockerfilePath = "Dockerfile" 3 + 4 + [deploy] 5 + healthcheckPath = "/api/health" 6 + healthcheckTimeout = 30 7 + restartPolicyType = "ON_FAILURE" 8 + restartPolicyMaxRetries = 5
+94
scripts/pds-account.ts
··· 1 + /** 2 + * Create a test account on the local PDS. 3 + * 4 + * The PDS must be running (docker compose -f docker-compose.dev.yml up -d). 5 + * 6 + * Usage: 7 + * bun run scripts/pds-account.ts <name> [password] 8 + * 9 + * Examples: 10 + * bun run scripts/pds-account.ts alice 11 + * bun run scripts/pds-account.ts bob secret123 12 + * 13 + * The handle is derived from PDS_HOSTNAME in pds/pds.env (e.g. alice.pds.dev). 14 + * Outputs the created DID which you can use to log in during local development. 15 + */ 16 + 17 + import { readFileSync } from "node:fs"; 18 + import { join } from "node:path"; 19 + 20 + const PDS_URL = process.env.PDS_URL ?? "http://localhost:3000"; 21 + const ENV_FILE = join(import.meta.dir, "..", "pds", "pds.env"); 22 + 23 + function readEnvVar(name: string): string { 24 + const env = readFileSync(ENV_FILE, "utf-8"); 25 + const match = env.match(new RegExp(`^${name}=(.+)$`, "m")); 26 + if (!match) throw new Error(`${name} not found in pds/pds.env`); 27 + return match[1].trim(); 28 + } 29 + 30 + const name = process.argv[2]; 31 + const password = process.argv[3] ?? "localdev"; 32 + 33 + if (!name) { 34 + console.error("Usage: bun run scripts/pds-account.ts <name> [password]"); 35 + console.error("Example: bun run scripts/pds-account.ts alice"); 36 + process.exit(1); 37 + } 38 + 39 + const pdsHostname = readEnvVar("PDS_HOSTNAME"); 40 + const handle = `${name}.${pdsHostname}`; 41 + 42 + const adminPassword = readEnvVar("PDS_ADMIN_PASSWORD"); 43 + 44 + // Use the PDS admin API to create an invite code first 45 + const inviteRes = await fetch(`${PDS_URL}/xrpc/com.atproto.server.createInviteCode`, { 46 + method: "POST", 47 + headers: { 48 + "Content-Type": "application/json", 49 + Authorization: `Basic ${btoa(`admin:${adminPassword}`)}`, 50 + }, 51 + body: JSON.stringify({ useCount: 1 }), 52 + }); 53 + 54 + if (!inviteRes.ok) { 55 + const text = await inviteRes.text(); 56 + console.error(`Failed to create invite code: ${inviteRes.status} ${text}`); 57 + process.exit(1); 58 + } 59 + 60 + const { code: inviteCode } = (await inviteRes.json()) as { code: string }; 61 + 62 + // Create the account 63 + const email = `${handle.replace(/\./g, "-")}@example.com`; 64 + 65 + const createRes = await fetch(`${PDS_URL}/xrpc/com.atproto.server.createAccount`, { 66 + method: "POST", 67 + headers: { "Content-Type": "application/json" }, 68 + body: JSON.stringify({ 69 + handle, 70 + email, 71 + password, 72 + inviteCode, 73 + }), 74 + }); 75 + 76 + if (!createRes.ok) { 77 + const text = await createRes.text(); 78 + console.error(`Failed to create account: ${createRes.status} ${text}`); 79 + process.exit(1); 80 + } 81 + 82 + const account = (await createRes.json()) as { 83 + did: string; 84 + handle: string; 85 + accessJwt: string; 86 + }; 87 + 88 + console.log("Account created successfully!"); 89 + console.log(` Handle: ${account.handle}`); 90 + console.log(` DID: ${account.did}`); 91 + console.log(` Email: ${email}`); 92 + console.log(` Password: ${password}`); 93 + console.log(""); 94 + console.log("Use the DID to log in via Exosphere (handle won't resolve via DNS on local PDS).");
+62
scripts/pds-init.ts
··· 1 + /** 2 + * Initialize the local PDS environment for development. 3 + * 4 + * Generates the pds/pds.env file with the necessary secrets and configuration. 5 + * Run this once before starting the PDS for the first time. 6 + * 7 + * Usage: bun run scripts/pds-init.ts 8 + */ 9 + 10 + import { existsSync, mkdirSync, writeFileSync } from "node:fs"; 11 + import { join } from "node:path"; 12 + import { randomBytes } from "node:crypto"; 13 + 14 + const ROOT = join(import.meta.dir, ".."); 15 + const PDS_DIR = join(ROOT, "pds"); 16 + const ENV_FILE = join(PDS_DIR, "pds.env"); 17 + 18 + if (existsSync(ENV_FILE)) { 19 + console.log("pds/pds.env already exists. Delete it to regenerate."); 20 + process.exit(0); 21 + } 22 + 23 + mkdirSync(PDS_DIR, { recursive: true }); 24 + 25 + const jwtSecret = randomBytes(32).toString("hex"); 26 + const adminPassword = "localdev"; 27 + const plcRotationKey = randomBytes(32).toString("hex"); 28 + 29 + // AT Protocol disallows these TLDs for handles: 30 + // .local, .arpa, .invalid, .localhost, .internal, .example, .alt, .onion 31 + // We use a real-looking domain so handle validation passes. 32 + // Handles will be <name>.pds.dev — they won't resolve via DNS, but that's 33 + // fine for local dev (use the DID to log in instead). 34 + const pdsHostname = "pds.dev"; 35 + 36 + const env = `# Generated by scripts/pds-init.ts — do not commit 37 + PDS_HOSTNAME=${pdsHostname} 38 + PDS_PORT=3000 39 + PDS_JWT_SECRET=${jwtSecret} 40 + PDS_ADMIN_PASSWORD=${adminPassword} 41 + PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${plcRotationKey} 42 + PDS_DATA_DIRECTORY=/pds/data 43 + PDS_BLOBSTORE_DISK_LOCATION=/pds/data/blocks 44 + PDS_DID_PLC_URL=https://plc.directory 45 + PDS_DEV_MODE=1 46 + PDS_LOG_ENABLED=true 47 + 48 + # AppView and moderation are not needed for local dev 49 + # but the PDS may require them to start 50 + PDS_BSKY_APP_VIEW_URL=https://api.bsky.app 51 + PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app 52 + PDS_REPORT_SERVICE_URL=https://mod.bsky.app 53 + PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac 54 + PDS_CRAWLERS= 55 + `; 56 + 57 + writeFileSync(ENV_FILE, env); 58 + console.log("Created pds/pds.env"); 59 + console.log(` Admin password: ${adminPassword}`); 60 + console.log(""); 61 + console.log("Start the PDS with:"); 62 + console.log(" docker compose -f docker-compose.dev.yml up -d");
+26
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "strict": true, 4 + "target": "ESNext", 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "jsx": "react-jsx", 8 + "jsxImportSource": "preact", 9 + "esModuleInterop": true, 10 + "skipLibCheck": true, 11 + "allowImportingTsExtensions": true, 12 + "noEmit": true, 13 + "forceConsistentCasingInFileNames": true, 14 + "paths": { 15 + "@exosphere/core/*": ["./packages/core/src/*"], 16 + "@exosphere/client": ["./packages/client/src/index.ts"], 17 + "@exosphere/client/*": ["./packages/client/src/*"], 18 + "@exosphere/feeds": ["./packages/feeds/src/index.ts"], 19 + "@exosphere/feeds/*": ["./packages/feeds/src/*"], 20 + "@exosphere/feature-requests": ["./packages/feature-requests/src/index.ts"], 21 + "@exosphere/feature-requests/*": ["./packages/feature-requests/src/*"], 22 + "@exosphere/mcp": ["./packages/mcp/src/index.ts"] 23 + } 24 + }, 25 + "exclude": ["node_modules", "dist"] 26 + }
+7
vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + exclude: ["**/e2e/**", "**/node_modules/**"], 6 + }, 7 + });
+1
vitest.workspace.ts
··· 1 + export default ["packages/core", "packages/feature-requests"];