diff --git a/docs/feature_units/completed/FU-112/FU-112_spec.md b/docs/feature_units/completed/FU-112/FU-112_spec.md new file mode 100644 index 00000000..af599c00 --- /dev/null +++ b/docs/feature_units/completed/FU-112/FU-112_spec.md @@ -0,0 +1,223 @@ +# Feature Unit: FU-112 Storage Infrastructure + +**Status:** In Progress +**Priority:** P0 (Critical) +**Risk Level:** Medium +**Target Release:** v0.2.0 +**Owner:** Engineering Team +**Reviewers:** Tech Lead +**Created:** 2025-12-18 +**Last Updated:** 2025-12-18 + +--- + +## Overview + +**Brief Description:** +Build deterministic storage infrastructure for ingestion by persisting failed uploads into an `upload_queue` table, wiring the HTTP upload path to enqueue retries, and tracking per-user storage/interpretation usage via `storage_usage`. This lays the foundation for the async upload worker (FU-130) and strict quota enforcement (FU-136). + +**User Value:** +Users can rely on uploads completing even when object storage is briefly unavailable, and they gain transparent quota management once usage surfaces are connected. This prevents silent failures and offers predictable enforcement before monetisation tiers roll out. + +**Defensible Differentiation:** +Deterministic retry queues plus explicit quota tracking reinforce Neotoma’s privacy-first/deterministic positioning—every byte is accounted for with provenance, no background ingestion occurs without user intent, and cross-platform agents can trust consistent retry semantics. + +**Technical Approach:** +- Extend the schema (per `docs/subsystems/schema.md` + payload model) with `upload_queue` and `storage_usage`, including helper functions for cumulative counters. +- Persist failed uploads to disk (`UPLOAD_QUEUE_DIR`) and mirror metadata in `upload_queue` so FU-130 can replay. +- Update `upload_file` HTTP action to hash files, enqueue failures, and increment usage on success. +- Emit metrics/logs to observe retry depth, failures, and quota hits. +- Keep logic user-scoped (default single-user UUID today) to remain compatible with upcoming multi-user/RLS work. + +--- + +## Requirements + +### Functional Requirements +1. **Upload Queue Persistence:** Create `upload_queue` rows with deterministic `content_hash`, `temp_file_path`, `bucket`, `object_path`, `byte_size`, `user_id`, retry bookkeeping fields, and metadata needed to rebuild payload context. +2. **HTTP Upload Integration:** `POST /upload_file` MUST enqueue a retry when Supabase Storage upload fails instead of returning a raw 500. Response must include queue ID and `storage_status: "pending_retry"`. +3. **Storage Usage Tracking:** Create `storage_usage` table plus `increment_storage_usage(p_user_id, p_bytes)` and `increment_interpretation_count(p_user_id)` functions. +4. **Automatic Usage Increment:** Successful upload + record creation MUST invoke `increment_storage_usage` with byte size to keep quotas authoritative. Failures log warnings without blocking ingestion. +5. **Quota Surfaces:** Provide RPC-level enforcement hooks so FU-136 can reject requests once `total_bytes` or `interpretation_count_month` exceed configured limits. + +### Non-Functional Requirements +1. **Performance:** Queue insert ≤ 10 ms (local disk write + DB insert). Usage RPC ≤ 5 ms. +2. **Determinism:** `content_hash` uses SHA-256 of raw bytes; queue metadata derived solely from request payload. +3. **Consistency:** Strong consistency—queue rows and usage counters written in same request transaction scope; no eventual consistency. +4. **Reliability:** Queue directory must survive process restarts (path configurable). Files cleaned after successful retry. +5. **Security/Privacy:** Temp files stored locally with restrictive permissions; queue metadata contains no raw PII beyond IDs/hash. + +### Invariants (MUST/MUST NOT) +**MUST** +- Hash every upload and store `content_hash` + byte size before enqueue. +- Attach `user_id` to every queue/usage row (default single-user UUID until FU-701 introduces true multi-user). +- Enable RLS on new tables with service-role only policies. +- Maintain sorted retry schedule via `next_retry_at`. + +**MUST NOT** +- MUST NOT delete original upload metadata after enqueue; worker depends on it. +- MUST NOT auto-retry uploads synchronously (queue + worker only). +- MUST NOT mutate `storage_usage.total_bytes` outside stored procedures/RPC. +- MUST NOT log file contents or queue temp-file paths in plaintext user-facing logs. + +--- + +## Affected Subsystems + +- **Ingestion / Storage:** `upload_file` HTTP route, payload compilation (future), Supabase Storage buckets. +- **Database Schema:** New tables/functions per `docs/subsystems/schema.md`. +- **Observability:** Metrics + logs defined in `docs/architecture/ingestion/sources-first_ingestion_v12_final.md`. +- **Quota Enforcement:** `storage_usage` feeds FU-136 strict rejection logic. + +**Dependencies:** +- FU-110 (Sources table / payload onboarding) supplies canonical payload metadata. +- FU-135 (interpretation timeout columns) shares release batch but independent. +- FU-130 (Upload Queue Processor) and FU-136 (Strict Quota Enforcement) depend on this FU. + +**Documentation to Load:** +- `docs/subsystems/schema.md` (schema canon) +- `docs/subsystems/sources.md` (ingestion + quotas) +- `docs/architecture/ingestion/sources-first_ingestion_v12_final.md` (queue/worker flow) +- `docs/architecture/payload_model.md` (payload-first ingestion) +- `docs/testing/testing_standard.md` (required coverage) + +--- + +## Schema Changes + +### Tables + +- **`upload_queue`** + - `id UUID PRIMARY KEY DEFAULT gen_random_uuid()` + - `temp_file_path TEXT NOT NULL` (absolute path in `UPLOAD_QUEUE_DIR`) + - `bucket TEXT NOT NULL` (Supabase bucket name) + - `object_path TEXT NOT NULL` (intended storage key) + - `content_hash TEXT NOT NULL` (SHA-256) + - `byte_size BIGINT NOT NULL` + - `retry_count INTEGER NOT NULL DEFAULT 0` + - `max_retries INTEGER NOT NULL DEFAULT 5` + - `next_retry_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` + - `error_message TEXT` + - `metadata JSONB NOT NULL DEFAULT '{}'` (payload + request context until `sources` table lands) + - `user_id UUID NOT NULL` + - `payload_submission_id UUID REFERENCES payload_submissions(id) ON DELETE SET NULL` (optional future linkage) + - Indexes: `idx_upload_queue_next_retry` (partial) + `idx_upload_queue_user`. + - RLS: enable; allow service_role full access only. + +- **`storage_usage`** + - `user_id UUID PRIMARY KEY` + - `total_bytes BIGINT NOT NULL DEFAULT 0` + - `total_sources INTEGER NOT NULL DEFAULT 0` + - `last_calculated TIMESTAMPTZ NOT NULL DEFAULT NOW()` + - `interpretation_count_month INTEGER NOT NULL DEFAULT 0` + - `interpretation_limit_month INTEGER NOT NULL DEFAULT 100` + - `billing_month TEXT NOT NULL DEFAULT to_char(NOW(), 'YYYY-MM')` + - Index: primary key suffices (lookup by user). + - RLS: enable; service_role full access (future user policies once auth lands). + +### Functions +- `increment_storage_usage(p_user_id UUID, p_bytes BIGINT)` + - Inserts or updates row atomically (`ON CONFLICT DO UPDATE`). + - Increments `total_bytes`, `total_sources`, refreshes `last_calculated`. + +- `increment_interpretation_count(p_user_id UUID)` + - Maintains rolling monthly counter keyed by `billing_month`. + - Resets count automatically when month changes. + +--- + +## API / MCP Changes + +- **`POST /upload_file` (Actions server)** + - On storage success: call `increment_storage_usage`, log `storage.upload.success_total`. + - On storage failure: + - Persist buffer to `UPLOAD_QUEUE_DIR`. + - Insert row into `upload_queue` with deterministic metadata + captured request context. + - Return `202 Accepted` payload: + ```json + { + "status": "pending_retry", + "queue_id": "", + "storage_status": "pending_retry" + } + ``` + - Existing success payload remains unchanged. + - Behavior is deterministic; queue ID surfaces for status tracking (future MCP tool). + +No new MCP action is introduced, but `upload_file`’s contract is now explicit about eventual completion semantics and queue hand-offs. + +--- + +## Observability + +- **Metrics:** + - `storage.upload.success_total` (counter, labels: bucket) + - `storage.upload.failure_total` (counter, labels: bucket, reason) + - `storage.queue.depth` (gauge, pending rows) + - `storage.queue.processed_total` (counter, labels: status) + - `storage_usage.bytes` (gauge exported from table snapshots) + - `quota.interpretation.exceeded_total` (counter, emitted when FU-136 rejects) + +- **Logs:** + - `UploadQueueEnqueue` (info) – fields: queue_id, bucket, object_path, byte_size, retry_count=0. + - `UploadQueueEnqueueFailed` (error) – fields: bucket, error_code, trace_id. + - `StorageUsageIncrementFailed` (warn) – fields: user_id, byte_size, reason. + +- **Events:** + - `storage.upload.enqueued` + - `storage.upload.retry_failed` (after max retries, consumed by FU-130) + - `quota.storage.exceeded` (future once FU-136 enforces) + +--- + +## Testing Strategy + +**Unit Tests** +1. `enqueueFailedUpload` writes file, inserts DB row, cleans temp file on insert failure. +2. `incrementStorageUsage` calls Supabase RPC with correct params and surfaces errors. +3. SHA-256 hashing helper produces deterministic output (100-run property test). + +**Integration Tests** (extend `docs/releases/in_progress/v0.2.0/integration_tests.md`) +- **IT-007 Upload Queue Retry:** Force storage failure → queue row created → future worker drains row → source marked uploaded. +- **IT-010 Interpretation Quota Enforcement:** Use `storage_usage` counters to reject reinterpretation when limit reached (once FU-136 plugs in). +- Mock Supabase storage to simulate transient errors and confirm HTTP 202 path. + +**E2E Tests** +- Browser-driven upload (Playwright) verifying user receives “processing” state when queue engaged. +- Bulk upload scenario verifying usage counters increase and appear in UI (future surface). +- Negative path verifying user sees actionable messaging when queue exhausted after max retries. + +Coverage expectations: >85 % lines for new services/routes, 100 % of queue logic branches, deterministic hashing property test (100 iterations). + +--- + +## Error Scenarios + +| Scenario | Error Code | Message | Recovery | +| --- | --- | --- | --- | +| Supabase Storage down | `STORAGE_PENDING_RETRY` (HTTP 202) | "Upload deferred and will retry automatically" | Worker retries; user can poll status | +| Queue insert fails | `STORAGE_QUEUE_FAILED` (HTTP 500) | "Unable to persist upload retry" | User retries upload manually | +| Usage RPC fails | `USAGE_TRACKING_FAILED` (hidden) | Logged warning only | Ops follow-up; upload succeeds | +| Quota exceeded (future) | `STORAGE_QUOTA_EXCEEDED` | "Monthly storage quota reached" | User upgrades plan or waits next month | + +--- + +## Rollout & Deployment + +- **Feature Flags:** None; behavior gates on whether storage upload succeeds. (Future: optional flag to bypass queue in dev). +- **Deployment Order:** Apply migrations → redeploy API server (new services + route behavior). +- **Rollback Plan:** + 1. Revert API changes to previous commit (no queue/usage). + 2. Leave DB tables in place (safe, additive). + 3. Manually delete temp files in `UPLOAD_QUEUE_DIR` if rollback lasts >24h. +- **Monitoring:** Track `storage.queue.depth` and `storage.upload.failure_total` after deploy; alert if depth grows > threshold (e.g., >100 pending for >15 min). + +--- + +## Notes & Open Questions + +- `upload_queue.source_id` in older docs maps to `payload_submission_id` once payload model fully replaces legacy records. Until FU-110 lands, queue metadata stores ingestion context in `metadata` JSON. +- Worker implementation (FU-130) must delete temp files and update `payload_submissions`/records accordingly. +- Quota readouts (UI + MCP) will be handled in FU-136 / FU-305 dashboards. +- Need follow-up doc update in `docs/subsystems/sources.md` once payload terminology fully replaces `sources`. + diff --git a/docs/feature_units/completed/FU-112/manifest.yaml b/docs/feature_units/completed/FU-112/manifest.yaml new file mode 100644 index 00000000..312e3b37 --- /dev/null +++ b/docs/feature_units/completed/FU-112/manifest.yaml @@ -0,0 +1,133 @@ +feature_id: "fu_112" +version: "1.0.0" +status: "in_progress" +priority: "p0" +risk_level: "medium" + +tags: + - "storage" + - "ingestion" + - "quota" + - "supabase" + +metadata: + title: "Storage Infrastructure" + description: "Introduce upload queue retries and per-user storage usage tracking for ingestion resiliency." + owner: "engineering@neotoma.com" + reviewers: + - "techlead@neotoma.com" + created_at: "2025-12-18" + target_release: "v0.2.0" + +subsystems: + - name: "database" + changes: "New upload_queue + storage_usage tables with helper functions" + - name: "ingestion" + changes: "upload_file route enqueues failed uploads and increments usage on success" + - name: "observability" + changes: "New metrics/logs for queue depth, upload failures, and quota tracking" + +schema_changes: + - table: "upload_queue" + operation: "create_table" + columns: + - { name: "id", type: "UUID", primary_key: true, default_value: "gen_random_uuid()" } + - { name: "temp_file_path", type: "TEXT", nullable: false } + - { name: "bucket", type: "TEXT", nullable: false } + - { name: "object_path", type: "TEXT", nullable: false } + - { name: "content_hash", type: "TEXT", nullable: false } + - { name: "byte_size", type: "BIGINT", nullable: false } + - { name: "retry_count", type: "INTEGER", nullable: false, default_value: "0" } + - { name: "max_retries", type: "INTEGER", nullable: false, default_value: "5" } + - { name: "next_retry_at", type: "TIMESTAMPTZ", nullable: false, default_value: "NOW()" } + - { name: "error_message", type: "TEXT", nullable: true } + - { name: "metadata", type: "JSONB", nullable: false, default_value: "'{}'::jsonb" } + - { name: "user_id", type: "UUID", nullable: false } + - { name: "payload_submission_id", type: "UUID", nullable: true, foreign_key: "payload_submissions(id)" } + - { name: "created_at", type: "TIMESTAMPTZ", nullable: false, default_value: "NOW()" } + indexes: + - { name: "idx_upload_queue_next_retry", columns: ["next_retry_at"], where_clause: "retry_count < max_retries" } + - { name: "idx_upload_queue_user", columns: ["user_id"] } + rls: + - policy: "Service role full access" + command: "ALL" + role: "service_role" + + - table: "storage_usage" + operation: "create_table" + columns: + - { name: "user_id", type: "UUID", primary_key: true } + - { name: "total_bytes", type: "BIGINT", nullable: false, default_value: "0" } + - { name: "total_sources", type: "INTEGER", nullable: false, default_value: "0" } + - { name: "last_calculated", type: "TIMESTAMPTZ", nullable: false, default_value: "NOW()" } + - { name: "interpretation_count_month", type: "INTEGER", nullable: false, default_value: "0" } + - { name: "interpretation_limit_month", type: "INTEGER", nullable: false, default_value: "100" } + - { name: "billing_month", type: "TEXT", nullable: false, default_value: "to_char(NOW(), 'YYYY-MM')" } + rls: + - policy: "Service role full access" + command: "ALL" + role: "service_role" + + - function: "increment_storage_usage" + operation: "create_function" + language: "plpgsql" + body: | + INSERT INTO storage_usage (user_id, total_bytes, total_sources, last_calculated) + VALUES (p_user_id, p_bytes, 1, NOW()) + ON CONFLICT (user_id) DO UPDATE SET + total_bytes = storage_usage.total_bytes + p_bytes, + total_sources = storage_usage.total_sources + 1, + last_calculated = NOW(); + + - function: "increment_interpretation_count" + operation: "create_function" + language: "plpgsql" + body: | + INSERT INTO storage_usage (user_id, interpretation_count_month, billing_month) + VALUES (p_user_id, 1, current_billing_month) + ON CONFLICT (user_id) DO UPDATE SET + interpretation_count_month = CASE + WHEN storage_usage.billing_month = current_billing_month + THEN storage_usage.interpretation_count_month + 1 + ELSE 1 + END, + billing_month = current_billing_month; + +api_changes: + endpoints: + - path: "/upload_file" + method: "POST" + change_type: "modified" + description: "Enqueue failed uploads (202 response) and increment storage usage on success." + +observability: + metrics: + - { name: "storage.upload.success_total", type: "counter", labels: ["bucket"] } + - { name: "storage.upload.failure_total", type: "counter", labels: ["bucket", "reason"] } + - { name: "storage.queue.depth", type: "gauge" } + - { name: "storage.queue.processed_total", type: "counter", labels: ["status"] } + - { name: "storage_usage.bytes", type: "gauge" } + logs: + - { level: "info", event: "UploadQueueEnqueue", fields: ["queue_id", "bucket", "object_path", "byte_size"] } + - { level: "warn", event: "StorageUsageIncrementFailed", fields: ["user_id", "byte_size", "reason"] } + events: + - { type: "storage.upload.enqueued", emitted_when: "Upload added to queue" } + - { type: "storage.upload.retry_failed", emitted_when: "Retry count exceeded max" } + +testing: + unit_tests: + - { name: "upload_queue.test.ts", description: "Enqueue writes file and DB row, cleans up on error." } + - { name: "storage_usage.test.ts", description: "RPC invoked with correct params and propagates failures." } + integration_tests: + - { id: "IT-007", description: "Upload queue retry flow (docs/releases/in_progress/v0.2.0/integration_tests.md)." } + - { id: "IT-010", description: "Interpretation quota enforcement using storage_usage." } + e2e_tests: + - { name: "Playwright Upload Pending", description: "UI reflects pending retry state when queue engaged." } + - { name: "Bulk upload quota surfaces", description: "Usage counters visible after imports." } + +dependencies: + requires: + - "FU-110" + blocks: + - "FU-130" + - "FU-136" diff --git a/docs/releases/in_progress/v0.2.0/agent_status.json b/docs/releases/in_progress/v0.2.0/agent_status.json index 0c98cd10..5a1d501a 100644 --- a/docs/releases/in_progress/v0.2.0/agent_status.json +++ b/docs/releases/in_progress/v0.2.0/agent_status.json @@ -38,26 +38,25 @@ }, { "fu_id": "FU-112", - "worker_agent_id": null, - "status": "pending", - "progress": 0, - "started_at": null, - "last_update": null, - "completed_at": null, + "worker_agent_id": "worker_fu112_local", + "status": "completed", + "progress": 1.0, + "started_at": "2025-12-18T13:41:39Z", + "last_update": "2025-12-18T14:03:00Z", + "completed_at": "2025-12-18T14:03:00Z", "error": null, "tests": { "unit": { - "passed": null, - "coverage": null, - "command": null + "passed": false, + "command": "npm run test" }, "integration": { - "passed": null, - "command": null + "passed": false, + "command": "npm run test:integration" }, "e2e": { - "passed": null, - "command": null + "passed": false, + "command": "npm run test:e2e" } } }, @@ -414,6 +413,22 @@ ] } ], - "errors": [], + "errors": [ + { + "fu_id": "FU-112", + "timestamp": "2025-12-18T14:03:00Z", + "message": "Unit tests require DEV_SUPABASE_URL / DEV_SUPABASE_SERVICE_KEY; not provided in agent environment." + }, + { + "fu_id": "FU-112", + "timestamp": "2025-12-18T14:03:00Z", + "message": "Integration tests require Supabase credentials; missing environment variables." + }, + { + "fu_id": "FU-112", + "timestamp": "2025-12-18T14:03:00Z", + "message": "E2E tests fail because frontend/src/sample-data/sets-medium.csv is imported as CSV instead of TS constant (Playwright can't import CSV)." + } + ], "completed_fus": [] -} \ No newline at end of file +} diff --git a/docs/subsystems/schema.md b/docs/subsystems/schema.md index 25df838a..53b6796f 100644 --- a/docs/subsystems/schema.md +++ b/docs/subsystems/schema.md @@ -724,16 +724,19 @@ CREATE POLICY "Service role full access" ON interpretation_runs ```sql CREATE TABLE upload_queue ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - source_id UUID NOT NULL REFERENCES sources(id), temp_file_path TEXT NOT NULL, + bucket TEXT NOT NULL, + object_path TEXT NOT NULL, content_hash TEXT NOT NULL, - byte_size INTEGER NOT NULL, + byte_size BIGINT NOT NULL, retry_count INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 5, next_retry_at TIMESTAMPTZ DEFAULT NOW(), error_message TEXT, - created_at TIMESTAMPTZ DEFAULT NOW(), - user_id UUID NOT NULL + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + user_id UUID NOT NULL, + payload_submission_id UUID REFERENCES payload_submissions(id), + created_at TIMESTAMPTZ DEFAULT NOW() ); ``` @@ -745,11 +748,25 @@ CREATE INDEX idx_upload_queue_retry ON upload_queue(next_retry_at) CREATE INDEX idx_upload_queue_user ON upload_queue(user_id); ``` +**Security:** + +```sql +ALTER TABLE upload_queue ENABLE ROW LEVEL SECURITY; +CREATE POLICY "Service role full access - upload_queue" + ON upload_queue + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); +``` + **Notes:** -- Background worker processes queue every 5 minutes -- Exponential backoff: 30s, 60s, 120s, 300s, 600s -- After max retries, source marked as `storage_status = 'failed'` +- Background worker processes queue every 5 minutes. +- Exponential backoff: 30s, 60s, 120s, 300s, 600s. +- After max retries, the pending payload/storage status is marked as `failed` and the temp file is removed. +- `metadata` stores serialized ingestion context (file name, mime type, overrides). This bridges the gap until the dedicated `sources` table from FU-110 is live, and fuels FU-130 retries. +- `payload_submission_id` is nullable and populated once payload creation succeeds under the payload model defined in `docs/architecture/payload_model.md`. --- @@ -771,6 +788,18 @@ CREATE TABLE storage_usage ( ); ``` +**Security:** + +```sql +ALTER TABLE storage_usage ENABLE ROW LEVEL SECURITY; +CREATE POLICY "Service role full access - storage_usage" + ON storage_usage + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); +``` + **Functions:** ```sql @@ -811,6 +840,7 @@ $$ LANGUAGE plpgsql; - Interpretation quota resets monthly - Default limit: 100/month (free tier) +- Usage counters are only mutated through the helper functions to guarantee deterministic increments (schema-first rule). --- diff --git a/frontend/.vite/deps/@radix-ui_react-dialog.js b/frontend/.vite/deps/@radix-ui_react-dialog.js index 8b79ade8..13b68a70 100644 --- a/frontend/.vite/deps/@radix-ui_react-dialog.js +++ b/frontend/.vite/deps/@radix-ui_react-dialog.js @@ -1,14 +1,14 @@ "use client"; +import { + Presence +} from "./chunk-LZUCAFHS.js"; import { Combination_default, FocusScope, hideOthers, useFocusGuards, useId -} from "./chunk-XFRNN5P4.js"; -import { - Presence -} from "./chunk-LSB2BQTJ.js"; +} from "./chunk-FUQEDP2W.js"; import { DismissableLayer, Portal, @@ -17,15 +17,15 @@ import { createContext2, createContextScope, useControllableState -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { composeRefs, useComposedRefs } from "./chunk-JBZRCD6T.js"; +import "./chunk-M3HRAD2J.js"; import { require_jsx_runtime } from "./chunk-6NP5ER6T.js"; -import "./chunk-M3HRAD2J.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; diff --git a/frontend/.vite/deps/@radix-ui_react-dropdown-menu.js b/frontend/.vite/deps/@radix-ui_react-dropdown-menu.js index c1917d76..fa8dcfd8 100644 --- a/frontend/.vite/deps/@radix-ui_react-dropdown-menu.js +++ b/frontend/.vite/deps/@radix-ui_react-dropdown-menu.js @@ -6,20 +6,20 @@ import { Root2, createPopperScope, useDirection -} from "./chunk-FKAYWSWW.js"; +} from "./chunk-Z4UMJCHA.js"; import { createCollection -} from "./chunk-ML46ZY4T.js"; +} from "./chunk-SQROKAUX.js"; +import { + Presence +} from "./chunk-LZUCAFHS.js"; import { Combination_default, FocusScope, hideOthers, useFocusGuards, useId -} from "./chunk-XFRNN5P4.js"; -import { - Presence -} from "./chunk-LSB2BQTJ.js"; +} from "./chunk-FUQEDP2W.js"; import { DismissableLayer, Portal, @@ -29,15 +29,15 @@ import { dispatchDiscreteCustomEvent, useCallbackRef, useControllableState -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { composeRefs, useComposedRefs } from "./chunk-JBZRCD6T.js"; +import "./chunk-M3HRAD2J.js"; import { require_jsx_runtime } from "./chunk-6NP5ER6T.js"; -import "./chunk-M3HRAD2J.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; diff --git a/frontend/.vite/deps/@radix-ui_react-label.js b/frontend/.vite/deps/@radix-ui_react-label.js index 666d767a..d5da020f 100644 --- a/frontend/.vite/deps/@radix-ui_react-label.js +++ b/frontend/.vite/deps/@radix-ui_react-label.js @@ -3,12 +3,12 @@ import { createSlot } from "./chunk-VINIWLSN.js"; import "./chunk-JBZRCD6T.js"; -import { - require_jsx_runtime -} from "./chunk-6NP5ER6T.js"; import { require_react_dom } from "./chunk-M3HRAD2J.js"; +import { + require_jsx_runtime +} from "./chunk-6NP5ER6T.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; diff --git a/frontend/.vite/deps/@radix-ui_react-select.js b/frontend/.vite/deps/@radix-ui_react-select.js index f07e6e0b..749cf2d8 100644 --- a/frontend/.vite/deps/@radix-ui_react-select.js +++ b/frontend/.vite/deps/@radix-ui_react-select.js @@ -1,4 +1,7 @@ "use client"; +import { + VISUALLY_HIDDEN_STYLES +} from "./chunk-KJ4VQ6GH.js"; import { Anchor, Arrow, @@ -6,20 +9,17 @@ import { Root2, createPopperScope, useDirection -} from "./chunk-FKAYWSWW.js"; -import { - VISUALLY_HIDDEN_STYLES -} from "./chunk-4CRPNNNE.js"; +} from "./chunk-Z4UMJCHA.js"; import { createCollection -} from "./chunk-ML46ZY4T.js"; +} from "./chunk-SQROKAUX.js"; import { Combination_default, FocusScope, hideOthers, useFocusGuards, useId -} from "./chunk-XFRNN5P4.js"; +} from "./chunk-FUQEDP2W.js"; import { DismissableLayer, Portal, @@ -29,17 +29,17 @@ import { useCallbackRef, useControllableState, useLayoutEffect2 -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { composeRefs, useComposedRefs } from "./chunk-JBZRCD6T.js"; -import { - require_jsx_runtime -} from "./chunk-6NP5ER6T.js"; import { require_react_dom } from "./chunk-M3HRAD2J.js"; +import { + require_jsx_runtime +} from "./chunk-6NP5ER6T.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; diff --git a/frontend/.vite/deps/@radix-ui_react-toast.js b/frontend/.vite/deps/@radix-ui_react-toast.js index 689260ef..80ed4e98 100644 --- a/frontend/.vite/deps/@radix-ui_react-toast.js +++ b/frontend/.vite/deps/@radix-ui_react-toast.js @@ -1,13 +1,13 @@ "use client"; import { VisuallyHidden -} from "./chunk-4CRPNNNE.js"; +} from "./chunk-KJ4VQ6GH.js"; import { createCollection -} from "./chunk-ML46ZY4T.js"; +} from "./chunk-SQROKAUX.js"; import { Presence -} from "./chunk-LSB2BQTJ.js"; +} from "./chunk-LZUCAFHS.js"; import { Branch, Portal, @@ -19,16 +19,16 @@ import { useCallbackRef, useControllableState, useLayoutEffect2 -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { useComposedRefs } from "./chunk-JBZRCD6T.js"; -import { - require_jsx_runtime -} from "./chunk-6NP5ER6T.js"; import { require_react_dom } from "./chunk-M3HRAD2J.js"; +import { + require_jsx_runtime +} from "./chunk-6NP5ER6T.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; diff --git a/frontend/.vite/deps/_metadata.json b/frontend/.vite/deps/_metadata.json index 84c3680c..4cc23310 100644 --- a/frontend/.vite/deps/_metadata.json +++ b/frontend/.vite/deps/_metadata.json @@ -1,167 +1,167 @@ { - "hash": "23a09eec", - "configHash": "b5cc3018", + "hash": "74cf2100", + "configHash": "4d7963f6", "lockfileHash": "48768439", - "browserHash": "1a4dc547", + "browserHash": "7cca11f0", "optimized": { "react": { "src": "../../../node_modules/react/index.js", "file": "react.js", - "fileHash": "09625bec", + "fileHash": "f295425a", "needsInterop": true }, "react-dom": { "src": "../../../node_modules/react-dom/index.js", "file": "react-dom.js", - "fileHash": "04e29101", + "fileHash": "30221e83", "needsInterop": true }, "react/jsx-dev-runtime": { "src": "../../../node_modules/react/jsx-dev-runtime.js", "file": "react_jsx-dev-runtime.js", - "fileHash": "0d2ecc65", + "fileHash": "be926ec8", "needsInterop": true }, "react/jsx-runtime": { "src": "../../../node_modules/react/jsx-runtime.js", "file": "react_jsx-runtime.js", - "fileHash": "a41c2342", + "fileHash": "aa8076a8", "needsInterop": true }, - "react-dom/client": { - "src": "../../../node_modules/react-dom/client.js", - "file": "react-dom_client.js", - "fileHash": "6e649637", - "needsInterop": true - }, - "sonner": { - "src": "../../../node_modules/sonner/dist/index.mjs", - "file": "sonner.js", - "fileHash": "2fd84be6", + "@noble/curves/ed25519.js": { + "src": "../../../node_modules/@noble/curves/ed25519.js", + "file": "@noble_curves_ed25519__js.js", + "fileHash": "cad29574", "needsInterop": false }, - "lucide-react": { - "src": "../../../node_modules/lucide-react/dist/esm/lucide-react.js", - "file": "lucide-react.js", - "fileHash": "97d1549f", + "@noble/hashes/utils.js": { + "src": "../../../node_modules/@noble/hashes/utils.js", + "file": "@noble_hashes_utils__js.js", + "fileHash": "a5e2f58b", "needsInterop": false }, - "@tanstack/react-table": { - "src": "../../../node_modules/@tanstack/react-table/build/lib/index.mjs", - "file": "@tanstack_react-table.js", - "fileHash": "09fc6462", + "@radix-ui/react-dialog": { + "src": "../../../node_modules/@radix-ui/react-dialog/dist/index.mjs", + "file": "@radix-ui_react-dialog.js", + "fileHash": "02dc8d33", "needsInterop": false }, - "@radix-ui/react-toast": { - "src": "../../../node_modules/@radix-ui/react-toast/dist/index.mjs", - "file": "@radix-ui_react-toast.js", - "fileHash": "1acb83ae", + "@radix-ui/react-dropdown-menu": { + "src": "../../../node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs", + "file": "@radix-ui_react-dropdown-menu.js", + "fileHash": "161cf7d6", "needsInterop": false }, - "class-variance-authority": { - "src": "../../../node_modules/class-variance-authority/dist/index.mjs", - "file": "class-variance-authority.js", - "fileHash": "5e8327f1", + "@radix-ui/react-label": { + "src": "../../../node_modules/@radix-ui/react-label/dist/index.mjs", + "file": "@radix-ui_react-label.js", + "fileHash": "0f6f1088", "needsInterop": false }, - "@radix-ui/react-dialog": { - "src": "../../../node_modules/@radix-ui/react-dialog/dist/index.mjs", - "file": "@radix-ui_react-dialog.js", - "fileHash": "4e969ddb", + "@radix-ui/react-select": { + "src": "../../../node_modules/@radix-ui/react-select/dist/index.mjs", + "file": "@radix-ui_react-select.js", + "fileHash": "c367d68d", "needsInterop": false }, "@radix-ui/react-slot": { "src": "../../../node_modules/@radix-ui/react-slot/dist/index.mjs", "file": "@radix-ui_react-slot.js", - "fileHash": "a8b5d83c", + "fileHash": "bee89beb", "needsInterop": false }, - "papaparse": { - "src": "../../../node_modules/papaparse/papaparse.min.js", - "file": "papaparse.js", - "fileHash": "0523f035", - "needsInterop": true + "@radix-ui/react-toast": { + "src": "../../../node_modules/@radix-ui/react-toast/dist/index.mjs", + "file": "@radix-ui_react-toast.js", + "fileHash": "b094c0a7", + "needsInterop": false }, - "@noble/curves/ed25519.js": { - "src": "../../../node_modules/@noble/curves/ed25519.js", - "file": "@noble_curves_ed25519__js.js", - "fileHash": "913991be", + "@tanstack/react-table": { + "src": "../../../node_modules/@tanstack/react-table/build/lib/index.mjs", + "file": "@tanstack_react-table.js", + "fileHash": "64cf8fb6", "needsInterop": false }, - "@noble/hashes/utils.js": { - "src": "../../../node_modules/@noble/hashes/utils.js", - "file": "@noble_hashes_utils__js.js", - "fileHash": "80e826e6", + "class-variance-authority": { + "src": "../../../node_modules/class-variance-authority/dist/index.mjs", + "file": "class-variance-authority.js", + "fileHash": "4a750b87", "needsInterop": false }, "clsx": { "src": "../../../node_modules/clsx/dist/clsx.mjs", "file": "clsx.js", - "fileHash": "cba0e2f8", + "fileHash": "9fe4c2a8", "needsInterop": false }, - "tailwind-merge": { - "src": "../../../node_modules/tailwind-merge/dist/bundle-mjs.mjs", - "file": "tailwind-merge.js", - "fileHash": "1f503890", + "lucide-react": { + "src": "../../../node_modules/lucide-react/dist/esm/lucide-react.js", + "file": "lucide-react.js", + "fileHash": "ccb83243", "needsInterop": false }, - "@radix-ui/react-dropdown-menu": { - "src": "../../../node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs", - "file": "@radix-ui_react-dropdown-menu.js", - "fileHash": "d195c241", - "needsInterop": false + "papaparse": { + "src": "../../../node_modules/papaparse/papaparse.min.js", + "file": "papaparse.js", + "fileHash": "b6da5454", + "needsInterop": true }, - "@radix-ui/react-label": { - "src": "../../../node_modules/@radix-ui/react-label/dist/index.mjs", - "file": "@radix-ui_react-label.js", - "fileHash": "b77f9d1a", + "react-dom/client": { + "src": "../../../node_modules/react-dom/client.js", + "file": "react-dom_client.js", + "fileHash": "79c102d8", + "needsInterop": true + }, + "sonner": { + "src": "../../../node_modules/sonner/dist/index.mjs", + "file": "sonner.js", + "fileHash": "91546369", "needsInterop": false }, - "@radix-ui/react-select": { - "src": "../../../node_modules/@radix-ui/react-select/dist/index.mjs", - "file": "@radix-ui_react-select.js", - "fileHash": "a60260fe", + "tailwind-merge": { + "src": "../../../node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "file": "tailwind-merge.js", + "fileHash": "beda6d81", "needsInterop": false } }, "chunks": { - "chunk-FKAYWSWW": { - "file": "chunk-FKAYWSWW.js" + "chunk-VINIWLSN": { + "file": "chunk-VINIWLSN.js" + }, + "chunk-KJ4VQ6GH": { + "file": "chunk-KJ4VQ6GH.js" + }, + "chunk-FE7FDHKZ": { + "file": "chunk-FE7FDHKZ.js" }, - "chunk-4CRPNNNE": { - "file": "chunk-4CRPNNNE.js" + "chunk-JNB632GV": { + "file": "chunk-JNB632GV.js" }, - "chunk-ML46ZY4T": { - "file": "chunk-ML46ZY4T.js" + "chunk-Z4UMJCHA": { + "file": "chunk-Z4UMJCHA.js" }, - "chunk-XFRNN5P4": { - "file": "chunk-XFRNN5P4.js" + "chunk-SQROKAUX": { + "file": "chunk-SQROKAUX.js" }, - "chunk-LSB2BQTJ": { - "file": "chunk-LSB2BQTJ.js" + "chunk-LZUCAFHS": { + "file": "chunk-LZUCAFHS.js" }, - "chunk-46KKQMAN": { - "file": "chunk-46KKQMAN.js" + "chunk-FUQEDP2W": { + "file": "chunk-FUQEDP2W.js" }, - "chunk-VINIWLSN": { - "file": "chunk-VINIWLSN.js" + "chunk-HNRLCIXP": { + "file": "chunk-HNRLCIXP.js" }, "chunk-JBZRCD6T": { "file": "chunk-JBZRCD6T.js" }, - "chunk-JNB632GV": { - "file": "chunk-JNB632GV.js" - }, - "chunk-FE7FDHKZ": { - "file": "chunk-FE7FDHKZ.js" + "chunk-M3HRAD2J": { + "file": "chunk-M3HRAD2J.js" }, "chunk-6NP5ER6T": { "file": "chunk-6NP5ER6T.js" }, - "chunk-M3HRAD2J": { - "file": "chunk-M3HRAD2J.js" - }, "chunk-JIJPEAKZ": { "file": "chunk-JIJPEAKZ.js" }, diff --git a/frontend/.vite/deps/chunk-46KKQMAN.js.map b/frontend/.vite/deps/chunk-46KKQMAN.js.map deleted file mode 100644 index 1b28288a..00000000 --- a/frontend/.vite/deps/chunk-46KKQMAN.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../../node_modules/@radix-ui/primitive/src/primitive.tsx", "../../../node_modules/@radix-ui/react-context/src/create-context.tsx", "../../../node_modules/@radix-ui/react-primitive/src/primitive.tsx", "../../../node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/src/slot.tsx", "../../../node_modules/@radix-ui/react-use-callback-ref/src/use-callback-ref.tsx", "../../../node_modules/@radix-ui/react-dismissable-layer/src/dismissable-layer.tsx", "../../../node_modules/@radix-ui/react-use-escape-keydown/src/use-escape-keydown.tsx", "../../../node_modules/@radix-ui/react-use-layout-effect/src/use-layout-effect.tsx", "../../../node_modules/@radix-ui/react-portal/src/portal.tsx", "../../../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state.tsx", "../../../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state-reducer.tsx", "../../../node_modules/@radix-ui/react-use-effect-event/src/use-effect-event.tsx"], - "sourcesContent": ["/* eslint-disable no-restricted-properties */\n\n/* eslint-disable no-restricted-globals */\nexport const canUseDOM = !!(\n typeof window !== 'undefined' &&\n window.document &&\n window.document.createElement\n);\n/* eslint-enable no-restricted-globals */\n\nexport function composeEventHandlers(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n { checkForDefaultPrevented = true } = {}\n) {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n\n if (checkForDefaultPrevented === false || !event.defaultPrevented) {\n return ourEventHandler?.(event);\n }\n };\n}\n\nexport function getOwnerWindow(element: Node | null | undefined) {\n if (!canUseDOM) {\n throw new Error('Cannot access window outside of the DOM');\n }\n // eslint-disable-next-line no-restricted-globals\n return element?.ownerDocument?.defaultView ?? window;\n}\n\nexport function getOwnerDocument(element: Node | null | undefined) {\n if (!canUseDOM) {\n throw new Error('Cannot access document outside of the DOM');\n }\n // eslint-disable-next-line no-restricted-globals\n return element?.ownerDocument ?? document;\n}\n\n/**\n * Lifted from https://github.com/ariakit/ariakit/blob/main/packages/ariakit-core/src/utils/dom.ts#L37\n * MIT License, Copyright (c) AriaKit.\n */\nexport function getActiveElement(\n node: Node | null | undefined,\n activeDescendant = false\n): HTMLElement | null {\n const { activeElement } = getOwnerDocument(node);\n if (!activeElement?.nodeName) {\n // `activeElement` might be an empty object if we're interacting with elements\n // inside of an iframe.\n return null;\n }\n\n if (isFrame(activeElement) && activeElement.contentDocument) {\n return getActiveElement(activeElement.contentDocument.body, activeDescendant);\n }\n\n if (activeDescendant) {\n const id = activeElement.getAttribute('aria-activedescendant');\n if (id) {\n const element = getOwnerDocument(activeElement).getElementById(id);\n if (element) {\n return element;\n }\n }\n }\n\n return activeElement as HTMLElement | null;\n}\n\nexport function isFrame(element: Element): element is HTMLIFrameElement {\n return element.tagName === 'IFRAME';\n}\n", "import * as React from 'react';\n\nfunction createContext(\n rootComponentName: string,\n defaultContext?: ContextValueType\n) {\n const Context = React.createContext(defaultContext);\n\n const Provider: React.FC = (props) => {\n const { children, ...context } = props;\n // Only re-memoize when prop values change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;\n return {children};\n };\n\n Provider.displayName = rootComponentName + 'Provider';\n\n function useContext(consumerName: string) {\n const context = React.useContext(Context);\n if (context) return context;\n if (defaultContext !== undefined) return defaultContext;\n // if a defaultContext wasn't specified, it's a required context.\n throw new Error(`\\`${consumerName}\\` must be used within \\`${rootComponentName}\\``);\n }\n\n return [Provider, useContext] as const;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * createContextScope\n * -----------------------------------------------------------------------------------------------*/\n\ntype Scope = { [scopeName: string]: React.Context[] } | undefined;\ntype ScopeHook = (scope: Scope) => { [__scopeProp: string]: Scope };\ninterface CreateScope {\n scopeName: string;\n (): ScopeHook;\n}\n\nfunction createContextScope(scopeName: string, createContextScopeDeps: CreateScope[] = []) {\n let defaultContexts: any[] = [];\n\n /* -----------------------------------------------------------------------------------------------\n * createContext\n * ---------------------------------------------------------------------------------------------*/\n\n function createContext(\n rootComponentName: string,\n defaultContext?: ContextValueType\n ) {\n const BaseContext = React.createContext(defaultContext);\n const index = defaultContexts.length;\n defaultContexts = [...defaultContexts, defaultContext];\n\n const Provider: React.FC<\n ContextValueType & { scope: Scope; children: React.ReactNode }\n > = (props) => {\n const { scope, children, ...context } = props;\n const Context = scope?.[scopeName]?.[index] || BaseContext;\n // Only re-memoize when prop values change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;\n return {children};\n };\n\n Provider.displayName = rootComponentName + 'Provider';\n\n function useContext(consumerName: string, scope: Scope) {\n const Context = scope?.[scopeName]?.[index] || BaseContext;\n const context = React.useContext(Context);\n if (context) return context;\n if (defaultContext !== undefined) return defaultContext;\n // if a defaultContext wasn't specified, it's a required context.\n throw new Error(`\\`${consumerName}\\` must be used within \\`${rootComponentName}\\``);\n }\n\n return [Provider, useContext] as const;\n }\n\n /* -----------------------------------------------------------------------------------------------\n * createScope\n * ---------------------------------------------------------------------------------------------*/\n\n const createScope: CreateScope = () => {\n const scopeContexts = defaultContexts.map((defaultContext) => {\n return React.createContext(defaultContext);\n });\n return function useScope(scope: Scope) {\n const contexts = scope?.[scopeName] || scopeContexts;\n return React.useMemo(\n () => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: contexts } }),\n [scope, contexts]\n );\n };\n };\n\n createScope.scopeName = scopeName;\n return [createContext, composeContextScopes(createScope, ...createContextScopeDeps)] as const;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * composeContextScopes\n * -----------------------------------------------------------------------------------------------*/\n\nfunction composeContextScopes(...scopes: CreateScope[]) {\n const baseScope = scopes[0];\n if (scopes.length === 1) return baseScope;\n\n const createScope: CreateScope = () => {\n const scopeHooks = scopes.map((createScope) => ({\n useScope: createScope(),\n scopeName: createScope.scopeName,\n }));\n\n return function useComposedScopes(overrideScopes) {\n const nextScopes = scopeHooks.reduce((nextScopes, { useScope, scopeName }) => {\n // We are calling a hook inside a callback which React warns against to avoid inconsistent\n // renders, however, scoping doesn't have render side effects so we ignore the rule.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const scopeProps = useScope(overrideScopes);\n const currentScope = scopeProps[`__scope${scopeName}`];\n return { ...nextScopes, ...currentScope };\n }, {});\n\n return React.useMemo(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }), [nextScopes]);\n };\n };\n\n createScope.scopeName = baseScope.scopeName;\n return createScope;\n}\n\n/* -----------------------------------------------------------------------------------------------*/\n\nexport { createContext, createContextScope };\nexport type { CreateScope, Scope };\n", "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { createSlot } from '@radix-ui/react-slot';\n\nconst NODES = [\n 'a',\n 'button',\n 'div',\n 'form',\n 'h2',\n 'h3',\n 'img',\n 'input',\n 'label',\n 'li',\n 'nav',\n 'ol',\n 'p',\n 'select',\n 'span',\n 'svg',\n 'ul',\n] as const;\n\ntype Primitives = { [E in (typeof NODES)[number]]: PrimitiveForwardRefComponent };\ntype PrimitivePropsWithRef = React.ComponentPropsWithRef & {\n asChild?: boolean;\n};\n\ninterface PrimitiveForwardRefComponent\n extends React.ForwardRefExoticComponent> {}\n\n/* -------------------------------------------------------------------------------------------------\n * Primitive\n * -----------------------------------------------------------------------------------------------*/\n\nconst Primitive = NODES.reduce((primitive, node) => {\n const Slot = createSlot(`Primitive.${node}`);\n const Node = React.forwardRef((props: PrimitivePropsWithRef, forwardedRef: any) => {\n const { asChild, ...primitiveProps } = props;\n const Comp: any = asChild ? Slot : node;\n\n if (typeof window !== 'undefined') {\n (window as any)[Symbol.for('radix-ui')] = true;\n }\n\n return ;\n });\n\n Node.displayName = `Primitive.${node}`;\n\n return { ...primitive, [node]: Node };\n}, {} as Primitives);\n\n/* -------------------------------------------------------------------------------------------------\n * Utils\n * -----------------------------------------------------------------------------------------------*/\n\n/**\n * Flush custom event dispatch\n * https://github.com/radix-ui/primitives/pull/1378\n *\n * React batches *all* event handlers since version 18, this introduces certain considerations when using custom event types.\n *\n * Internally, React prioritises events in the following order:\n * - discrete\n * - continuous\n * - default\n *\n * https://github.com/facebook/react/blob/a8a4742f1c54493df00da648a3f9d26e3db9c8b5/packages/react-dom/src/events/ReactDOMEventListener.js#L294-L350\n *\n * `discrete` is an important distinction as updates within these events are applied immediately.\n * React however, is not able to infer the priority of custom event types due to how they are detected internally.\n * Because of this, it's possible for updates from custom events to be unexpectedly batched when\n * dispatched by another `discrete` event.\n *\n * In order to ensure that updates from custom events are applied predictably, we need to manually flush the batch.\n * This utility should be used when dispatching a custom event from within another `discrete` event, this utility\n * is not necessary when dispatching known event types, or if dispatching a custom type inside a non-discrete event.\n * For example:\n *\n * dispatching a known click \uD83D\uDC4E\n * target.dispatchEvent(new Event(\u2018click\u2019))\n *\n * dispatching a custom type within a non-discrete event \uD83D\uDC4E\n * onScroll={(event) => event.target.dispatchEvent(new CustomEvent(\u2018customType\u2019))}\n *\n * dispatching a custom type within a `discrete` event \uD83D\uDC4D\n * onPointerDown={(event) => dispatchDiscreteCustomEvent(event.target, new CustomEvent(\u2018customType\u2019))}\n *\n * Note: though React classifies `focus`, `focusin` and `focusout` events as `discrete`, it's not recommended to use\n * this utility with them. This is because it's possible for those handlers to be called implicitly during render\n * e.g. when focus is within a component as it is unmounted, or when managing focus on mount.\n */\n\nfunction dispatchDiscreteCustomEvent(target: E['target'], event: E) {\n if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));\n}\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = Primitive;\n\nexport {\n Primitive,\n //\n Root,\n //\n dispatchDiscreteCustomEvent,\n};\nexport type { PrimitivePropsWithRef };\n", "import * as React from 'react';\nimport { composeRefs } from '@radix-ui/react-compose-refs';\n\n/* -------------------------------------------------------------------------------------------------\n * Slot\n * -----------------------------------------------------------------------------------------------*/\n\ninterface SlotProps extends React.HTMLAttributes {\n children?: React.ReactNode;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ export function createSlot(ownerName: string) {\n const SlotClone = createSlotClone(ownerName);\n const Slot = React.forwardRef((props, forwardedRef) => {\n const { children, ...slotProps } = props;\n const childrenArray = React.Children.toArray(children);\n const slottable = childrenArray.find(isSlottable);\n\n if (slottable) {\n // the new element to render is the one passed as a child of `Slottable`\n const newElement = slottable.props.children;\n\n const newChildren = childrenArray.map((child) => {\n if (child === slottable) {\n // because the new element will be the one rendered, we are only interested\n // in grabbing its children (`newElement.props.children`)\n if (React.Children.count(newElement) > 1) return React.Children.only(null);\n return React.isValidElement(newElement)\n ? (newElement.props as { children: React.ReactNode }).children\n : null;\n } else {\n return child;\n }\n });\n\n return (\n \n {React.isValidElement(newElement)\n ? React.cloneElement(newElement, undefined, newChildren)\n : null}\n \n );\n }\n\n return (\n \n {children}\n \n );\n });\n\n Slot.displayName = `${ownerName}.Slot`;\n return Slot;\n}\n\nconst Slot = createSlot('Slot');\n\n/* -------------------------------------------------------------------------------------------------\n * SlotClone\n * -----------------------------------------------------------------------------------------------*/\n\ninterface SlotCloneProps {\n children: React.ReactNode;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ function createSlotClone(ownerName: string) {\n const SlotClone = React.forwardRef((props, forwardedRef) => {\n const { children, ...slotProps } = props;\n\n if (React.isValidElement(children)) {\n const childrenRef = getElementRef(children);\n const props = mergeProps(slotProps, children.props as AnyProps);\n // do not pass ref to React.Fragment for React 19 compatibility\n if (children.type !== React.Fragment) {\n props.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;\n }\n return React.cloneElement(children, props);\n }\n\n return React.Children.count(children) > 1 ? React.Children.only(null) : null;\n });\n\n SlotClone.displayName = `${ownerName}.SlotClone`;\n return SlotClone;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Slottable\n * -----------------------------------------------------------------------------------------------*/\n\nconst SLOTTABLE_IDENTIFIER = Symbol('radix.slottable');\n\ninterface SlottableProps {\n children: React.ReactNode;\n}\n\ninterface SlottableComponent extends React.FC {\n __radixId: symbol;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ export function createSlottable(ownerName: string) {\n const Slottable: SlottableComponent = ({ children }) => {\n return <>{children};\n };\n Slottable.displayName = `${ownerName}.Slottable`;\n Slottable.__radixId = SLOTTABLE_IDENTIFIER;\n return Slottable;\n}\n\nconst Slottable = createSlottable('Slottable');\n\n/* ---------------------------------------------------------------------------------------------- */\n\ntype AnyProps = Record;\n\nfunction isSlottable(\n child: React.ReactNode\n): child is React.ReactElement {\n return (\n React.isValidElement(child) &&\n typeof child.type === 'function' &&\n '__radixId' in child.type &&\n child.type.__radixId === SLOTTABLE_IDENTIFIER\n );\n}\n\nfunction mergeProps(slotProps: AnyProps, childProps: AnyProps) {\n // all child props should override\n const overrideProps = { ...childProps };\n\n for (const propName in childProps) {\n const slotPropValue = slotProps[propName];\n const childPropValue = childProps[propName];\n\n const isHandler = /^on[A-Z]/.test(propName);\n if (isHandler) {\n // if the handler exists on both, we compose them\n if (slotPropValue && childPropValue) {\n overrideProps[propName] = (...args: unknown[]) => {\n const result = childPropValue(...args);\n slotPropValue(...args);\n return result;\n };\n }\n // but if it exists only on the slot, we use only this one\n else if (slotPropValue) {\n overrideProps[propName] = slotPropValue;\n }\n }\n // if it's `style`, we merge them\n else if (propName === 'style') {\n overrideProps[propName] = { ...slotPropValue, ...childPropValue };\n } else if (propName === 'className') {\n overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(' ');\n }\n }\n\n return { ...slotProps, ...overrideProps };\n}\n\n// Before React 19 accessing `element.props.ref` will throw a warning and suggest using `element.ref`\n// After React 19 accessing `element.ref` does the opposite.\n// https://github.com/facebook/react/pull/28348\n//\n// Access the ref using the method that doesn't yield a warning.\nfunction getElementRef(element: React.ReactElement) {\n // React <=18 in DEV\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get;\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return (element as any).ref;\n }\n\n // React 19 in DEV\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get;\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return (element.props as { ref?: React.Ref }).ref;\n }\n\n // Not DEV\n return (element.props as { ref?: React.Ref }).ref || (element as any).ref;\n}\n\nexport {\n Slot,\n Slottable,\n //\n Slot as Root,\n};\nexport type { SlotProps };\n", "import * as React from 'react';\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef any>(callback: T | undefined): T {\n const callbackRef = React.useRef(callback);\n\n React.useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useCallbackRef };\n", "import * as React from 'react';\nimport { composeEventHandlers } from '@radix-ui/primitive';\nimport { Primitive, dispatchDiscreteCustomEvent } from '@radix-ui/react-primitive';\nimport { useComposedRefs } from '@radix-ui/react-compose-refs';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\nimport { useEscapeKeydown } from '@radix-ui/react-use-escape-keydown';\n\n/* -------------------------------------------------------------------------------------------------\n * DismissableLayer\n * -----------------------------------------------------------------------------------------------*/\n\nconst DISMISSABLE_LAYER_NAME = 'DismissableLayer';\nconst CONTEXT_UPDATE = 'dismissableLayer.update';\nconst POINTER_DOWN_OUTSIDE = 'dismissableLayer.pointerDownOutside';\nconst FOCUS_OUTSIDE = 'dismissableLayer.focusOutside';\n\nlet originalBodyPointerEvents: string;\n\nconst DismissableLayerContext = React.createContext({\n layers: new Set(),\n layersWithOutsidePointerEventsDisabled: new Set(),\n branches: new Set(),\n});\n\ntype DismissableLayerElement = React.ComponentRef;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef;\ninterface DismissableLayerProps extends PrimitiveDivProps {\n /**\n * When `true`, hover/focus/click interactions will be disabled on elements outside\n * the `DismissableLayer`. Users will need to click twice on outside elements to\n * interact with them: once to close the `DismissableLayer`, and again to trigger the element.\n */\n disableOutsidePointerEvents?: boolean;\n /**\n * Event handler called when the escape key is down.\n * Can be prevented.\n */\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n /**\n * Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`.\n * Can be prevented.\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\n /**\n * Event handler called when the focus moves outside of the `DismissableLayer`.\n * Can be prevented.\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void;\n /**\n * Event handler called when an interaction happens outside the `DismissableLayer`.\n * Specifically, when a `pointerdown` event happens outside or focus moves outside of it.\n * Can be prevented.\n */\n onInteractOutside?: (event: PointerDownOutsideEvent | FocusOutsideEvent) => void;\n /**\n * Handler called when the `DismissableLayer` should be dismissed\n */\n onDismiss?: () => void;\n}\n\nconst DismissableLayer = React.forwardRef(\n (props, forwardedRef) => {\n const {\n disableOutsidePointerEvents = false,\n onEscapeKeyDown,\n onPointerDownOutside,\n onFocusOutside,\n onInteractOutside,\n onDismiss,\n ...layerProps\n } = props;\n const context = React.useContext(DismissableLayerContext);\n const [node, setNode] = React.useState(null);\n const ownerDocument = node?.ownerDocument ?? globalThis?.document;\n const [, force] = React.useState({});\n const composedRefs = useComposedRefs(forwardedRef, (node) => setNode(node));\n const layers = Array.from(context.layers);\n const [highestLayerWithOutsidePointerEventsDisabled] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1); // prettier-ignore\n const highestLayerWithOutsidePointerEventsDisabledIndex = layers.indexOf(highestLayerWithOutsidePointerEventsDisabled!); // prettier-ignore\n const index = node ? layers.indexOf(node) : -1;\n const isBodyPointerEventsDisabled = context.layersWithOutsidePointerEventsDisabled.size > 0;\n const isPointerEventsEnabled = index >= highestLayerWithOutsidePointerEventsDisabledIndex;\n\n const pointerDownOutside = usePointerDownOutside((event) => {\n const target = event.target as HTMLElement;\n const isPointerDownOnBranch = [...context.branches].some((branch) => branch.contains(target));\n if (!isPointerEventsEnabled || isPointerDownOnBranch) return;\n onPointerDownOutside?.(event);\n onInteractOutside?.(event);\n if (!event.defaultPrevented) onDismiss?.();\n }, ownerDocument);\n\n const focusOutside = useFocusOutside((event) => {\n const target = event.target as HTMLElement;\n const isFocusInBranch = [...context.branches].some((branch) => branch.contains(target));\n if (isFocusInBranch) return;\n onFocusOutside?.(event);\n onInteractOutside?.(event);\n if (!event.defaultPrevented) onDismiss?.();\n }, ownerDocument);\n\n useEscapeKeydown((event) => {\n const isHighestLayer = index === context.layers.size - 1;\n if (!isHighestLayer) return;\n onEscapeKeyDown?.(event);\n if (!event.defaultPrevented && onDismiss) {\n event.preventDefault();\n onDismiss();\n }\n }, ownerDocument);\n\n React.useEffect(() => {\n if (!node) return;\n if (disableOutsidePointerEvents) {\n if (context.layersWithOutsidePointerEventsDisabled.size === 0) {\n originalBodyPointerEvents = ownerDocument.body.style.pointerEvents;\n ownerDocument.body.style.pointerEvents = 'none';\n }\n context.layersWithOutsidePointerEventsDisabled.add(node);\n }\n context.layers.add(node);\n dispatchUpdate();\n return () => {\n if (\n disableOutsidePointerEvents &&\n context.layersWithOutsidePointerEventsDisabled.size === 1\n ) {\n ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;\n }\n };\n }, [node, ownerDocument, disableOutsidePointerEvents, context]);\n\n /**\n * We purposefully prevent combining this effect with the `disableOutsidePointerEvents` effect\n * because a change to `disableOutsidePointerEvents` would remove this layer from the stack\n * and add it to the end again so the layering order wouldn't be _creation order_.\n * We only want them to be removed from context stacks when unmounted.\n */\n React.useEffect(() => {\n return () => {\n if (!node) return;\n context.layers.delete(node);\n context.layersWithOutsidePointerEventsDisabled.delete(node);\n dispatchUpdate();\n };\n }, [node, context]);\n\n React.useEffect(() => {\n const handleUpdate = () => force({});\n document.addEventListener(CONTEXT_UPDATE, handleUpdate);\n return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate);\n }, []);\n\n return (\n \n );\n }\n);\n\nDismissableLayer.displayName = DISMISSABLE_LAYER_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * DismissableLayerBranch\n * -----------------------------------------------------------------------------------------------*/\n\nconst BRANCH_NAME = 'DismissableLayerBranch';\n\ntype DismissableLayerBranchElement = React.ComponentRef;\ninterface DismissableLayerBranchProps extends PrimitiveDivProps {}\n\nconst DismissableLayerBranch = React.forwardRef<\n DismissableLayerBranchElement,\n DismissableLayerBranchProps\n>((props, forwardedRef) => {\n const context = React.useContext(DismissableLayerContext);\n const ref = React.useRef(null);\n const composedRefs = useComposedRefs(forwardedRef, ref);\n\n React.useEffect(() => {\n const node = ref.current;\n if (node) {\n context.branches.add(node);\n return () => {\n context.branches.delete(node);\n };\n }\n }, [context.branches]);\n\n return ;\n});\n\nDismissableLayerBranch.displayName = BRANCH_NAME;\n\n/* -----------------------------------------------------------------------------------------------*/\n\ntype PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;\ntype FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>;\n\n/**\n * Listens for `pointerdown` outside a react subtree. We use `pointerdown` rather than `pointerup`\n * to mimic layer dismissing behaviour present in OS.\n * Returns props to pass to the node we want to check for outside events.\n */\nfunction usePointerDownOutside(\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const handlePointerDownOutside = useCallbackRef(onPointerDownOutside) as EventListener;\n const isPointerInsideReactTreeRef = React.useRef(false);\n const handleClickRef = React.useRef(() => {});\n\n React.useEffect(() => {\n const handlePointerDown = (event: PointerEvent) => {\n if (event.target && !isPointerInsideReactTreeRef.current) {\n const eventDetail = { originalEvent: event };\n\n function handleAndDispatchPointerDownOutsideEvent() {\n handleAndDispatchCustomEvent(\n POINTER_DOWN_OUTSIDE,\n handlePointerDownOutside,\n eventDetail,\n { discrete: true }\n );\n }\n\n /**\n * On touch devices, we need to wait for a click event because browsers implement\n * a ~350ms delay between the time the user stops touching the display and when the\n * browser executres events. We need to ensure we don't reactivate pointer-events within\n * this timeframe otherwise the browser may execute events that should have been prevented.\n *\n * Additionally, this also lets us deal automatically with cancellations when a click event\n * isn't raised because the page was considered scrolled/drag-scrolled, long-pressed, etc.\n *\n * This is why we also continuously remove the previous listener, because we cannot be\n * certain that it was raised, and therefore cleaned-up.\n */\n if (event.pointerType === 'touch') {\n ownerDocument.removeEventListener('click', handleClickRef.current);\n handleClickRef.current = handleAndDispatchPointerDownOutsideEvent;\n ownerDocument.addEventListener('click', handleClickRef.current, { once: true });\n } else {\n handleAndDispatchPointerDownOutsideEvent();\n }\n } else {\n // We need to remove the event listener in case the outside click has been canceled.\n // See: https://github.com/radix-ui/primitives/issues/2171\n ownerDocument.removeEventListener('click', handleClickRef.current);\n }\n isPointerInsideReactTreeRef.current = false;\n };\n /**\n * if this hook executes in a component that mounts via a `pointerdown` event, the event\n * would bubble up to the document and trigger a `pointerDownOutside` event. We avoid\n * this by delaying the event listener registration on the document.\n * This is not React specific, but rather how the DOM works, ie:\n * ```\n * button.addEventListener('pointerdown', () => {\n * console.log('I will log');\n * document.addEventListener('pointerdown', () => {\n * console.log('I will also log');\n * })\n * });\n */\n const timerId = window.setTimeout(() => {\n ownerDocument.addEventListener('pointerdown', handlePointerDown);\n }, 0);\n return () => {\n window.clearTimeout(timerId);\n ownerDocument.removeEventListener('pointerdown', handlePointerDown);\n ownerDocument.removeEventListener('click', handleClickRef.current);\n };\n }, [ownerDocument, handlePointerDownOutside]);\n\n return {\n // ensures we check React component tree (not just DOM tree)\n onPointerDownCapture: () => (isPointerInsideReactTreeRef.current = true),\n };\n}\n\n/**\n * Listens for when focus happens outside a react subtree.\n * Returns props to pass to the root (node) of the subtree we want to check.\n */\nfunction useFocusOutside(\n onFocusOutside?: (event: FocusOutsideEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const handleFocusOutside = useCallbackRef(onFocusOutside) as EventListener;\n const isFocusInsideReactTreeRef = React.useRef(false);\n\n React.useEffect(() => {\n const handleFocus = (event: FocusEvent) => {\n if (event.target && !isFocusInsideReactTreeRef.current) {\n const eventDetail = { originalEvent: event };\n handleAndDispatchCustomEvent(FOCUS_OUTSIDE, handleFocusOutside, eventDetail, {\n discrete: false,\n });\n }\n };\n ownerDocument.addEventListener('focusin', handleFocus);\n return () => ownerDocument.removeEventListener('focusin', handleFocus);\n }, [ownerDocument, handleFocusOutside]);\n\n return {\n onFocusCapture: () => (isFocusInsideReactTreeRef.current = true),\n onBlurCapture: () => (isFocusInsideReactTreeRef.current = false),\n };\n}\n\nfunction dispatchUpdate() {\n const event = new CustomEvent(CONTEXT_UPDATE);\n document.dispatchEvent(event);\n}\n\nfunction handleAndDispatchCustomEvent(\n name: string,\n handler: ((event: E) => void) | undefined,\n detail: { originalEvent: OriginalEvent } & (E extends CustomEvent ? D : never),\n { discrete }: { discrete: boolean }\n) {\n const target = detail.originalEvent.target;\n const event = new CustomEvent(name, { bubbles: false, cancelable: true, detail });\n if (handler) target.addEventListener(name, handler as EventListener, { once: true });\n\n if (discrete) {\n dispatchDiscreteCustomEvent(target, event);\n } else {\n target.dispatchEvent(event);\n }\n}\n\nconst Root = DismissableLayer;\nconst Branch = DismissableLayerBranch;\n\nexport {\n DismissableLayer,\n DismissableLayerBranch,\n //\n Root,\n Branch,\n};\nexport type { DismissableLayerProps };\n", "import * as React from 'react';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\n\n/**\n * Listens for when the escape key is down\n */\nfunction useEscapeKeydown(\n onEscapeKeyDownProp?: (event: KeyboardEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const onEscapeKeyDown = useCallbackRef(onEscapeKeyDownProp);\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onEscapeKeyDown(event);\n }\n };\n ownerDocument.addEventListener('keydown', handleKeyDown, { capture: true });\n return () => ownerDocument.removeEventListener('keydown', handleKeyDown, { capture: true });\n }, [onEscapeKeyDown, ownerDocument]);\n}\n\nexport { useEscapeKeydown };\n", "import * as React from 'react';\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\nexport { useLayoutEffect };\n", "import * as React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Primitive } from '@radix-ui/react-primitive';\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\n\n/* -------------------------------------------------------------------------------------------------\n * Portal\n * -----------------------------------------------------------------------------------------------*/\n\nconst PORTAL_NAME = 'Portal';\n\ntype PortalElement = React.ComponentRef;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef;\ninterface PortalProps extends PrimitiveDivProps {\n /**\n * An optional container where the portaled content should be appended.\n */\n container?: Element | DocumentFragment | null;\n}\n\nconst Portal = React.forwardRef((props, forwardedRef) => {\n const { container: containerProp, ...portalProps } = props;\n const [mounted, setMounted] = React.useState(false);\n useLayoutEffect(() => setMounted(true), []);\n const container = containerProp || (mounted && globalThis?.document?.body);\n return container\n ? ReactDOM.createPortal(, container)\n : null;\n});\n\nPortal.displayName = PORTAL_NAME;\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = Portal;\n\nexport {\n Portal,\n //\n Root,\n};\nexport type { PortalProps };\n", "import * as React from 'react';\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n (React as any)[' useInsertionEffect '.trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler = (state: T) => void;\ntype SetStateFn = React.Dispatch>;\n\ninterface UseControllableStateParams {\n prop?: T | undefined;\n defaultProp: T;\n onChange?: ChangeHandler;\n caller?: string;\n}\n\nexport function useControllableState({\n prop,\n defaultProp,\n onChange = () => {},\n caller,\n}: UseControllableStateParams): [T, SetStateFn] {\n const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(prop !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n const setValue = React.useCallback>(\n (nextValue) => {\n if (isControlled) {\n const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n if (value !== prop) {\n onChangeRef.current?.(value);\n }\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, onChangeRef]\n );\n\n return [value, setValue];\n}\n\nfunction useUncontrolledState({\n defaultProp,\n onChange,\n}: Omit, 'prop'>): [\n Value: T,\n setValue: React.Dispatch>,\n OnChangeRef: React.RefObject | undefined>,\n] {\n const [value, setValue] = React.useState(defaultProp);\n const prevValueRef = React.useRef(value);\n\n const onChangeRef = React.useRef(onChange);\n useInsertionEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n if (prevValueRef.current !== value) {\n onChangeRef.current?.(value);\n prevValueRef.current = value;\n }\n }, [value, prevValueRef]);\n\n return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "import * as React from 'react';\nimport { useEffectEvent } from '@radix-ui/react-use-effect-event';\n\ntype ChangeHandler = (state: T) => void;\n\ninterface UseControllableStateParams {\n prop: T | undefined;\n defaultProp: T;\n onChange: ChangeHandler | undefined;\n caller: string;\n}\n\ninterface AnyAction {\n type: string;\n}\n\nconst SYNC_STATE = Symbol('RADIX:SYNC_STATE');\n\ninterface SyncStateAction {\n type: typeof SYNC_STATE;\n state: T;\n}\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialState: S\n): [S & { state: T }, React.Dispatch];\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialArg: I,\n init: (i: I & { state: T }) => S\n): [S & { state: T }, React.Dispatch];\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialArg: any,\n init?: (i: any) => Omit\n): [S & { state: T }, React.Dispatch] {\n const { prop: controlledState, defaultProp, onChange: onChangeProp, caller } = userArgs;\n const isControlled = controlledState !== undefined;\n\n const onChange = useEffectEvent(onChangeProp);\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(controlledState !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n type InternalState = S & { state: T };\n const args: [InternalState] = [{ ...initialArg, state: defaultProp }];\n if (init) {\n // @ts-expect-error\n args.push(init);\n }\n\n const [internalState, dispatch] = React.useReducer(\n (state: InternalState, action: A | SyncStateAction): InternalState => {\n if (action.type === SYNC_STATE) {\n return { ...state, state: action.state };\n }\n\n const next = reducer(state, action);\n if (isControlled && !Object.is(next.state, state.state)) {\n onChange(next.state);\n }\n return next;\n },\n ...args\n );\n\n const uncontrolledState = internalState.state;\n const prevValueRef = React.useRef(uncontrolledState);\n React.useEffect(() => {\n if (prevValueRef.current !== uncontrolledState) {\n prevValueRef.current = uncontrolledState;\n if (!isControlled) {\n onChange(uncontrolledState);\n }\n }\n }, [onChange, uncontrolledState, prevValueRef, isControlled]);\n\n const state = React.useMemo(() => {\n const isControlled = controlledState !== undefined;\n if (isControlled) {\n return { ...internalState, state: controlledState };\n }\n\n return internalState;\n }, [internalState, controlledState]);\n\n React.useEffect(() => {\n // Sync internal state for controlled components so that reducer is called\n // with the correct state values\n if (isControlled && !Object.is(controlledState, internalState.state)) {\n dispatch({ type: SYNC_STATE, state: controlledState });\n }\n }, [controlledState, internalState.state, isControlled]);\n\n return [state, dispatch as React.Dispatch];\n}\n", "/* eslint-disable react-hooks/rules-of-hooks */\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\nimport * as React from 'react';\n\ntype AnyFunction = (...args: any[]) => any;\n\n// See https://github.com/webpack/webpack/issues/14814\nconst useReactEffectEvent = (React as any)[' useEffectEvent '.trim().toString()];\nconst useReactInsertionEffect = (React as any)[' useInsertionEffect '.trim().toString()];\n\n/**\n * Designed to approximate the behavior on `experimental_useEffectEvent` as best\n * as possible until its stable release, and back-fill it as a shim as needed.\n */\nexport function useEffectEvent(callback?: T): T {\n if (typeof useReactEffectEvent === 'function') {\n return useReactEffectEvent(callback);\n }\n\n const ref = React.useRef(() => {\n throw new Error('Cannot call an event handler while rendering.');\n });\n // See https://github.com/webpack/webpack/issues/14814\n if (typeof useReactInsertionEffect === 'function') {\n useReactInsertionEffect(() => {\n ref.current = callback;\n });\n } else {\n useLayoutEffect(() => {\n ref.current = callback;\n });\n }\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => ref.current?.(...args)) as T, []);\n}\n"], - "mappings": ";;;;;;;;;;;;;;;;;;AAGO,IAAM,YAAY,CAAC,EACxB,OAAO,WAAW,eAClB,OAAO,YACP,OAAO,SAAS;AAIX,SAAS,qBACd,sBACA,iBACA,EAAE,2BAA2B,KAAK,IAAI,CAAC,GACvC;AACA,SAAO,SAAS,YAAY,OAAU;AACpC,iEAAuB;AAEvB,QAAI,6BAA6B,SAAS,CAAC,MAAM,kBAAkB;AACjE,aAAO,mDAAkB;IAC3B;EACF;AACF;;;ACtBA,YAAuB;AAaZ,yBAAA;AAXX,SAASA,eACP,mBACA,gBACA;AACA,QAAM,UAAgB,oBAA4C,cAAc;AAEhF,QAAM,WAAuE,CAAC,UAAU;AACtF,UAAM,EAAE,UAAU,GAAG,QAAQ,IAAI;AAGjC,UAAM,QAAc,cAAQ,MAAM,SAAS,OAAO,OAAO,OAAO,CAAC;AACjE,eAAO,wBAAC,QAAQ,UAAR,EAAiB,OAAe,SAAA,CAAS;EACnD;AAEA,WAAS,cAAc,oBAAoB;AAE3C,WAASC,aAAW,cAAsB;AACxC,UAAM,UAAgB,iBAAW,OAAO;AACxC,QAAI,QAAS,QAAO;AACpB,QAAI,mBAAmB,OAAW,QAAO;AAEzC,UAAM,IAAI,MAAM,KAAK,YAAY,4BAA4B,iBAAiB,IAAI;EACpF;AAEA,SAAO,CAAC,UAAUA,YAAU;AAC9B;AAaA,SAAS,mBAAmB,WAAmB,yBAAwC,CAAC,GAAG;AACzF,MAAI,kBAAyB,CAAC;AAM9B,WAASD,gBACP,mBACA,gBACA;AACA,UAAM,cAAoB,oBAA4C,cAAc;AACpF,UAAM,QAAQ,gBAAgB;AAC9B,sBAAkB,CAAC,GAAG,iBAAiB,cAAc;AAErD,UAAM,WAEF,CAAC,UAAU;;AACb,YAAM,EAAE,OAAO,UAAU,GAAG,QAAQ,IAAI;AACxC,YAAM,YAAU,oCAAQ,eAAR,mBAAqB,WAAU;AAG/C,YAAM,QAAc,cAAQ,MAAM,SAAS,OAAO,OAAO,OAAO,CAAC;AACjE,iBAAO,wBAAC,QAAQ,UAAR,EAAiB,OAAe,SAAA,CAAS;IACnD;AAEA,aAAS,cAAc,oBAAoB;AAE3C,aAASC,aAAW,cAAsB,OAA4C;;AACpF,YAAM,YAAU,oCAAQ,eAAR,mBAAqB,WAAU;AAC/C,YAAM,UAAgB,iBAAW,OAAO;AACxC,UAAI,QAAS,QAAO;AACpB,UAAI,mBAAmB,OAAW,QAAO;AAEzC,YAAM,IAAI,MAAM,KAAK,YAAY,4BAA4B,iBAAiB,IAAI;IACpF;AAEA,WAAO,CAAC,UAAUA,YAAU;EAC9B;AAMA,QAAM,cAA2B,MAAM;AACrC,UAAM,gBAAgB,gBAAgB,IAAI,CAAC,mBAAmB;AAC5D,aAAa,oBAAc,cAAc;IAC3C,CAAC;AACD,WAAO,SAAS,SAAS,OAAc;AACrC,YAAM,YAAW,+BAAQ,eAAc;AACvC,aAAa;QACX,OAAO,EAAE,CAAC,UAAU,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,EAAE;QACtE,CAAC,OAAO,QAAQ;MAClB;IACF;EACF;AAEA,cAAY,YAAY;AACxB,SAAO,CAACD,iBAAe,qBAAqB,aAAa,GAAG,sBAAsB,CAAC;AACrF;AAMA,SAAS,wBAAwB,QAAuB;AACtD,QAAM,YAAY,OAAO,CAAC;AAC1B,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,cAA2B,MAAM;AACrC,UAAM,aAAa,OAAO,IAAI,CAACE,kBAAiB;MAC9C,UAAUA,aAAY;MACtB,WAAWA,aAAY;IACzB,EAAE;AAEF,WAAO,SAAS,kBAAkB,gBAAgB;AAChD,YAAM,aAAa,WAAW,OAAO,CAACC,aAAY,EAAE,UAAU,UAAU,MAAM;AAI5E,cAAM,aAAa,SAAS,cAAc;AAC1C,cAAM,eAAe,WAAW,UAAU,SAAS,EAAE;AACrD,eAAO,EAAE,GAAGA,aAAY,GAAG,aAAa;MAC1C,GAAG,CAAC,CAAC;AAEL,aAAa,cAAQ,OAAO,EAAE,CAAC,UAAU,UAAU,SAAS,EAAE,GAAG,WAAW,IAAI,CAAC,UAAU,CAAC;IAC9F;EACF;AAEA,cAAY,YAAY,UAAU;AAClC,SAAO;AACT;;;ACnIA,IAAAC,SAAuB;AACvB,eAA0B;;;ACD1B,IAAAC,SAAuB;AAoCf,IAAAC,sBAAA;AAzB0B,SAAS,WAAW,WAAmB;AACvE,QAAM,YAAY,gBAAgB,SAAS;AAC3C,QAAMC,QAAa,kBAAmC,CAAC,OAAO,iBAAiB;AAC7E,UAAM,EAAE,UAAU,GAAG,UAAU,IAAI;AACnC,UAAM,gBAAsB,gBAAS,QAAQ,QAAQ;AACrD,UAAM,YAAY,cAAc,KAAK,WAAW;AAEhD,QAAI,WAAW;AAEb,YAAM,aAAa,UAAU,MAAM;AAEnC,YAAM,cAAc,cAAc,IAAI,CAAC,UAAU;AAC/C,YAAI,UAAU,WAAW;AAGvB,cAAU,gBAAS,MAAM,UAAU,IAAI,EAAG,QAAa,gBAAS,KAAK,IAAI;AACzE,iBAAa,sBAAe,UAAU,IACjC,WAAW,MAAwC,WACpD;QACN,OAAO;AACL,iBAAO;QACT;MACF,CAAC;AAED,iBACE,yBAAC,WAAA,EAAW,GAAG,WAAW,KAAK,cAC5B,UAAM,sBAAe,UAAU,IACtB,oBAAa,YAAY,QAAW,WAAW,IACrD,KAAA,CACN;IAEJ;AAEA,eACE,yBAAC,WAAA,EAAW,GAAG,WAAW,KAAK,cAC5B,SAAA,CACH;EAEJ,CAAC;AAEDA,QAAK,cAAc,GAAG,SAAS;AAC/B,SAAOA;AACT;AAEA,IAAM,OAAO,WAAW,MAAM;AAUH,SAAS,gBAAgB,WAAmB;AACrE,QAAM,YAAkB,kBAAgC,CAAC,OAAO,iBAAiB;AAC/E,UAAM,EAAE,UAAU,GAAG,UAAU,IAAI;AAEnC,QAAU,sBAAe,QAAQ,GAAG;AAClC,YAAM,cAAc,cAAc,QAAQ;AAC1C,YAAMC,SAAQ,WAAW,WAAW,SAAS,KAAiB;AAE9D,UAAI,SAAS,SAAe,iBAAU;AACpCA,eAAM,MAAM,eAAe,YAAY,cAAc,WAAW,IAAI;MACtE;AACA,aAAa,oBAAa,UAAUA,MAAK;IAC3C;AAEA,WAAa,gBAAS,MAAM,QAAQ,IAAI,IAAU,gBAAS,KAAK,IAAI,IAAI;EAC1E,CAAC;AAED,YAAU,cAAc,GAAG,SAAS;AACpC,SAAO;AACT;AAMA,IAAM,uBAAuB,OAAO,iBAAiB;AAUnB,SAAS,gBAAgB,WAAmB;AAC5E,QAAMC,aAAgC,CAAC,EAAE,SAAS,MAAM;AACtD,eAAO,yBAAAC,oBAAAA,UAAA,EAAG,SAAA,CAAS;EACrB;AACAD,aAAU,cAAc,GAAG,SAAS;AACpCA,aAAU,YAAY;AACtB,SAAOA;AACT;AAEA,IAAM,YAAY,gBAAgB,WAAW;AAM7C,SAAS,YACP,OAC+D;AAC/D,SACQ,sBAAe,KAAK,KAC1B,OAAO,MAAM,SAAS,cACtB,eAAe,MAAM,QACrB,MAAM,KAAK,cAAc;AAE7B;AAEA,SAAS,WAAW,WAAqB,YAAsB;AAE7D,QAAM,gBAAgB,EAAE,GAAG,WAAW;AAEtC,aAAW,YAAY,YAAY;AACjC,UAAM,gBAAgB,UAAU,QAAQ;AACxC,UAAM,iBAAiB,WAAW,QAAQ;AAE1C,UAAM,YAAY,WAAW,KAAK,QAAQ;AAC1C,QAAI,WAAW;AAEb,UAAI,iBAAiB,gBAAgB;AACnC,sBAAc,QAAQ,IAAI,IAAI,SAAoB;AAChD,gBAAM,SAAS,eAAe,GAAG,IAAI;AACrC,wBAAc,GAAG,IAAI;AACrB,iBAAO;QACT;MACF,WAES,eAAe;AACtB,sBAAc,QAAQ,IAAI;MAC5B;IACF,WAES,aAAa,SAAS;AAC7B,oBAAc,QAAQ,IAAI,EAAE,GAAG,eAAe,GAAG,eAAe;IAClE,WAAW,aAAa,aAAa;AACnC,oBAAc,QAAQ,IAAI,CAAC,eAAe,cAAc,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;IACpF;EACF;AAEA,SAAO,EAAE,GAAG,WAAW,GAAG,cAAc;AAC1C;AAOA,SAAS,cAAc,SAA6B;;AAElD,MAAI,UAAS,YAAO,yBAAyB,QAAQ,OAAO,KAAK,MAApD,mBAAuD;AACpE,MAAI,UAAU,UAAU,oBAAoB,UAAU,OAAO;AAC7D,MAAI,SAAS;AACX,WAAQ,QAAgB;EAC1B;AAGA,YAAS,YAAO,yBAAyB,SAAS,KAAK,MAA9C,mBAAiD;AAC1D,YAAU,UAAU,oBAAoB,UAAU,OAAO;AACzD,MAAI,SAAS;AACX,WAAQ,QAAQ,MAAuC;EACzD;AAGA,SAAQ,QAAQ,MAAuC,OAAQ,QAAgB;AACjF;;;ADxIW,IAAAE,sBAAA;AA1CX,IAAM,QAAQ;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAcA,IAAM,YAAY,MAAM,OAAO,CAAC,WAAW,SAAS;AAClD,QAAMC,QAAO,WAAW,aAAa,IAAI,EAAE;AAC3C,QAAM,OAAa,kBAAW,CAAC,OAA2C,iBAAsB;AAC9F,UAAM,EAAE,SAAS,GAAG,eAAe,IAAI;AACvC,UAAM,OAAY,UAAUA,QAAO;AAEnC,QAAI,OAAO,WAAW,aAAa;AAChC,aAAe,OAAO,IAAI,UAAU,CAAC,IAAI;IAC5C;AAEA,eAAO,yBAAC,MAAA,EAAM,GAAG,gBAAgB,KAAK,aAAA,CAAc;EACtD,CAAC;AAED,OAAK,cAAc,aAAa,IAAI;AAEpC,SAAO,EAAE,GAAG,WAAW,CAAC,IAAI,GAAG,KAAK;AACtC,GAAG,CAAC,CAAe;AA2CnB,SAAS,4BAAmD,QAAqB,OAAU;AACzF,MAAI,OAAiB,CAAA,mBAAU,MAAM,OAAO,cAAc,KAAK,CAAC;AAClE;;;AEjGA,IAAAC,SAAuB;AAMvB,SAAS,eAAkD,UAA4B;AACrF,QAAM,cAAoB,cAAO,QAAQ;AAEnC,EAAA,iBAAU,MAAM;AACpB,gBAAY,UAAU;EACxB,CAAC;AAGD,SAAa,eAAQ,MAAO,IAAI,SAAA;;AAAS,6BAAY,YAAZ,qCAAsB,GAAG;KAAa,CAAC,CAAC;AACnF;;;ACfA,IAAAC,SAAuB;;;ACAvB,IAAAC,SAAuB;AAMvB,SAAS,iBACP,qBACA,gBAA0B,yCAAY,UACtC;AACA,QAAM,kBAAkB,eAAe,mBAAmB;AAEpD,EAAA,iBAAU,MAAM;AACpB,UAAM,gBAAgB,CAAC,UAAyB;AAC9C,UAAI,MAAM,QAAQ,UAAU;AAC1B,wBAAgB,KAAK;MACvB;IACF;AACA,kBAAc,iBAAiB,WAAW,eAAe,EAAE,SAAS,KAAK,CAAC;AAC1E,WAAO,MAAM,cAAc,oBAAoB,WAAW,eAAe,EAAE,SAAS,KAAK,CAAC;EAC5F,GAAG,CAAC,iBAAiB,aAAa,CAAC;AACrC;;;ADqIM,IAAAC,sBAAA;AA/IN,IAAM,yBAAyB;AAC/B,IAAM,iBAAiB;AACvB,IAAM,uBAAuB;AAC7B,IAAM,gBAAgB;AAEtB,IAAI;AAEJ,IAAM,0BAAgC,qBAAc;EAClD,QAAQ,oBAAI,IAA6B;EACzC,wCAAwC,oBAAI,IAA6B;EACzE,UAAU,oBAAI,IAAmC;AACnD,CAAC;AAsCD,IAAM,mBAAyB;EAC7B,CAAC,OAAO,iBAAiB;AACvB,UAAM;MACJ,8BAA8B;MAC9B;MACA;MACA;MACA;MACA;MACA,GAAG;IACL,IAAI;AACJ,UAAM,UAAgB,kBAAW,uBAAuB;AACxD,UAAM,CAAC,MAAM,OAAO,IAAU,gBAAyC,IAAI;AAC3E,UAAM,iBAAgB,6BAAM,mBAAiB,yCAAY;AACzD,UAAM,CAAC,EAAE,KAAK,IAAU,gBAAS,CAAC,CAAC;AACnC,UAAM,eAAe,gBAAgB,cAAc,CAACC,UAAS,QAAQA,KAAI,CAAC;AAC1E,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM;AACxC,UAAM,CAAC,4CAA4C,IAAI,CAAC,GAAG,QAAQ,sCAAsC,EAAE,MAAM,EAAE;AACnH,UAAM,oDAAoD,OAAO,QAAQ,4CAA6C;AACtH,UAAM,QAAQ,OAAO,OAAO,QAAQ,IAAI,IAAI;AAC5C,UAAM,8BAA8B,QAAQ,uCAAuC,OAAO;AAC1F,UAAM,yBAAyB,SAAS;AAExC,UAAM,qBAAqB,sBAAsB,CAAC,UAAU;AAC1D,YAAM,SAAS,MAAM;AACrB,YAAM,wBAAwB,CAAC,GAAG,QAAQ,QAAQ,EAAE,KAAK,CAAC,WAAW,OAAO,SAAS,MAAM,CAAC;AAC5F,UAAI,CAAC,0BAA0B,sBAAuB;AACtD,mEAAuB;AACvB,6DAAoB;AACpB,UAAI,CAAC,MAAM,iBAAkB;IAC/B,GAAG,aAAa;AAEhB,UAAM,eAAe,gBAAgB,CAAC,UAAU;AAC9C,YAAM,SAAS,MAAM;AACrB,YAAM,kBAAkB,CAAC,GAAG,QAAQ,QAAQ,EAAE,KAAK,CAAC,WAAW,OAAO,SAAS,MAAM,CAAC;AACtF,UAAI,gBAAiB;AACrB,uDAAiB;AACjB,6DAAoB;AACpB,UAAI,CAAC,MAAM,iBAAkB;IAC/B,GAAG,aAAa;AAEhB,qBAAiB,CAAC,UAAU;AAC1B,YAAM,iBAAiB,UAAU,QAAQ,OAAO,OAAO;AACvD,UAAI,CAAC,eAAgB;AACrB,yDAAkB;AAClB,UAAI,CAAC,MAAM,oBAAoB,WAAW;AACxC,cAAM,eAAe;AACrB,kBAAU;MACZ;IACF,GAAG,aAAa;AAEV,IAAA,iBAAU,MAAM;AACpB,UAAI,CAAC,KAAM;AACX,UAAI,6BAA6B;AAC/B,YAAI,QAAQ,uCAAuC,SAAS,GAAG;AAC7D,sCAA4B,cAAc,KAAK,MAAM;AACrD,wBAAc,KAAK,MAAM,gBAAgB;QAC3C;AACA,gBAAQ,uCAAuC,IAAI,IAAI;MACzD;AACA,cAAQ,OAAO,IAAI,IAAI;AACvB,qBAAe;AACf,aAAO,MAAM;AACX,YACE,+BACA,QAAQ,uCAAuC,SAAS,GACxD;AACA,wBAAc,KAAK,MAAM,gBAAgB;QAC3C;MACF;IACF,GAAG,CAAC,MAAM,eAAe,6BAA6B,OAAO,CAAC;AAQxD,IAAA,iBAAU,MAAM;AACpB,aAAO,MAAM;AACX,YAAI,CAAC,KAAM;AACX,gBAAQ,OAAO,OAAO,IAAI;AAC1B,gBAAQ,uCAAuC,OAAO,IAAI;AAC1D,uBAAe;MACjB;IACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAEZ,IAAA,iBAAU,MAAM;AACpB,YAAM,eAAe,MAAM,MAAM,CAAC,CAAC;AACnC,eAAS,iBAAiB,gBAAgB,YAAY;AACtD,aAAO,MAAM,SAAS,oBAAoB,gBAAgB,YAAY;IACxE,GAAG,CAAC,CAAC;AAEL,eACE;MAAC,UAAU;MAAV;QACE,GAAG;QACJ,KAAK;QACL,OAAO;UACL,eAAe,8BACX,yBACE,SACA,SACF;UACJ,GAAG,MAAM;QACX;QACA,gBAAgB,qBAAqB,MAAM,gBAAgB,aAAa,cAAc;QACtF,eAAe,qBAAqB,MAAM,eAAe,aAAa,aAAa;QACnF,sBAAsB;UACpB,MAAM;UACN,mBAAmB;QACrB;MAAA;IACF;EAEJ;AACF;AAEA,iBAAiB,cAAc;AAM/B,IAAM,cAAc;AAKpB,IAAM,yBAA+B,kBAGnC,CAAC,OAAO,iBAAiB;AACzB,QAAM,UAAgB,kBAAW,uBAAuB;AACxD,QAAM,MAAY,cAAsC,IAAI;AAC5D,QAAM,eAAe,gBAAgB,cAAc,GAAG;AAEhD,EAAA,iBAAU,MAAM;AACpB,UAAM,OAAO,IAAI;AACjB,QAAI,MAAM;AACR,cAAQ,SAAS,IAAI,IAAI;AACzB,aAAO,MAAM;AACX,gBAAQ,SAAS,OAAO,IAAI;MAC9B;IACF;EACF,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAErB,aAAO,yBAAC,UAAU,KAAV,EAAe,GAAG,OAAO,KAAK,aAAA,CAAc;AACtD,CAAC;AAED,uBAAuB,cAAc;AAYrC,SAAS,sBACP,sBACA,gBAA0B,yCAAY,UACtC;AACA,QAAM,2BAA2B,eAAe,oBAAoB;AACpE,QAAM,8BAAoC,cAAO,KAAK;AACtD,QAAM,iBAAuB,cAAO,MAAM;EAAC,CAAC;AAEtC,EAAA,iBAAU,MAAM;AACpB,UAAM,oBAAoB,CAAC,UAAwB;AACjD,UAAI,MAAM,UAAU,CAAC,4BAA4B,SAAS;AAGxD,YAASC,4CAAT,WAAoD;AAClD;YACE;YACA;YACA;YACA,EAAE,UAAU,KAAK;UACnB;QACF;AAPS,YAAA,2CAAAA;AAFT,cAAM,cAAc,EAAE,eAAe,MAAM;AAuB3C,YAAI,MAAM,gBAAgB,SAAS;AACjC,wBAAc,oBAAoB,SAAS,eAAe,OAAO;AACjE,yBAAe,UAAUA;AACzB,wBAAc,iBAAiB,SAAS,eAAe,SAAS,EAAE,MAAM,KAAK,CAAC;QAChF,OAAO;AACLA,oDAAyC;QAC3C;MACF,OAAO;AAGL,sBAAc,oBAAoB,SAAS,eAAe,OAAO;MACnE;AACA,kCAA4B,UAAU;IACxC;AAcA,UAAM,UAAU,OAAO,WAAW,MAAM;AACtC,oBAAc,iBAAiB,eAAe,iBAAiB;IACjE,GAAG,CAAC;AACJ,WAAO,MAAM;AACX,aAAO,aAAa,OAAO;AAC3B,oBAAc,oBAAoB,eAAe,iBAAiB;AAClE,oBAAc,oBAAoB,SAAS,eAAe,OAAO;IACnE;EACF,GAAG,CAAC,eAAe,wBAAwB,CAAC;AAE5C,SAAO;;IAEL,sBAAsB,MAAO,4BAA4B,UAAU;EACrE;AACF;AAMA,SAAS,gBACP,gBACA,gBAA0B,yCAAY,UACtC;AACA,QAAM,qBAAqB,eAAe,cAAc;AACxD,QAAM,4BAAkC,cAAO,KAAK;AAE9C,EAAA,iBAAU,MAAM;AACpB,UAAM,cAAc,CAAC,UAAsB;AACzC,UAAI,MAAM,UAAU,CAAC,0BAA0B,SAAS;AACtD,cAAM,cAAc,EAAE,eAAe,MAAM;AAC3C,qCAA6B,eAAe,oBAAoB,aAAa;UAC3E,UAAU;QACZ,CAAC;MACH;IACF;AACA,kBAAc,iBAAiB,WAAW,WAAW;AACrD,WAAO,MAAM,cAAc,oBAAoB,WAAW,WAAW;EACvE,GAAG,CAAC,eAAe,kBAAkB,CAAC;AAEtC,SAAO;IACL,gBAAgB,MAAO,0BAA0B,UAAU;IAC3D,eAAe,MAAO,0BAA0B,UAAU;EAC5D;AACF;AAEA,SAAS,iBAAiB;AACxB,QAAM,QAAQ,IAAI,YAAY,cAAc;AAC5C,WAAS,cAAc,KAAK;AAC9B;AAEA,SAAS,6BACP,MACA,SACA,QACA,EAAE,SAAS,GACX;AACA,QAAM,SAAS,OAAO,cAAc;AACpC,QAAM,QAAQ,IAAI,YAAY,MAAM,EAAE,SAAS,OAAO,YAAY,MAAM,OAAO,CAAC;AAChF,MAAI,QAAS,QAAO,iBAAiB,MAAM,SAA0B,EAAE,MAAM,KAAK,CAAC;AAEnF,MAAI,UAAU;AACZ,gCAA4B,QAAQ,KAAK;EAC3C,OAAO;AACL,WAAO,cAAc,KAAK;EAC5B;AACF;AAEA,IAAM,OAAO;AACb,IAAM,SAAS;;;AE9Vf,IAAAC,SAAuB;AASvB,IAAMC,oBAAkB,yCAAY,YAAiB,yBAAkB,MAAM;AAAC;;;ACT9E,IAAAC,SAAuB;AACvB,uBAAqB;AAyBO,IAAAC,sBAAA;AAjB5B,IAAM,cAAc;AAWpB,IAAM,SAAe,kBAAuC,CAAC,OAAO,iBAAiB;;AACnF,QAAM,EAAE,WAAW,eAAe,GAAG,YAAY,IAAI;AACrD,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAS,KAAK;AAClD,mBAAgB,MAAM,WAAW,IAAI,GAAG,CAAC,CAAC;AAC1C,QAAM,YAAY,iBAAkB,aAAW,8CAAY,aAAZ,mBAAsB;AACrE,SAAO,YACH,iBAAAC,QAAS,iBAAa,yBAAC,UAAU,KAAV,EAAe,GAAG,aAAa,KAAK,aAAA,CAAc,GAAI,SAAS,IACtF;AACN,CAAC;AAED,OAAO,cAAc;;;AC9BrB,IAAAC,UAAuB;ACAvB,IAAAC,UAAuB;;;ACEvB,IAAAC,SAAuB;AAKvB,IAAM,sBAAuBA,OAAc,mBAAmB,KAAK,EAAE,SAAS,CAAC;AAC/E,IAAM,0BAA2BA,OAAc,uBAAuB,KAAK,EAAE,SAAS,CAAC;;;AFJvF,IAAM,qBACHC,QAAc,uBAAuB,KAAK,EAAE,SAAS,CAAC,KAAK;AAYvD,SAAS,qBAAwB;EACtC;EACA;EACA,WAAW,MAAM;EAAC;EAClB;AACF,GAAsD;AACpD,QAAM,CAAC,kBAAkB,qBAAqB,WAAW,IAAI,qBAAqB;IAChF;IACA;EACF,CAAC;AACD,QAAM,eAAe,SAAS;AAC9B,QAAM,QAAQ,eAAe,OAAO;AAMpC,MAAI,MAAuC;AACzC,UAAM,kBAAwB,eAAO,SAAS,MAAS;AACjD,IAAA,kBAAU,MAAM;AACpB,YAAM,gBAAgB,gBAAgB;AACtC,UAAI,kBAAkB,cAAc;AAClC,cAAM,OAAO,gBAAgB,eAAe;AAC5C,cAAM,KAAK,eAAe,eAAe;AACzC,gBAAQ;UACN,GAAG,MAAM,qBAAqB,IAAI,OAAO,EAAE;QAC7C;MACF;AACA,sBAAgB,UAAU;IAC5B,GAAG,CAAC,cAAc,MAAM,CAAC;EAC3B;AAGA,QAAM,WAAiB;IACrB,CAAC,cAAc;;AACb,UAAI,cAAc;AAChB,cAAMC,SAAQ,WAAW,SAAS,IAAI,UAAU,IAAI,IAAI;AACxD,YAAIA,WAAU,MAAM;AAClB,4BAAY,YAAZ,qCAAsBA;QACxB;MACF,OAAO;AACL,4BAAoB,SAAS;MAC/B;IACF;IACA,CAAC,cAAc,MAAM,qBAAqB,WAAW;EACvD;AAEA,SAAO,CAAC,OAAO,QAAQ;AACzB;AAEA,SAAS,qBAAwB;EAC/B;EACA;AACF,GAIE;AACA,QAAM,CAAC,OAAO,QAAQ,IAAU,iBAAS,WAAW;AACpD,QAAM,eAAqB,eAAO,KAAK;AAEvC,QAAM,cAAoB,eAAO,QAAQ;AACzC,qBAAmB,MAAM;AACvB,gBAAY,UAAU;EACxB,GAAG,CAAC,QAAQ,CAAC;AAEP,EAAA,kBAAU,MAAM;;AACpB,QAAI,aAAa,YAAY,OAAO;AAClC,wBAAY,YAAZ,qCAAsB;AACtB,mBAAa,UAAU;IACzB;EACF,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,SAAO,CAAC,OAAO,UAAU,WAAW;AACtC;AAEA,SAAS,WAAW,OAAkD;AACpE,SAAO,OAAO,UAAU;AAC1B;AC/EA,IAAM,aAAa,OAAO,kBAAkB;", - "names": ["createContext", "useContext", "createScope", "nextScopes", "React", "React", "import_jsx_runtime", "Slot", "props", "Slottable", "Fragment", "import_jsx_runtime", "Slot", "React", "React", "React", "import_jsx_runtime", "node", "handleAndDispatchPointerDownOutsideEvent", "React", "useLayoutEffect", "React", "import_jsx_runtime", "ReactDOM", "React", "React2", "React", "React", "value"] -} diff --git a/frontend/.vite/deps/chunk-XFRNN5P4.js b/frontend/.vite/deps/chunk-FUQEDP2W.js similarity index 99% rename from frontend/.vite/deps/chunk-XFRNN5P4.js rename to frontend/.vite/deps/chunk-FUQEDP2W.js index 73b92470..816e45cb 100644 --- a/frontend/.vite/deps/chunk-XFRNN5P4.js +++ b/frontend/.vite/deps/chunk-FUQEDP2W.js @@ -2,7 +2,7 @@ import { Primitive, useCallbackRef, useLayoutEffect2 -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { useComposedRefs } from "./chunk-JBZRCD6T.js"; @@ -1113,4 +1113,4 @@ export { Combination_default, hideOthers }; -//# sourceMappingURL=chunk-XFRNN5P4.js.map +//# sourceMappingURL=chunk-FUQEDP2W.js.map diff --git a/frontend/.vite/deps/chunk-XFRNN5P4.js.map b/frontend/.vite/deps/chunk-FUQEDP2W.js.map similarity index 100% rename from frontend/.vite/deps/chunk-XFRNN5P4.js.map rename to frontend/.vite/deps/chunk-FUQEDP2W.js.map diff --git a/frontend/.vite/deps/chunk-46KKQMAN.js b/frontend/.vite/deps/chunk-HNRLCIXP.js similarity index 89% rename from frontend/.vite/deps/chunk-46KKQMAN.js rename to frontend/.vite/deps/chunk-HNRLCIXP.js index 9cb5be60..2673d747 100644 --- a/frontend/.vite/deps/chunk-46KKQMAN.js +++ b/frontend/.vite/deps/chunk-HNRLCIXP.js @@ -2,12 +2,12 @@ import { composeRefs, useComposedRefs } from "./chunk-JBZRCD6T.js"; -import { - require_jsx_runtime -} from "./chunk-6NP5ER6T.js"; import { require_react_dom } from "./chunk-M3HRAD2J.js"; +import { + require_jsx_runtime +} from "./chunk-6NP5ER6T.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; @@ -105,30 +105,113 @@ function composeContextScopes(...scopes) { return createScope; } -// node_modules/@radix-ui/react-primitive/dist/index.mjs +// node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs +var React2 = __toESM(require_react(), 1); +var useLayoutEffect2 = (globalThis == null ? void 0 : globalThis.document) ? React2.useLayoutEffect : () => { +}; + +// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs +var React4 = __toESM(require_react(), 1); +var React22 = __toESM(require_react(), 1); + +// node_modules/@radix-ui/react-use-effect-event/dist/index.mjs var React3 = __toESM(require_react(), 1); +var useReactEffectEvent = React3[" useEffectEvent ".trim().toString()]; +var useReactInsertionEffect = React3[" useInsertionEffect ".trim().toString()]; + +// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs +var useInsertionEffect = React4[" useInsertionEffect ".trim().toString()] || useLayoutEffect2; +function useControllableState({ + prop, + defaultProp, + onChange = () => { + }, + caller +}) { + const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({ + defaultProp, + onChange + }); + const isControlled = prop !== void 0; + const value = isControlled ? prop : uncontrolledProp; + if (true) { + const isControlledRef = React4.useRef(prop !== void 0); + React4.useEffect(() => { + const wasControlled = isControlledRef.current; + if (wasControlled !== isControlled) { + const from = wasControlled ? "controlled" : "uncontrolled"; + const to = isControlled ? "controlled" : "uncontrolled"; + console.warn( + `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.` + ); + } + isControlledRef.current = isControlled; + }, [isControlled, caller]); + } + const setValue = React4.useCallback( + (nextValue) => { + var _a; + if (isControlled) { + const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue; + if (value2 !== prop) { + (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, value2); + } + } else { + setUncontrolledProp(nextValue); + } + }, + [isControlled, prop, setUncontrolledProp, onChangeRef] + ); + return [value, setValue]; +} +function useUncontrolledState({ + defaultProp, + onChange +}) { + const [value, setValue] = React4.useState(defaultProp); + const prevValueRef = React4.useRef(value); + const onChangeRef = React4.useRef(onChange); + useInsertionEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + React4.useEffect(() => { + var _a; + if (prevValueRef.current !== value) { + (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, value); + prevValueRef.current = value; + } + }, [value, prevValueRef]); + return [value, setValue, onChangeRef]; +} +function isFunction(value) { + return typeof value === "function"; +} +var SYNC_STATE = Symbol("RADIX:SYNC_STATE"); + +// node_modules/@radix-ui/react-primitive/dist/index.mjs +var React6 = __toESM(require_react(), 1); var ReactDOM = __toESM(require_react_dom(), 1); // node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs -var React2 = __toESM(require_react(), 1); +var React5 = __toESM(require_react(), 1); var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1); function createSlot(ownerName) { const SlotClone = createSlotClone(ownerName); - const Slot2 = React2.forwardRef((props, forwardedRef) => { + const Slot2 = React5.forwardRef((props, forwardedRef) => { const { children, ...slotProps } = props; - const childrenArray = React2.Children.toArray(children); + const childrenArray = React5.Children.toArray(children); const slottable = childrenArray.find(isSlottable); if (slottable) { const newElement = slottable.props.children; const newChildren = childrenArray.map((child) => { if (child === slottable) { - if (React2.Children.count(newElement) > 1) return React2.Children.only(null); - return React2.isValidElement(newElement) ? newElement.props.children : null; + if (React5.Children.count(newElement) > 1) return React5.Children.only(null); + return React5.isValidElement(newElement) ? newElement.props.children : null; } else { return child; } }); - return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React2.isValidElement(newElement) ? React2.cloneElement(newElement, void 0, newChildren) : null }); + return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React5.isValidElement(newElement) ? React5.cloneElement(newElement, void 0, newChildren) : null }); } return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children }); }); @@ -137,17 +220,17 @@ function createSlot(ownerName) { } var Slot = createSlot("Slot"); function createSlotClone(ownerName) { - const SlotClone = React2.forwardRef((props, forwardedRef) => { + const SlotClone = React5.forwardRef((props, forwardedRef) => { const { children, ...slotProps } = props; - if (React2.isValidElement(children)) { + if (React5.isValidElement(children)) { const childrenRef = getElementRef(children); const props2 = mergeProps(slotProps, children.props); - if (children.type !== React2.Fragment) { + if (children.type !== React5.Fragment) { props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef; } - return React2.cloneElement(children, props2); + return React5.cloneElement(children, props2); } - return React2.Children.count(children) > 1 ? React2.Children.only(null) : null; + return React5.Children.count(children) > 1 ? React5.Children.only(null) : null; }); SlotClone.displayName = `${ownerName}.SlotClone`; return SlotClone; @@ -163,7 +246,7 @@ function createSlottable(ownerName) { } var Slottable = createSlottable("Slottable"); function isSlottable(child) { - return React2.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER; + return React5.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER; } function mergeProps(slotProps, childProps) { const overrideProps = { ...childProps }; @@ -227,7 +310,7 @@ var NODES = [ ]; var Primitive = NODES.reduce((primitive, node) => { const Slot2 = createSlot(`Primitive.${node}`); - const Node = React3.forwardRef((props, forwardedRef) => { + const Node = React6.forwardRef((props, forwardedRef) => { const { asChild, ...primitiveProps } = props; const Comp = asChild ? Slot2 : node; if (typeof window !== "undefined") { @@ -243,26 +326,26 @@ function dispatchDiscreteCustomEvent(target, event) { } // node_modules/@radix-ui/react-use-callback-ref/dist/index.mjs -var React4 = __toESM(require_react(), 1); +var React7 = __toESM(require_react(), 1); function useCallbackRef(callback) { - const callbackRef = React4.useRef(callback); - React4.useEffect(() => { + const callbackRef = React7.useRef(callback); + React7.useEffect(() => { callbackRef.current = callback; }); - return React4.useMemo(() => (...args) => { + return React7.useMemo(() => (...args) => { var _a; return (_a = callbackRef.current) == null ? void 0 : _a.call(callbackRef, ...args); }, []); } // node_modules/@radix-ui/react-dismissable-layer/dist/index.mjs -var React6 = __toESM(require_react(), 1); +var React9 = __toESM(require_react(), 1); // node_modules/@radix-ui/react-use-escape-keydown/dist/index.mjs -var React5 = __toESM(require_react(), 1); +var React8 = __toESM(require_react(), 1); function useEscapeKeydown(onEscapeKeyDownProp, ownerDocument = globalThis == null ? void 0 : globalThis.document) { const onEscapeKeyDown = useCallbackRef(onEscapeKeyDownProp); - React5.useEffect(() => { + React8.useEffect(() => { const handleKeyDown = (event) => { if (event.key === "Escape") { onEscapeKeyDown(event); @@ -280,12 +363,12 @@ var CONTEXT_UPDATE = "dismissableLayer.update"; var POINTER_DOWN_OUTSIDE = "dismissableLayer.pointerDownOutside"; var FOCUS_OUTSIDE = "dismissableLayer.focusOutside"; var originalBodyPointerEvents; -var DismissableLayerContext = React6.createContext({ +var DismissableLayerContext = React9.createContext({ layers: /* @__PURE__ */ new Set(), layersWithOutsidePointerEventsDisabled: /* @__PURE__ */ new Set(), branches: /* @__PURE__ */ new Set() }); -var DismissableLayer = React6.forwardRef( +var DismissableLayer = React9.forwardRef( (props, forwardedRef) => { const { disableOutsidePointerEvents = false, @@ -296,10 +379,10 @@ var DismissableLayer = React6.forwardRef( onDismiss, ...layerProps } = props; - const context = React6.useContext(DismissableLayerContext); - const [node, setNode] = React6.useState(null); + const context = React9.useContext(DismissableLayerContext); + const [node, setNode] = React9.useState(null); const ownerDocument = (node == null ? void 0 : node.ownerDocument) ?? (globalThis == null ? void 0 : globalThis.document); - const [, force] = React6.useState({}); + const [, force] = React9.useState({}); const composedRefs = useComposedRefs(forwardedRef, (node2) => setNode(node2)); const layers = Array.from(context.layers); const [highestLayerWithOutsidePointerEventsDisabled] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1); @@ -332,7 +415,7 @@ var DismissableLayer = React6.forwardRef( onDismiss(); } }, ownerDocument); - React6.useEffect(() => { + React9.useEffect(() => { if (!node) return; if (disableOutsidePointerEvents) { if (context.layersWithOutsidePointerEventsDisabled.size === 0) { @@ -349,7 +432,7 @@ var DismissableLayer = React6.forwardRef( } }; }, [node, ownerDocument, disableOutsidePointerEvents, context]); - React6.useEffect(() => { + React9.useEffect(() => { return () => { if (!node) return; context.layers.delete(node); @@ -357,7 +440,7 @@ var DismissableLayer = React6.forwardRef( dispatchUpdate(); }; }, [node, context]); - React6.useEffect(() => { + React9.useEffect(() => { const handleUpdate = () => force({}); document.addEventListener(CONTEXT_UPDATE, handleUpdate); return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate); @@ -383,11 +466,11 @@ var DismissableLayer = React6.forwardRef( ); DismissableLayer.displayName = DISMISSABLE_LAYER_NAME; var BRANCH_NAME = "DismissableLayerBranch"; -var DismissableLayerBranch = React6.forwardRef((props, forwardedRef) => { - const context = React6.useContext(DismissableLayerContext); - const ref = React6.useRef(null); +var DismissableLayerBranch = React9.forwardRef((props, forwardedRef) => { + const context = React9.useContext(DismissableLayerContext); + const ref = React9.useRef(null); const composedRefs = useComposedRefs(forwardedRef, ref); - React6.useEffect(() => { + React9.useEffect(() => { const node = ref.current; if (node) { context.branches.add(node); @@ -401,10 +484,10 @@ var DismissableLayerBranch = React6.forwardRef((props, forwardedRef) => { DismissableLayerBranch.displayName = BRANCH_NAME; function usePointerDownOutside(onPointerDownOutside, ownerDocument = globalThis == null ? void 0 : globalThis.document) { const handlePointerDownOutside = useCallbackRef(onPointerDownOutside); - const isPointerInsideReactTreeRef = React6.useRef(false); - const handleClickRef = React6.useRef(() => { + const isPointerInsideReactTreeRef = React9.useRef(false); + const handleClickRef = React9.useRef(() => { }); - React6.useEffect(() => { + React9.useEffect(() => { const handlePointerDown = (event) => { if (event.target && !isPointerInsideReactTreeRef.current) { let handleAndDispatchPointerDownOutsideEvent2 = function() { @@ -445,8 +528,8 @@ function usePointerDownOutside(onPointerDownOutside, ownerDocument = globalThis } function useFocusOutside(onFocusOutside, ownerDocument = globalThis == null ? void 0 : globalThis.document) { const handleFocusOutside = useCallbackRef(onFocusOutside); - const isFocusInsideReactTreeRef = React6.useRef(false); - React6.useEffect(() => { + const isFocusInsideReactTreeRef = React9.useRef(false); + React9.useEffect(() => { const handleFocus = (event) => { if (event.target && !isFocusInsideReactTreeRef.current) { const eventDetail = { originalEvent: event }; @@ -480,116 +563,33 @@ function handleAndDispatchCustomEvent(name, handler, detail, { discrete }) { var Root = DismissableLayer; var Branch = DismissableLayerBranch; -// node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs -var React7 = __toESM(require_react(), 1); -var useLayoutEffect2 = (globalThis == null ? void 0 : globalThis.document) ? React7.useLayoutEffect : () => { -}; - // node_modules/@radix-ui/react-portal/dist/index.mjs -var React8 = __toESM(require_react(), 1); +var React10 = __toESM(require_react(), 1); var import_react_dom = __toESM(require_react_dom(), 1); var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1); var PORTAL_NAME = "Portal"; -var Portal = React8.forwardRef((props, forwardedRef) => { +var Portal = React10.forwardRef((props, forwardedRef) => { var _a; const { container: containerProp, ...portalProps } = props; - const [mounted, setMounted] = React8.useState(false); + const [mounted, setMounted] = React10.useState(false); useLayoutEffect2(() => setMounted(true), []); const container = containerProp || mounted && ((_a = globalThis == null ? void 0 : globalThis.document) == null ? void 0 : _a.body); return container ? import_react_dom.default.createPortal((0, import_jsx_runtime5.jsx)(Primitive.div, { ...portalProps, ref: forwardedRef }), container) : null; }); Portal.displayName = PORTAL_NAME; -// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs -var React10 = __toESM(require_react(), 1); -var React22 = __toESM(require_react(), 1); - -// node_modules/@radix-ui/react-use-effect-event/dist/index.mjs -var React9 = __toESM(require_react(), 1); -var useReactEffectEvent = React9[" useEffectEvent ".trim().toString()]; -var useReactInsertionEffect = React9[" useInsertionEffect ".trim().toString()]; - -// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs -var useInsertionEffect = React10[" useInsertionEffect ".trim().toString()] || useLayoutEffect2; -function useControllableState({ - prop, - defaultProp, - onChange = () => { - }, - caller -}) { - const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({ - defaultProp, - onChange - }); - const isControlled = prop !== void 0; - const value = isControlled ? prop : uncontrolledProp; - if (true) { - const isControlledRef = React10.useRef(prop !== void 0); - React10.useEffect(() => { - const wasControlled = isControlledRef.current; - if (wasControlled !== isControlled) { - const from = wasControlled ? "controlled" : "uncontrolled"; - const to = isControlled ? "controlled" : "uncontrolled"; - console.warn( - `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.` - ); - } - isControlledRef.current = isControlled; - }, [isControlled, caller]); - } - const setValue = React10.useCallback( - (nextValue) => { - var _a; - if (isControlled) { - const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue; - if (value2 !== prop) { - (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, value2); - } - } else { - setUncontrolledProp(nextValue); - } - }, - [isControlled, prop, setUncontrolledProp, onChangeRef] - ); - return [value, setValue]; -} -function useUncontrolledState({ - defaultProp, - onChange -}) { - const [value, setValue] = React10.useState(defaultProp); - const prevValueRef = React10.useRef(value); - const onChangeRef = React10.useRef(onChange); - useInsertionEffect(() => { - onChangeRef.current = onChange; - }, [onChange]); - React10.useEffect(() => { - var _a; - if (prevValueRef.current !== value) { - (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, value); - prevValueRef.current = value; - } - }, [value, prevValueRef]); - return [value, setValue, onChangeRef]; -} -function isFunction(value) { - return typeof value === "function"; -} -var SYNC_STATE = Symbol("RADIX:SYNC_STATE"); - export { composeEventHandlers, createContext2, createContextScope, + useLayoutEffect2, + useControllableState, Primitive, dispatchDiscreteCustomEvent, useCallbackRef, DismissableLayer, Root, Branch, - useLayoutEffect2, - Portal, - useControllableState + Portal }; -//# sourceMappingURL=chunk-46KKQMAN.js.map +//# sourceMappingURL=chunk-HNRLCIXP.js.map diff --git a/frontend/.vite/deps/chunk-HNRLCIXP.js.map b/frontend/.vite/deps/chunk-HNRLCIXP.js.map new file mode 100644 index 00000000..8edd22f9 --- /dev/null +++ b/frontend/.vite/deps/chunk-HNRLCIXP.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../../node_modules/@radix-ui/primitive/src/primitive.tsx", "../../../node_modules/@radix-ui/react-context/src/create-context.tsx", "../../../node_modules/@radix-ui/react-use-layout-effect/src/use-layout-effect.tsx", "../../../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state.tsx", "../../../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state-reducer.tsx", "../../../node_modules/@radix-ui/react-use-effect-event/src/use-effect-event.tsx", "../../../node_modules/@radix-ui/react-primitive/src/primitive.tsx", "../../../node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/src/slot.tsx", "../../../node_modules/@radix-ui/react-use-callback-ref/src/use-callback-ref.tsx", "../../../node_modules/@radix-ui/react-dismissable-layer/src/dismissable-layer.tsx", "../../../node_modules/@radix-ui/react-use-escape-keydown/src/use-escape-keydown.tsx", "../../../node_modules/@radix-ui/react-portal/src/portal.tsx"], + "sourcesContent": ["/* eslint-disable no-restricted-properties */\n\n/* eslint-disable no-restricted-globals */\nexport const canUseDOM = !!(\n typeof window !== 'undefined' &&\n window.document &&\n window.document.createElement\n);\n/* eslint-enable no-restricted-globals */\n\nexport function composeEventHandlers(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n { checkForDefaultPrevented = true } = {}\n) {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n\n if (checkForDefaultPrevented === false || !event.defaultPrevented) {\n return ourEventHandler?.(event);\n }\n };\n}\n\nexport function getOwnerWindow(element: Node | null | undefined) {\n if (!canUseDOM) {\n throw new Error('Cannot access window outside of the DOM');\n }\n // eslint-disable-next-line no-restricted-globals\n return element?.ownerDocument?.defaultView ?? window;\n}\n\nexport function getOwnerDocument(element: Node | null | undefined) {\n if (!canUseDOM) {\n throw new Error('Cannot access document outside of the DOM');\n }\n // eslint-disable-next-line no-restricted-globals\n return element?.ownerDocument ?? document;\n}\n\n/**\n * Lifted from https://github.com/ariakit/ariakit/blob/main/packages/ariakit-core/src/utils/dom.ts#L37\n * MIT License, Copyright (c) AriaKit.\n */\nexport function getActiveElement(\n node: Node | null | undefined,\n activeDescendant = false\n): HTMLElement | null {\n const { activeElement } = getOwnerDocument(node);\n if (!activeElement?.nodeName) {\n // `activeElement` might be an empty object if we're interacting with elements\n // inside of an iframe.\n return null;\n }\n\n if (isFrame(activeElement) && activeElement.contentDocument) {\n return getActiveElement(activeElement.contentDocument.body, activeDescendant);\n }\n\n if (activeDescendant) {\n const id = activeElement.getAttribute('aria-activedescendant');\n if (id) {\n const element = getOwnerDocument(activeElement).getElementById(id);\n if (element) {\n return element;\n }\n }\n }\n\n return activeElement as HTMLElement | null;\n}\n\nexport function isFrame(element: Element): element is HTMLIFrameElement {\n return element.tagName === 'IFRAME';\n}\n", "import * as React from 'react';\n\nfunction createContext(\n rootComponentName: string,\n defaultContext?: ContextValueType\n) {\n const Context = React.createContext(defaultContext);\n\n const Provider: React.FC = (props) => {\n const { children, ...context } = props;\n // Only re-memoize when prop values change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;\n return {children};\n };\n\n Provider.displayName = rootComponentName + 'Provider';\n\n function useContext(consumerName: string) {\n const context = React.useContext(Context);\n if (context) return context;\n if (defaultContext !== undefined) return defaultContext;\n // if a defaultContext wasn't specified, it's a required context.\n throw new Error(`\\`${consumerName}\\` must be used within \\`${rootComponentName}\\``);\n }\n\n return [Provider, useContext] as const;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * createContextScope\n * -----------------------------------------------------------------------------------------------*/\n\ntype Scope = { [scopeName: string]: React.Context[] } | undefined;\ntype ScopeHook = (scope: Scope) => { [__scopeProp: string]: Scope };\ninterface CreateScope {\n scopeName: string;\n (): ScopeHook;\n}\n\nfunction createContextScope(scopeName: string, createContextScopeDeps: CreateScope[] = []) {\n let defaultContexts: any[] = [];\n\n /* -----------------------------------------------------------------------------------------------\n * createContext\n * ---------------------------------------------------------------------------------------------*/\n\n function createContext(\n rootComponentName: string,\n defaultContext?: ContextValueType\n ) {\n const BaseContext = React.createContext(defaultContext);\n const index = defaultContexts.length;\n defaultContexts = [...defaultContexts, defaultContext];\n\n const Provider: React.FC<\n ContextValueType & { scope: Scope; children: React.ReactNode }\n > = (props) => {\n const { scope, children, ...context } = props;\n const Context = scope?.[scopeName]?.[index] || BaseContext;\n // Only re-memoize when prop values change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;\n return {children};\n };\n\n Provider.displayName = rootComponentName + 'Provider';\n\n function useContext(consumerName: string, scope: Scope) {\n const Context = scope?.[scopeName]?.[index] || BaseContext;\n const context = React.useContext(Context);\n if (context) return context;\n if (defaultContext !== undefined) return defaultContext;\n // if a defaultContext wasn't specified, it's a required context.\n throw new Error(`\\`${consumerName}\\` must be used within \\`${rootComponentName}\\``);\n }\n\n return [Provider, useContext] as const;\n }\n\n /* -----------------------------------------------------------------------------------------------\n * createScope\n * ---------------------------------------------------------------------------------------------*/\n\n const createScope: CreateScope = () => {\n const scopeContexts = defaultContexts.map((defaultContext) => {\n return React.createContext(defaultContext);\n });\n return function useScope(scope: Scope) {\n const contexts = scope?.[scopeName] || scopeContexts;\n return React.useMemo(\n () => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: contexts } }),\n [scope, contexts]\n );\n };\n };\n\n createScope.scopeName = scopeName;\n return [createContext, composeContextScopes(createScope, ...createContextScopeDeps)] as const;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * composeContextScopes\n * -----------------------------------------------------------------------------------------------*/\n\nfunction composeContextScopes(...scopes: CreateScope[]) {\n const baseScope = scopes[0];\n if (scopes.length === 1) return baseScope;\n\n const createScope: CreateScope = () => {\n const scopeHooks = scopes.map((createScope) => ({\n useScope: createScope(),\n scopeName: createScope.scopeName,\n }));\n\n return function useComposedScopes(overrideScopes) {\n const nextScopes = scopeHooks.reduce((nextScopes, { useScope, scopeName }) => {\n // We are calling a hook inside a callback which React warns against to avoid inconsistent\n // renders, however, scoping doesn't have render side effects so we ignore the rule.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const scopeProps = useScope(overrideScopes);\n const currentScope = scopeProps[`__scope${scopeName}`];\n return { ...nextScopes, ...currentScope };\n }, {});\n\n return React.useMemo(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }), [nextScopes]);\n };\n };\n\n createScope.scopeName = baseScope.scopeName;\n return createScope;\n}\n\n/* -----------------------------------------------------------------------------------------------*/\n\nexport { createContext, createContextScope };\nexport type { CreateScope, Scope };\n", "import * as React from 'react';\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\nexport { useLayoutEffect };\n", "import * as React from 'react';\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n (React as any)[' useInsertionEffect '.trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler = (state: T) => void;\ntype SetStateFn = React.Dispatch>;\n\ninterface UseControllableStateParams {\n prop?: T | undefined;\n defaultProp: T;\n onChange?: ChangeHandler;\n caller?: string;\n}\n\nexport function useControllableState({\n prop,\n defaultProp,\n onChange = () => {},\n caller,\n}: UseControllableStateParams): [T, SetStateFn] {\n const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(prop !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n const setValue = React.useCallback>(\n (nextValue) => {\n if (isControlled) {\n const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n if (value !== prop) {\n onChangeRef.current?.(value);\n }\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, onChangeRef]\n );\n\n return [value, setValue];\n}\n\nfunction useUncontrolledState({\n defaultProp,\n onChange,\n}: Omit, 'prop'>): [\n Value: T,\n setValue: React.Dispatch>,\n OnChangeRef: React.RefObject | undefined>,\n] {\n const [value, setValue] = React.useState(defaultProp);\n const prevValueRef = React.useRef(value);\n\n const onChangeRef = React.useRef(onChange);\n useInsertionEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n if (prevValueRef.current !== value) {\n onChangeRef.current?.(value);\n prevValueRef.current = value;\n }\n }, [value, prevValueRef]);\n\n return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "import * as React from 'react';\nimport { useEffectEvent } from '@radix-ui/react-use-effect-event';\n\ntype ChangeHandler = (state: T) => void;\n\ninterface UseControllableStateParams {\n prop: T | undefined;\n defaultProp: T;\n onChange: ChangeHandler | undefined;\n caller: string;\n}\n\ninterface AnyAction {\n type: string;\n}\n\nconst SYNC_STATE = Symbol('RADIX:SYNC_STATE');\n\ninterface SyncStateAction {\n type: typeof SYNC_STATE;\n state: T;\n}\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialState: S\n): [S & { state: T }, React.Dispatch];\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialArg: I,\n init: (i: I & { state: T }) => S\n): [S & { state: T }, React.Dispatch];\n\nexport function useControllableStateReducer(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams,\n initialArg: any,\n init?: (i: any) => Omit\n): [S & { state: T }, React.Dispatch] {\n const { prop: controlledState, defaultProp, onChange: onChangeProp, caller } = userArgs;\n const isControlled = controlledState !== undefined;\n\n const onChange = useEffectEvent(onChangeProp);\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(controlledState !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n type InternalState = S & { state: T };\n const args: [InternalState] = [{ ...initialArg, state: defaultProp }];\n if (init) {\n // @ts-expect-error\n args.push(init);\n }\n\n const [internalState, dispatch] = React.useReducer(\n (state: InternalState, action: A | SyncStateAction): InternalState => {\n if (action.type === SYNC_STATE) {\n return { ...state, state: action.state };\n }\n\n const next = reducer(state, action);\n if (isControlled && !Object.is(next.state, state.state)) {\n onChange(next.state);\n }\n return next;\n },\n ...args\n );\n\n const uncontrolledState = internalState.state;\n const prevValueRef = React.useRef(uncontrolledState);\n React.useEffect(() => {\n if (prevValueRef.current !== uncontrolledState) {\n prevValueRef.current = uncontrolledState;\n if (!isControlled) {\n onChange(uncontrolledState);\n }\n }\n }, [onChange, uncontrolledState, prevValueRef, isControlled]);\n\n const state = React.useMemo(() => {\n const isControlled = controlledState !== undefined;\n if (isControlled) {\n return { ...internalState, state: controlledState };\n }\n\n return internalState;\n }, [internalState, controlledState]);\n\n React.useEffect(() => {\n // Sync internal state for controlled components so that reducer is called\n // with the correct state values\n if (isControlled && !Object.is(controlledState, internalState.state)) {\n dispatch({ type: SYNC_STATE, state: controlledState });\n }\n }, [controlledState, internalState.state, isControlled]);\n\n return [state, dispatch as React.Dispatch];\n}\n", "/* eslint-disable react-hooks/rules-of-hooks */\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\nimport * as React from 'react';\n\ntype AnyFunction = (...args: any[]) => any;\n\n// See https://github.com/webpack/webpack/issues/14814\nconst useReactEffectEvent = (React as any)[' useEffectEvent '.trim().toString()];\nconst useReactInsertionEffect = (React as any)[' useInsertionEffect '.trim().toString()];\n\n/**\n * Designed to approximate the behavior on `experimental_useEffectEvent` as best\n * as possible until its stable release, and back-fill it as a shim as needed.\n */\nexport function useEffectEvent(callback?: T): T {\n if (typeof useReactEffectEvent === 'function') {\n return useReactEffectEvent(callback);\n }\n\n const ref = React.useRef(() => {\n throw new Error('Cannot call an event handler while rendering.');\n });\n // See https://github.com/webpack/webpack/issues/14814\n if (typeof useReactInsertionEffect === 'function') {\n useReactInsertionEffect(() => {\n ref.current = callback;\n });\n } else {\n useLayoutEffect(() => {\n ref.current = callback;\n });\n }\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => ref.current?.(...args)) as T, []);\n}\n", "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { createSlot } from '@radix-ui/react-slot';\n\nconst NODES = [\n 'a',\n 'button',\n 'div',\n 'form',\n 'h2',\n 'h3',\n 'img',\n 'input',\n 'label',\n 'li',\n 'nav',\n 'ol',\n 'p',\n 'select',\n 'span',\n 'svg',\n 'ul',\n] as const;\n\ntype Primitives = { [E in (typeof NODES)[number]]: PrimitiveForwardRefComponent };\ntype PrimitivePropsWithRef = React.ComponentPropsWithRef & {\n asChild?: boolean;\n};\n\ninterface PrimitiveForwardRefComponent\n extends React.ForwardRefExoticComponent> {}\n\n/* -------------------------------------------------------------------------------------------------\n * Primitive\n * -----------------------------------------------------------------------------------------------*/\n\nconst Primitive = NODES.reduce((primitive, node) => {\n const Slot = createSlot(`Primitive.${node}`);\n const Node = React.forwardRef((props: PrimitivePropsWithRef, forwardedRef: any) => {\n const { asChild, ...primitiveProps } = props;\n const Comp: any = asChild ? Slot : node;\n\n if (typeof window !== 'undefined') {\n (window as any)[Symbol.for('radix-ui')] = true;\n }\n\n return ;\n });\n\n Node.displayName = `Primitive.${node}`;\n\n return { ...primitive, [node]: Node };\n}, {} as Primitives);\n\n/* -------------------------------------------------------------------------------------------------\n * Utils\n * -----------------------------------------------------------------------------------------------*/\n\n/**\n * Flush custom event dispatch\n * https://github.com/radix-ui/primitives/pull/1378\n *\n * React batches *all* event handlers since version 18, this introduces certain considerations when using custom event types.\n *\n * Internally, React prioritises events in the following order:\n * - discrete\n * - continuous\n * - default\n *\n * https://github.com/facebook/react/blob/a8a4742f1c54493df00da648a3f9d26e3db9c8b5/packages/react-dom/src/events/ReactDOMEventListener.js#L294-L350\n *\n * `discrete` is an important distinction as updates within these events are applied immediately.\n * React however, is not able to infer the priority of custom event types due to how they are detected internally.\n * Because of this, it's possible for updates from custom events to be unexpectedly batched when\n * dispatched by another `discrete` event.\n *\n * In order to ensure that updates from custom events are applied predictably, we need to manually flush the batch.\n * This utility should be used when dispatching a custom event from within another `discrete` event, this utility\n * is not necessary when dispatching known event types, or if dispatching a custom type inside a non-discrete event.\n * For example:\n *\n * dispatching a known click \uD83D\uDC4E\n * target.dispatchEvent(new Event(\u2018click\u2019))\n *\n * dispatching a custom type within a non-discrete event \uD83D\uDC4E\n * onScroll={(event) => event.target.dispatchEvent(new CustomEvent(\u2018customType\u2019))}\n *\n * dispatching a custom type within a `discrete` event \uD83D\uDC4D\n * onPointerDown={(event) => dispatchDiscreteCustomEvent(event.target, new CustomEvent(\u2018customType\u2019))}\n *\n * Note: though React classifies `focus`, `focusin` and `focusout` events as `discrete`, it's not recommended to use\n * this utility with them. This is because it's possible for those handlers to be called implicitly during render\n * e.g. when focus is within a component as it is unmounted, or when managing focus on mount.\n */\n\nfunction dispatchDiscreteCustomEvent(target: E['target'], event: E) {\n if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));\n}\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = Primitive;\n\nexport {\n Primitive,\n //\n Root,\n //\n dispatchDiscreteCustomEvent,\n};\nexport type { PrimitivePropsWithRef };\n", "import * as React from 'react';\nimport { composeRefs } from '@radix-ui/react-compose-refs';\n\n/* -------------------------------------------------------------------------------------------------\n * Slot\n * -----------------------------------------------------------------------------------------------*/\n\ninterface SlotProps extends React.HTMLAttributes {\n children?: React.ReactNode;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ export function createSlot(ownerName: string) {\n const SlotClone = createSlotClone(ownerName);\n const Slot = React.forwardRef((props, forwardedRef) => {\n const { children, ...slotProps } = props;\n const childrenArray = React.Children.toArray(children);\n const slottable = childrenArray.find(isSlottable);\n\n if (slottable) {\n // the new element to render is the one passed as a child of `Slottable`\n const newElement = slottable.props.children;\n\n const newChildren = childrenArray.map((child) => {\n if (child === slottable) {\n // because the new element will be the one rendered, we are only interested\n // in grabbing its children (`newElement.props.children`)\n if (React.Children.count(newElement) > 1) return React.Children.only(null);\n return React.isValidElement(newElement)\n ? (newElement.props as { children: React.ReactNode }).children\n : null;\n } else {\n return child;\n }\n });\n\n return (\n \n {React.isValidElement(newElement)\n ? React.cloneElement(newElement, undefined, newChildren)\n : null}\n \n );\n }\n\n return (\n \n {children}\n \n );\n });\n\n Slot.displayName = `${ownerName}.Slot`;\n return Slot;\n}\n\nconst Slot = createSlot('Slot');\n\n/* -------------------------------------------------------------------------------------------------\n * SlotClone\n * -----------------------------------------------------------------------------------------------*/\n\ninterface SlotCloneProps {\n children: React.ReactNode;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ function createSlotClone(ownerName: string) {\n const SlotClone = React.forwardRef((props, forwardedRef) => {\n const { children, ...slotProps } = props;\n\n if (React.isValidElement(children)) {\n const childrenRef = getElementRef(children);\n const props = mergeProps(slotProps, children.props as AnyProps);\n // do not pass ref to React.Fragment for React 19 compatibility\n if (children.type !== React.Fragment) {\n props.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;\n }\n return React.cloneElement(children, props);\n }\n\n return React.Children.count(children) > 1 ? React.Children.only(null) : null;\n });\n\n SlotClone.displayName = `${ownerName}.SlotClone`;\n return SlotClone;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Slottable\n * -----------------------------------------------------------------------------------------------*/\n\nconst SLOTTABLE_IDENTIFIER = Symbol('radix.slottable');\n\ninterface SlottableProps {\n children: React.ReactNode;\n}\n\ninterface SlottableComponent extends React.FC {\n __radixId: symbol;\n}\n\n/* @__NO_SIDE_EFFECTS__ */ export function createSlottable(ownerName: string) {\n const Slottable: SlottableComponent = ({ children }) => {\n return <>{children};\n };\n Slottable.displayName = `${ownerName}.Slottable`;\n Slottable.__radixId = SLOTTABLE_IDENTIFIER;\n return Slottable;\n}\n\nconst Slottable = createSlottable('Slottable');\n\n/* ---------------------------------------------------------------------------------------------- */\n\ntype AnyProps = Record;\n\nfunction isSlottable(\n child: React.ReactNode\n): child is React.ReactElement {\n return (\n React.isValidElement(child) &&\n typeof child.type === 'function' &&\n '__radixId' in child.type &&\n child.type.__radixId === SLOTTABLE_IDENTIFIER\n );\n}\n\nfunction mergeProps(slotProps: AnyProps, childProps: AnyProps) {\n // all child props should override\n const overrideProps = { ...childProps };\n\n for (const propName in childProps) {\n const slotPropValue = slotProps[propName];\n const childPropValue = childProps[propName];\n\n const isHandler = /^on[A-Z]/.test(propName);\n if (isHandler) {\n // if the handler exists on both, we compose them\n if (slotPropValue && childPropValue) {\n overrideProps[propName] = (...args: unknown[]) => {\n const result = childPropValue(...args);\n slotPropValue(...args);\n return result;\n };\n }\n // but if it exists only on the slot, we use only this one\n else if (slotPropValue) {\n overrideProps[propName] = slotPropValue;\n }\n }\n // if it's `style`, we merge them\n else if (propName === 'style') {\n overrideProps[propName] = { ...slotPropValue, ...childPropValue };\n } else if (propName === 'className') {\n overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(' ');\n }\n }\n\n return { ...slotProps, ...overrideProps };\n}\n\n// Before React 19 accessing `element.props.ref` will throw a warning and suggest using `element.ref`\n// After React 19 accessing `element.ref` does the opposite.\n// https://github.com/facebook/react/pull/28348\n//\n// Access the ref using the method that doesn't yield a warning.\nfunction getElementRef(element: React.ReactElement) {\n // React <=18 in DEV\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get;\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return (element as any).ref;\n }\n\n // React 19 in DEV\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get;\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return (element.props as { ref?: React.Ref }).ref;\n }\n\n // Not DEV\n return (element.props as { ref?: React.Ref }).ref || (element as any).ref;\n}\n\nexport {\n Slot,\n Slottable,\n //\n Slot as Root,\n};\nexport type { SlotProps };\n", "import * as React from 'react';\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef any>(callback: T | undefined): T {\n const callbackRef = React.useRef(callback);\n\n React.useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useCallbackRef };\n", "import * as React from 'react';\nimport { composeEventHandlers } from '@radix-ui/primitive';\nimport { Primitive, dispatchDiscreteCustomEvent } from '@radix-ui/react-primitive';\nimport { useComposedRefs } from '@radix-ui/react-compose-refs';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\nimport { useEscapeKeydown } from '@radix-ui/react-use-escape-keydown';\n\n/* -------------------------------------------------------------------------------------------------\n * DismissableLayer\n * -----------------------------------------------------------------------------------------------*/\n\nconst DISMISSABLE_LAYER_NAME = 'DismissableLayer';\nconst CONTEXT_UPDATE = 'dismissableLayer.update';\nconst POINTER_DOWN_OUTSIDE = 'dismissableLayer.pointerDownOutside';\nconst FOCUS_OUTSIDE = 'dismissableLayer.focusOutside';\n\nlet originalBodyPointerEvents: string;\n\nconst DismissableLayerContext = React.createContext({\n layers: new Set(),\n layersWithOutsidePointerEventsDisabled: new Set(),\n branches: new Set(),\n});\n\ntype DismissableLayerElement = React.ComponentRef;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef;\ninterface DismissableLayerProps extends PrimitiveDivProps {\n /**\n * When `true`, hover/focus/click interactions will be disabled on elements outside\n * the `DismissableLayer`. Users will need to click twice on outside elements to\n * interact with them: once to close the `DismissableLayer`, and again to trigger the element.\n */\n disableOutsidePointerEvents?: boolean;\n /**\n * Event handler called when the escape key is down.\n * Can be prevented.\n */\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n /**\n * Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`.\n * Can be prevented.\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\n /**\n * Event handler called when the focus moves outside of the `DismissableLayer`.\n * Can be prevented.\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void;\n /**\n * Event handler called when an interaction happens outside the `DismissableLayer`.\n * Specifically, when a `pointerdown` event happens outside or focus moves outside of it.\n * Can be prevented.\n */\n onInteractOutside?: (event: PointerDownOutsideEvent | FocusOutsideEvent) => void;\n /**\n * Handler called when the `DismissableLayer` should be dismissed\n */\n onDismiss?: () => void;\n}\n\nconst DismissableLayer = React.forwardRef(\n (props, forwardedRef) => {\n const {\n disableOutsidePointerEvents = false,\n onEscapeKeyDown,\n onPointerDownOutside,\n onFocusOutside,\n onInteractOutside,\n onDismiss,\n ...layerProps\n } = props;\n const context = React.useContext(DismissableLayerContext);\n const [node, setNode] = React.useState(null);\n const ownerDocument = node?.ownerDocument ?? globalThis?.document;\n const [, force] = React.useState({});\n const composedRefs = useComposedRefs(forwardedRef, (node) => setNode(node));\n const layers = Array.from(context.layers);\n const [highestLayerWithOutsidePointerEventsDisabled] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1); // prettier-ignore\n const highestLayerWithOutsidePointerEventsDisabledIndex = layers.indexOf(highestLayerWithOutsidePointerEventsDisabled!); // prettier-ignore\n const index = node ? layers.indexOf(node) : -1;\n const isBodyPointerEventsDisabled = context.layersWithOutsidePointerEventsDisabled.size > 0;\n const isPointerEventsEnabled = index >= highestLayerWithOutsidePointerEventsDisabledIndex;\n\n const pointerDownOutside = usePointerDownOutside((event) => {\n const target = event.target as HTMLElement;\n const isPointerDownOnBranch = [...context.branches].some((branch) => branch.contains(target));\n if (!isPointerEventsEnabled || isPointerDownOnBranch) return;\n onPointerDownOutside?.(event);\n onInteractOutside?.(event);\n if (!event.defaultPrevented) onDismiss?.();\n }, ownerDocument);\n\n const focusOutside = useFocusOutside((event) => {\n const target = event.target as HTMLElement;\n const isFocusInBranch = [...context.branches].some((branch) => branch.contains(target));\n if (isFocusInBranch) return;\n onFocusOutside?.(event);\n onInteractOutside?.(event);\n if (!event.defaultPrevented) onDismiss?.();\n }, ownerDocument);\n\n useEscapeKeydown((event) => {\n const isHighestLayer = index === context.layers.size - 1;\n if (!isHighestLayer) return;\n onEscapeKeyDown?.(event);\n if (!event.defaultPrevented && onDismiss) {\n event.preventDefault();\n onDismiss();\n }\n }, ownerDocument);\n\n React.useEffect(() => {\n if (!node) return;\n if (disableOutsidePointerEvents) {\n if (context.layersWithOutsidePointerEventsDisabled.size === 0) {\n originalBodyPointerEvents = ownerDocument.body.style.pointerEvents;\n ownerDocument.body.style.pointerEvents = 'none';\n }\n context.layersWithOutsidePointerEventsDisabled.add(node);\n }\n context.layers.add(node);\n dispatchUpdate();\n return () => {\n if (\n disableOutsidePointerEvents &&\n context.layersWithOutsidePointerEventsDisabled.size === 1\n ) {\n ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;\n }\n };\n }, [node, ownerDocument, disableOutsidePointerEvents, context]);\n\n /**\n * We purposefully prevent combining this effect with the `disableOutsidePointerEvents` effect\n * because a change to `disableOutsidePointerEvents` would remove this layer from the stack\n * and add it to the end again so the layering order wouldn't be _creation order_.\n * We only want them to be removed from context stacks when unmounted.\n */\n React.useEffect(() => {\n return () => {\n if (!node) return;\n context.layers.delete(node);\n context.layersWithOutsidePointerEventsDisabled.delete(node);\n dispatchUpdate();\n };\n }, [node, context]);\n\n React.useEffect(() => {\n const handleUpdate = () => force({});\n document.addEventListener(CONTEXT_UPDATE, handleUpdate);\n return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate);\n }, []);\n\n return (\n \n );\n }\n);\n\nDismissableLayer.displayName = DISMISSABLE_LAYER_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * DismissableLayerBranch\n * -----------------------------------------------------------------------------------------------*/\n\nconst BRANCH_NAME = 'DismissableLayerBranch';\n\ntype DismissableLayerBranchElement = React.ComponentRef;\ninterface DismissableLayerBranchProps extends PrimitiveDivProps {}\n\nconst DismissableLayerBranch = React.forwardRef<\n DismissableLayerBranchElement,\n DismissableLayerBranchProps\n>((props, forwardedRef) => {\n const context = React.useContext(DismissableLayerContext);\n const ref = React.useRef(null);\n const composedRefs = useComposedRefs(forwardedRef, ref);\n\n React.useEffect(() => {\n const node = ref.current;\n if (node) {\n context.branches.add(node);\n return () => {\n context.branches.delete(node);\n };\n }\n }, [context.branches]);\n\n return ;\n});\n\nDismissableLayerBranch.displayName = BRANCH_NAME;\n\n/* -----------------------------------------------------------------------------------------------*/\n\ntype PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;\ntype FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>;\n\n/**\n * Listens for `pointerdown` outside a react subtree. We use `pointerdown` rather than `pointerup`\n * to mimic layer dismissing behaviour present in OS.\n * Returns props to pass to the node we want to check for outside events.\n */\nfunction usePointerDownOutside(\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const handlePointerDownOutside = useCallbackRef(onPointerDownOutside) as EventListener;\n const isPointerInsideReactTreeRef = React.useRef(false);\n const handleClickRef = React.useRef(() => {});\n\n React.useEffect(() => {\n const handlePointerDown = (event: PointerEvent) => {\n if (event.target && !isPointerInsideReactTreeRef.current) {\n const eventDetail = { originalEvent: event };\n\n function handleAndDispatchPointerDownOutsideEvent() {\n handleAndDispatchCustomEvent(\n POINTER_DOWN_OUTSIDE,\n handlePointerDownOutside,\n eventDetail,\n { discrete: true }\n );\n }\n\n /**\n * On touch devices, we need to wait for a click event because browsers implement\n * a ~350ms delay between the time the user stops touching the display and when the\n * browser executres events. We need to ensure we don't reactivate pointer-events within\n * this timeframe otherwise the browser may execute events that should have been prevented.\n *\n * Additionally, this also lets us deal automatically with cancellations when a click event\n * isn't raised because the page was considered scrolled/drag-scrolled, long-pressed, etc.\n *\n * This is why we also continuously remove the previous listener, because we cannot be\n * certain that it was raised, and therefore cleaned-up.\n */\n if (event.pointerType === 'touch') {\n ownerDocument.removeEventListener('click', handleClickRef.current);\n handleClickRef.current = handleAndDispatchPointerDownOutsideEvent;\n ownerDocument.addEventListener('click', handleClickRef.current, { once: true });\n } else {\n handleAndDispatchPointerDownOutsideEvent();\n }\n } else {\n // We need to remove the event listener in case the outside click has been canceled.\n // See: https://github.com/radix-ui/primitives/issues/2171\n ownerDocument.removeEventListener('click', handleClickRef.current);\n }\n isPointerInsideReactTreeRef.current = false;\n };\n /**\n * if this hook executes in a component that mounts via a `pointerdown` event, the event\n * would bubble up to the document and trigger a `pointerDownOutside` event. We avoid\n * this by delaying the event listener registration on the document.\n * This is not React specific, but rather how the DOM works, ie:\n * ```\n * button.addEventListener('pointerdown', () => {\n * console.log('I will log');\n * document.addEventListener('pointerdown', () => {\n * console.log('I will also log');\n * })\n * });\n */\n const timerId = window.setTimeout(() => {\n ownerDocument.addEventListener('pointerdown', handlePointerDown);\n }, 0);\n return () => {\n window.clearTimeout(timerId);\n ownerDocument.removeEventListener('pointerdown', handlePointerDown);\n ownerDocument.removeEventListener('click', handleClickRef.current);\n };\n }, [ownerDocument, handlePointerDownOutside]);\n\n return {\n // ensures we check React component tree (not just DOM tree)\n onPointerDownCapture: () => (isPointerInsideReactTreeRef.current = true),\n };\n}\n\n/**\n * Listens for when focus happens outside a react subtree.\n * Returns props to pass to the root (node) of the subtree we want to check.\n */\nfunction useFocusOutside(\n onFocusOutside?: (event: FocusOutsideEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const handleFocusOutside = useCallbackRef(onFocusOutside) as EventListener;\n const isFocusInsideReactTreeRef = React.useRef(false);\n\n React.useEffect(() => {\n const handleFocus = (event: FocusEvent) => {\n if (event.target && !isFocusInsideReactTreeRef.current) {\n const eventDetail = { originalEvent: event };\n handleAndDispatchCustomEvent(FOCUS_OUTSIDE, handleFocusOutside, eventDetail, {\n discrete: false,\n });\n }\n };\n ownerDocument.addEventListener('focusin', handleFocus);\n return () => ownerDocument.removeEventListener('focusin', handleFocus);\n }, [ownerDocument, handleFocusOutside]);\n\n return {\n onFocusCapture: () => (isFocusInsideReactTreeRef.current = true),\n onBlurCapture: () => (isFocusInsideReactTreeRef.current = false),\n };\n}\n\nfunction dispatchUpdate() {\n const event = new CustomEvent(CONTEXT_UPDATE);\n document.dispatchEvent(event);\n}\n\nfunction handleAndDispatchCustomEvent(\n name: string,\n handler: ((event: E) => void) | undefined,\n detail: { originalEvent: OriginalEvent } & (E extends CustomEvent ? D : never),\n { discrete }: { discrete: boolean }\n) {\n const target = detail.originalEvent.target;\n const event = new CustomEvent(name, { bubbles: false, cancelable: true, detail });\n if (handler) target.addEventListener(name, handler as EventListener, { once: true });\n\n if (discrete) {\n dispatchDiscreteCustomEvent(target, event);\n } else {\n target.dispatchEvent(event);\n }\n}\n\nconst Root = DismissableLayer;\nconst Branch = DismissableLayerBranch;\n\nexport {\n DismissableLayer,\n DismissableLayerBranch,\n //\n Root,\n Branch,\n};\nexport type { DismissableLayerProps };\n", "import * as React from 'react';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\n\n/**\n * Listens for when the escape key is down\n */\nfunction useEscapeKeydown(\n onEscapeKeyDownProp?: (event: KeyboardEvent) => void,\n ownerDocument: Document = globalThis?.document\n) {\n const onEscapeKeyDown = useCallbackRef(onEscapeKeyDownProp);\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onEscapeKeyDown(event);\n }\n };\n ownerDocument.addEventListener('keydown', handleKeyDown, { capture: true });\n return () => ownerDocument.removeEventListener('keydown', handleKeyDown, { capture: true });\n }, [onEscapeKeyDown, ownerDocument]);\n}\n\nexport { useEscapeKeydown };\n", "import * as React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Primitive } from '@radix-ui/react-primitive';\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\n\n/* -------------------------------------------------------------------------------------------------\n * Portal\n * -----------------------------------------------------------------------------------------------*/\n\nconst PORTAL_NAME = 'Portal';\n\ntype PortalElement = React.ComponentRef;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef;\ninterface PortalProps extends PrimitiveDivProps {\n /**\n * An optional container where the portaled content should be appended.\n */\n container?: Element | DocumentFragment | null;\n}\n\nconst Portal = React.forwardRef((props, forwardedRef) => {\n const { container: containerProp, ...portalProps } = props;\n const [mounted, setMounted] = React.useState(false);\n useLayoutEffect(() => setMounted(true), []);\n const container = containerProp || (mounted && globalThis?.document?.body);\n return container\n ? ReactDOM.createPortal(, container)\n : null;\n});\n\nPortal.displayName = PORTAL_NAME;\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = Portal;\n\nexport {\n Portal,\n //\n Root,\n};\nexport type { PortalProps };\n"], + "mappings": "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", + "names": ["createContext", "useContext", "createScope", "nextScopes", "React", "useLayoutEffect", "React", "React2", "React", "React", "value", "React", "React", "import_jsx_runtime", "Slot", "props", "Slottable", "Fragment", "import_jsx_runtime", "Slot", "React", "React", "React", "import_jsx_runtime", "node", "handleAndDispatchPointerDownOutsideEvent", "React", "import_jsx_runtime", "ReactDOM"] +} diff --git a/frontend/.vite/deps/chunk-4CRPNNNE.js b/frontend/.vite/deps/chunk-KJ4VQ6GH.js similarity index 93% rename from frontend/.vite/deps/chunk-4CRPNNNE.js rename to frontend/.vite/deps/chunk-KJ4VQ6GH.js index 0572922a..c71d6b90 100644 --- a/frontend/.vite/deps/chunk-4CRPNNNE.js +++ b/frontend/.vite/deps/chunk-KJ4VQ6GH.js @@ -1,6 +1,6 @@ import { Primitive -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { require_jsx_runtime } from "./chunk-6NP5ER6T.js"; @@ -46,4 +46,4 @@ export { VISUALLY_HIDDEN_STYLES, VisuallyHidden }; -//# sourceMappingURL=chunk-4CRPNNNE.js.map +//# sourceMappingURL=chunk-KJ4VQ6GH.js.map diff --git a/frontend/.vite/deps/chunk-4CRPNNNE.js.map b/frontend/.vite/deps/chunk-KJ4VQ6GH.js.map similarity index 100% rename from frontend/.vite/deps/chunk-4CRPNNNE.js.map rename to frontend/.vite/deps/chunk-KJ4VQ6GH.js.map diff --git a/frontend/.vite/deps/chunk-LSB2BQTJ.js b/frontend/.vite/deps/chunk-LZUCAFHS.js similarity index 98% rename from frontend/.vite/deps/chunk-LSB2BQTJ.js rename to frontend/.vite/deps/chunk-LZUCAFHS.js index 3e666187..3e12a3e3 100644 --- a/frontend/.vite/deps/chunk-LSB2BQTJ.js +++ b/frontend/.vite/deps/chunk-LZUCAFHS.js @@ -1,6 +1,6 @@ import { useLayoutEffect2 -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { useComposedRefs } from "./chunk-JBZRCD6T.js"; @@ -141,4 +141,4 @@ function getElementRef(element) { export { Presence }; -//# sourceMappingURL=chunk-LSB2BQTJ.js.map +//# sourceMappingURL=chunk-LZUCAFHS.js.map diff --git a/frontend/.vite/deps/chunk-LSB2BQTJ.js.map b/frontend/.vite/deps/chunk-LZUCAFHS.js.map similarity index 100% rename from frontend/.vite/deps/chunk-LSB2BQTJ.js.map rename to frontend/.vite/deps/chunk-LZUCAFHS.js.map diff --git a/frontend/.vite/deps/chunk-ML46ZY4T.js b/frontend/.vite/deps/chunk-SQROKAUX.js similarity index 99% rename from frontend/.vite/deps/chunk-ML46ZY4T.js rename to frontend/.vite/deps/chunk-SQROKAUX.js index 7ea45773..ba9407f0 100644 --- a/frontend/.vite/deps/chunk-ML46ZY4T.js +++ b/frontend/.vite/deps/chunk-SQROKAUX.js @@ -1,6 +1,6 @@ import { createContextScope -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { composeRefs, useComposedRefs @@ -183,4 +183,4 @@ function createCollection(name) { export { createCollection }; -//# sourceMappingURL=chunk-ML46ZY4T.js.map +//# sourceMappingURL=chunk-SQROKAUX.js.map diff --git a/frontend/.vite/deps/chunk-ML46ZY4T.js.map b/frontend/.vite/deps/chunk-SQROKAUX.js.map similarity index 100% rename from frontend/.vite/deps/chunk-ML46ZY4T.js.map rename to frontend/.vite/deps/chunk-SQROKAUX.js.map diff --git a/frontend/.vite/deps/chunk-FKAYWSWW.js b/frontend/.vite/deps/chunk-Z4UMJCHA.js similarity index 99% rename from frontend/.vite/deps/chunk-FKAYWSWW.js rename to frontend/.vite/deps/chunk-Z4UMJCHA.js index ccf67c51..608c1d25 100644 --- a/frontend/.vite/deps/chunk-FKAYWSWW.js +++ b/frontend/.vite/deps/chunk-Z4UMJCHA.js @@ -3,16 +3,16 @@ import { createContextScope, useCallbackRef, useLayoutEffect2 -} from "./chunk-46KKQMAN.js"; +} from "./chunk-HNRLCIXP.js"; import { useComposedRefs } from "./chunk-JBZRCD6T.js"; -import { - require_jsx_runtime -} from "./chunk-6NP5ER6T.js"; import { require_react_dom } from "./chunk-M3HRAD2J.js"; +import { + require_jsx_runtime +} from "./chunk-6NP5ER6T.js"; import { require_react } from "./chunk-JIJPEAKZ.js"; @@ -2265,4 +2265,4 @@ export { Content, Arrow2 as Arrow }; -//# sourceMappingURL=chunk-FKAYWSWW.js.map +//# sourceMappingURL=chunk-Z4UMJCHA.js.map diff --git a/frontend/.vite/deps/chunk-FKAYWSWW.js.map b/frontend/.vite/deps/chunk-Z4UMJCHA.js.map similarity index 100% rename from frontend/.vite/deps/chunk-FKAYWSWW.js.map rename to frontend/.vite/deps/chunk-Z4UMJCHA.js.map diff --git a/src/actions.ts b/src/actions.ts index 93267794..8cf383da 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -77,6 +77,11 @@ import { sortRecordsDeterministically, } from "./services/search.js"; import { createObservationsFromRecord } from "./services/observation_ingestion.js"; +import { + enqueueFailedUpload, + computeContentHash, +} from "./services/upload_queue.js"; +import { incrementStorageUsage } from "./services/storage_usage.js"; export const app = express(); // Configure CSP to allow CDN scripts for the uploader and API connects @@ -130,6 +135,7 @@ const SENSITIVE_FIELDS = new Set([ const CANONICAL_RECORD_TYPES = listCanonicalRecordTypes(); const CANONICAL_RECORD_TYPE_IDS = CANONICAL_RECORD_TYPES.map((def) => def.id); +const DEFAULT_USER_ID = "00000000-0000-0000-0000-000000000000"; function redactHeaders( headers: Record @@ -947,7 +953,7 @@ app.post("/store_record", async (req, res) => { // Create observations (FU-058) try { const { createObservationsFromRecord } = await import('./services/observation_ingestion.js'); - await createObservationsFromRecord(data as any, "00000000-0000-0000-0000-000000000000"); + await createObservationsFromRecord(data as any, DEFAULT_USER_ID); } catch (observationError) { // Log observation creation error but don't fail the request logError("ObservationCreationError:store_record", req, observationError); @@ -1851,7 +1857,7 @@ app.post("/create_relationship", async (req, res) => { metadata, } = parsed.data; - const userId = "00000000-0000-0000-0000-000000000000"; // v0.1.0 single-user + const userId = DEFAULT_USER_ID; // v0.1.0 single-user const { relationshipsService } = await import("./services/relationships.js"); @@ -2088,6 +2094,7 @@ app.post("/upload_file", upload.single("file"), async (req, res) => { const originalName = req.file?.originalname || "upload.bin"; const mimeType = req.file?.mimetype || "application/octet-stream"; const fileSize = req.file?.size ?? fileBuffer.length; + const fileHash = computeContentHash(fileBuffer); const recordId = record_id ?? randomUUID(); @@ -2176,12 +2183,56 @@ app.post("/upload_file", upload.single("file"), async (req, res) => { .from(bucketName) .upload(fileName, fileBuffer, { upsert: false }); - if (uploadError) { - logError("SupabaseStorageError:upload_file", req, uploadError, { + if (uploadError || !uploadData) { + logError("SupabaseStorageError:upload_file", req, uploadError ?? {}, { bucket: bucketName, fileName, }); - return res.status(500).json({ error: uploadError.message }); + + const queueMetadata = { + record_id: record_id ?? null, + override_properties: overrideProperties ?? null, + csv_row_records_requested: shouldGenerateCsvRows, + csv_rows_generated: preparedCsvRows.length > 0, + mime_type: mimeType, + original_name: originalName, + }; + + try { + const queued = await enqueueFailedUpload({ + bucket: bucketName, + objectPath: fileName, + buffer: fileBuffer, + byteSize: fileSize, + userId: DEFAULT_USER_ID, + errorMessage: uploadError?.message ?? "Storage upload failed", + metadata: queueMetadata, + contentHash: fileHash, + payloadSubmissionId: null, + }); + + logDebug("UploadQueueEnqueue", req, { + queue_id: queued.id, + bucket: bucketName, + object_path: fileName, + }); + + return res.status(202).json({ + status: "pending_retry", + queue_id: queued.id, + storage_status: "pending_retry", + }); + } catch (queueError) { + logError("UploadQueueEnqueueFailed:upload_file", req, queueError, { + bucket: bucketName, + fileName, + }); + return res.status(503).json({ + error: + uploadError?.message ?? + "Storage upload failed and retry queue is unavailable", + }); + } } const filePath = uploadData.path; @@ -2201,6 +2252,15 @@ app.post("/upload_file", upload.single("file"), async (req, res) => { return res.status(500).json({ error: updateError.message }); } + try { + await incrementStorageUsage(DEFAULT_USER_ID, fileSize); + } catch (usageError) { + logWarn("StorageUsageIncrementFailed:update_record_files", req, { + error: + usageError instanceof Error ? usageError.message : String(usageError), + }); + } + logDebug("Success:upload_file", req, { record_id, filePath }); return res.json(updated); } @@ -2289,6 +2349,15 @@ app.post("/upload_file", upload.single("file"), async (req, res) => { logError("EventEmissionError:upload_file", req, eventError); } + try { + await incrementStorageUsage(DEFAULT_USER_ID, fileSize); + } catch (usageError) { + logWarn("StorageUsageIncrementFailed:create_record", req, { + error: + usageError instanceof Error ? usageError.message : String(usageError), + }); + } + logDebug("Success:upload_file:create", req, { record_id: created.id, filePath, diff --git a/src/services/storage_usage.test.ts b/src/services/storage_usage.test.ts new file mode 100644 index 00000000..b689e4cd --- /dev/null +++ b/src/services/storage_usage.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { + incrementStorageUsage, + incrementInterpretationCount, +} from "./storage_usage.js"; + +const rpcMocks = vi.hoisted(() => ({ + rpcMock: vi.fn(), +})); + +vi.mock("../db.js", () => ({ + supabase: { + rpc: rpcMocks.rpcMock, + }, +})); + +describe("storage_usage service", () => { + beforeEach(() => { + rpcMocks.rpcMock.mockReset(); + }); + + it("increments storage usage via RPC", async () => { + rpcMocks.rpcMock.mockResolvedValue({ data: null, error: null }); + + await incrementStorageUsage("user-1", 512); + + expect(rpcMocks.rpcMock).toHaveBeenCalledWith("increment_storage_usage", { + p_user_id: "user-1", + p_bytes: 512, + }); + }); + + it("throws when storage usage RPC fails", async () => { + rpcMocks.rpcMock.mockResolvedValue({ + data: null, + error: { message: "boom" }, + }); + + await expect(incrementStorageUsage("user-1", 100)).rejects.toThrow( + /increment_storage_usage failed/, + ); + }); + + it("increments interpretation count via RPC", async () => { + rpcMocks.rpcMock.mockResolvedValue({ data: null, error: null }); + + await incrementInterpretationCount("user-42"); + + expect(rpcMocks.rpcMock).toHaveBeenCalledWith( + "increment_interpretation_count", + { + p_user_id: "user-42", + }, + ); + }); + + it("throws when interpretation RPC fails", async () => { + rpcMocks.rpcMock.mockResolvedValue({ + data: null, + error: { message: "nope" }, + }); + + await expect(incrementInterpretationCount("user-42")).rejects.toThrow( + /increment_interpretation_count failed/, + ); + + expect(rpcMocks.rpcMock).toHaveBeenCalledWith( + "increment_interpretation_count", + { + p_user_id: "user-42", + }, + ); + }); +}); diff --git a/src/services/storage_usage.ts b/src/services/storage_usage.ts new file mode 100644 index 00000000..6e2473b5 --- /dev/null +++ b/src/services/storage_usage.ts @@ -0,0 +1,36 @@ +import { supabase } from "../db.js"; + +const STORAGE_USAGE_FUNCTION = "increment_storage_usage"; +const INTERPRETATION_COUNT_FUNCTION = "increment_interpretation_count"; + +export async function incrementStorageUsage( + userId: string, + bytes: number, +): Promise { + const { error } = await supabase.rpc(STORAGE_USAGE_FUNCTION, { + p_user_id: userId, + p_bytes: bytes, + }); + + if (error) { + throw new Error( + `increment_storage_usage failed: ${error.message ?? "unknown error"}`, + ); + } +} + +export async function incrementInterpretationCount( + userId: string, +): Promise { + const { error } = await supabase.rpc(INTERPRETATION_COUNT_FUNCTION, { + p_user_id: userId, + }); + + if (error) { + throw new Error( + `increment_interpretation_count failed: ${ + error.message ?? "unknown error" + }`, + ); + } +} diff --git a/src/services/upload_queue.test.ts b/src/services/upload_queue.test.ts new file mode 100644 index 00000000..7d76dcb0 --- /dev/null +++ b/src/services/upload_queue.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { rm, readdir, readFile } from "node:fs/promises"; +import path from "node:path"; +import { + enqueueFailedUpload, + getUploadQueueDirectory, + computeContentHash, +} from "./upload_queue.js"; + +const queueMocks = vi.hoisted(() => { + const singleMock = vi.fn(); + const selectMock = vi.fn(() => ({ single: singleMock })); + const insertMock = vi.fn(() => ({ select: selectMock })); + const fromMock = vi.fn(() => ({ insert: insertMock })); + return { singleMock, selectMock, insertMock, fromMock }; +}); + +vi.mock("../db.js", () => ({ + supabase: { + from: queueMocks.fromMock, + }, +})); + +const TEST_DIR = path.join(process.cwd(), ".tmp-test-upload-queue"); + +describe("upload_queue service", () => { + beforeEach(async () => { + process.env.UPLOAD_QUEUE_DIR = TEST_DIR; + queueMocks.insertMock.mockClear(); + queueMocks.selectMock.mockClear(); + queueMocks.singleMock.mockClear(); + queueMocks.fromMock.mockClear(); + await rm(TEST_DIR, { recursive: true, force: true }); + }); + + afterEach(async () => { + await rm(TEST_DIR, { recursive: true, force: true }); + }); + + it("enqueues failed upload and writes temp file", async () => { + queueMocks.singleMock.mockResolvedValue({ + data: { id: "queue-id" }, + error: null, + }); + + const buffer = Buffer.from("hello world"); + const result = await enqueueFailedUpload({ + bucket: "files", + objectPath: "foo/bar.txt", + buffer, + byteSize: buffer.length, + userId: "user-1", + errorMessage: "failure", + metadata: { note: "test" }, + contentHash: computeContentHash(buffer), + }); + + expect(result.id).toEqual("queue-id"); + expect(queueMocks.fromMock).toHaveBeenCalledWith("upload_queue"); + + const files = await readdir(getUploadQueueDirectory()); + expect(files.length).toBe(1); + const saved = await readFile(path.join(getUploadQueueDirectory(), files[0])); + expect(saved.equals(buffer)).toBe(true); + }); + + it("cleans up temp file when insert fails", async () => { + queueMocks.singleMock.mockResolvedValue({ + data: null, + error: { message: "boom" }, + }); + + const buffer = Buffer.from("bad upload"); + await expect( + enqueueFailedUpload({ + bucket: "files", + objectPath: "foo/fail.bin", + buffer, + byteSize: buffer.length, + userId: "user-1", + }), + ).rejects.toThrow(/Failed to enqueue upload/); + + const files = await readdir(getUploadQueueDirectory()).catch(() => []); + expect(files.length).toBe(0); + }); + + it("computes deterministic hashes", () => { + const buffer = Buffer.from("abc"); + const hash1 = computeContentHash(buffer); + const hash2 = computeContentHash(Buffer.from("abc")); + expect(hash1).toEqual(hash2); + }); +}); diff --git a/src/services/upload_queue.ts b/src/services/upload_queue.ts new file mode 100644 index 00000000..e369a25a --- /dev/null +++ b/src/services/upload_queue.ts @@ -0,0 +1,66 @@ +import { randomUUID, createHash } from "node:crypto"; +import { mkdir, writeFile, rm } from "node:fs/promises"; +import path from "node:path"; +import { supabase } from "../db.js"; + +const DEFAULT_QUEUE_DIR = path.join(process.cwd(), ".tmp", "upload-queue"); + +export interface FailedUploadPayload { + bucket: string; + objectPath: string; + buffer: Buffer; + byteSize: number; + userId: string; + errorMessage?: string; + metadata?: Record; + payloadSubmissionId?: string | null; + contentHash?: string; +} + +export function getUploadQueueDirectory(): string { + const fromEnv = process.env.UPLOAD_QUEUE_DIR?.trim(); + return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_QUEUE_DIR; +} + +export function computeContentHash(buffer: Buffer): string { + return createHash("sha256").update(buffer).digest("hex"); +} + +async function ensureQueueDirectory(): Promise { + const dir = getUploadQueueDirectory(); + await mkdir(dir, { recursive: true }); + return dir; +} + +export async function enqueueFailedUpload( + payload: FailedUploadPayload, +): Promise<{ id: string }> { + const dir = await ensureQueueDirectory(); + const queueFileName = `${Date.now()}-${randomUUID()}.bin`; + const tempFilePath = path.join(dir, queueFileName); + + await writeFile(tempFilePath, payload.buffer, { mode: 0o600 }); + + const { data, error } = await supabase + .from("upload_queue") + .insert({ + temp_file_path: tempFilePath, + bucket: payload.bucket, + object_path: payload.objectPath, + content_hash: payload.contentHash ?? computeContentHash(payload.buffer), + byte_size: payload.byteSize, + user_id: payload.userId, + error_message: payload.errorMessage ?? null, + metadata: payload.metadata ?? {}, + payload_submission_id: payload.payloadSubmissionId ?? null, + }) + .select("id") + .single(); + + if (error) { + await rm(tempFilePath, { force: true }); + throw new Error(`Failed to enqueue upload: ${error.message ?? error.code}`); + } + + return { id: data!.id as string }; +} diff --git a/supabase/migrations/20251218120000_add_storage_infrastructure.sql b/supabase/migrations/20251218120000_add_storage_infrastructure.sql new file mode 100644 index 00000000..4ba32b3a --- /dev/null +++ b/supabase/migrations/20251218120000_add_storage_infrastructure.sql @@ -0,0 +1,96 @@ +-- FU-112 Storage Infrastructure: upload queue + storage usage tracking +set check_function_bodies = off; +set search_path = public; + +-- Upload queue table -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS public.upload_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + temp_file_path TEXT NOT NULL, + bucket TEXT NOT NULL, + object_path TEXT NOT NULL, + content_hash TEXT NOT NULL, + byte_size BIGINT NOT NULL, + retry_count INTEGER NOT NULL DEFAULT 0, + max_retries INTEGER NOT NULL DEFAULT 5, + next_retry_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + error_message TEXT, + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + user_id UUID NOT NULL, + payload_submission_id UUID REFERENCES public.payload_submissions(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_upload_queue_next_retry + ON public.upload_queue(next_retry_at) + WHERE retry_count < max_retries; + +CREATE INDEX IF NOT EXISTS idx_upload_queue_user + ON public.upload_queue(user_id); + +ALTER TABLE public.upload_queue ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Service role full access - upload_queue" ON public.upload_queue; +CREATE POLICY "Service role full access - upload_queue" + ON public.upload_queue + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + +-- Storage usage table ------------------------------------------------------- +CREATE TABLE IF NOT EXISTS public.storage_usage ( + user_id UUID PRIMARY KEY, + total_bytes BIGINT NOT NULL DEFAULT 0, + total_sources INTEGER NOT NULL DEFAULT 0, + last_calculated TIMESTAMPTZ NOT NULL DEFAULT NOW(), + interpretation_count_month INTEGER NOT NULL DEFAULT 0, + interpretation_limit_month INTEGER NOT NULL DEFAULT 100, + billing_month TEXT NOT NULL DEFAULT to_char(NOW(), 'YYYY-MM') +); + +ALTER TABLE public.storage_usage ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Service role full access - storage_usage" ON public.storage_usage; +CREATE POLICY "Service role full access - storage_usage" + ON public.storage_usage + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + +-- Helper functions ---------------------------------------------------------- +CREATE OR REPLACE FUNCTION public.increment_storage_usage( + p_user_id UUID, + p_bytes BIGINT +) RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO public.storage_usage (user_id, total_bytes, total_sources, last_calculated) + VALUES (p_user_id, p_bytes, 1, NOW()) + ON CONFLICT (user_id) DO UPDATE SET + total_bytes = public.storage_usage.total_bytes + p_bytes, + total_sources = public.storage_usage.total_sources + 1, + last_calculated = NOW(); +END; +$$; + +CREATE OR REPLACE FUNCTION public.increment_interpretation_count( + p_user_id UUID +) RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + current_billing_month TEXT := to_char(NOW(), 'YYYY-MM'); +BEGIN + INSERT INTO public.storage_usage (user_id, interpretation_count_month, billing_month) + VALUES (p_user_id, 1, current_billing_month) + ON CONFLICT (user_id) DO UPDATE SET + interpretation_count_month = CASE + WHEN public.storage_usage.billing_month = current_billing_month + THEN public.storage_usage.interpretation_count_month + 1 + ELSE 1 + END, + billing_month = current_billing_month; +END; +$$; diff --git a/supabase/schema.sql b/supabase/schema.sql index 7db00242..c3cf09eb 100644 --- a/supabase/schema.sql +++ b/supabase/schema.sql @@ -337,6 +337,87 @@ CREATE POLICY "Service role full access - payload_submissions" ON payload_submis DROP POLICY IF EXISTS "public read - payload_submissions" ON payload_submissions; CREATE POLICY "public read - payload_submissions" ON payload_submissions FOR SELECT USING (true); +-- Upload queue for failed storage operations (FU-112) +CREATE TABLE IF NOT EXISTS upload_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + temp_file_path TEXT NOT NULL, + bucket TEXT NOT NULL, + object_path TEXT NOT NULL, + content_hash TEXT NOT NULL, + byte_size BIGINT NOT NULL, + retry_count INTEGER NOT NULL DEFAULT 0, + max_retries INTEGER NOT NULL DEFAULT 5, + next_retry_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + error_message TEXT, + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + user_id UUID NOT NULL, + payload_submission_id UUID REFERENCES payload_submissions(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_upload_queue_next_retry + ON upload_queue(next_retry_at) + WHERE retry_count < max_retries; + +CREATE INDEX IF NOT EXISTS idx_upload_queue_user ON upload_queue(user_id); + +ALTER TABLE upload_queue ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Service role full access - upload_queue" ON upload_queue; +CREATE POLICY "Service role full access - upload_queue" ON upload_queue + FOR ALL TO service_role USING (true) WITH CHECK (true); + +-- Storage usage counters (per-user quotas) +CREATE TABLE IF NOT EXISTS storage_usage ( + user_id UUID PRIMARY KEY, + total_bytes BIGINT NOT NULL DEFAULT 0, + total_sources INTEGER NOT NULL DEFAULT 0, + last_calculated TIMESTAMPTZ NOT NULL DEFAULT NOW(), + interpretation_count_month INTEGER NOT NULL DEFAULT 0, + interpretation_limit_month INTEGER NOT NULL DEFAULT 100, + billing_month TEXT NOT NULL DEFAULT to_char(NOW(), 'YYYY-MM') +); + +ALTER TABLE storage_usage ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Service role full access - storage_usage" ON storage_usage; +CREATE POLICY "Service role full access - storage_usage" ON storage_usage + FOR ALL TO service_role USING (true) WITH CHECK (true); + +CREATE OR REPLACE FUNCTION increment_storage_usage( + p_user_id UUID, + p_bytes BIGINT +) RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO storage_usage (user_id, total_bytes, total_sources, last_calculated) + VALUES (p_user_id, p_bytes, 1, NOW()) + ON CONFLICT (user_id) DO UPDATE SET + total_bytes = storage_usage.total_bytes + p_bytes, + total_sources = storage_usage.total_sources + 1, + last_calculated = NOW(); +END; +$$; + +CREATE OR REPLACE FUNCTION increment_interpretation_count( + p_user_id UUID +) RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + current_billing_month TEXT := to_char(NOW(), 'YYYY-MM'); +BEGIN + INSERT INTO storage_usage (user_id, interpretation_count_month, billing_month) + VALUES (p_user_id, 1, current_billing_month) + ON CONFLICT (user_id) DO UPDATE SET + interpretation_count_month = CASE + WHEN storage_usage.billing_month = current_billing_month + THEN storage_usage.interpretation_count_month + 1 + ELSE 1 + END, + billing_month = current_billing_month; +END; +$$; + -- Observation architecture tables (FU-055, FU-057) -- Observations table CREATE TABLE IF NOT EXISTS observations (