From 3af1275a3d171d4e73d8729e4138c63fd2f5f10f Mon Sep 17 00:00:00 2001 From: Pritesh Umraniya Date: Thu, 20 Aug 2026 13:35:17 +0530 Subject: [PATCH 1/5] chore: initialize dealer agreement redline build --- use-cases/dealer-agreement-redline/.gitignore | 27 +++++++++++++++++++ .../backend/.env.example | 1 + .../backend/app/__init__.py | 0 .../backend/app/api/__init__.py | 0 .../backend/app/services/__init__.py | 0 .../backend/app/superdocs/__init__.py | 0 6 files changed, 28 insertions(+) create mode 100644 use-cases/dealer-agreement-redline/.gitignore create mode 100644 use-cases/dealer-agreement-redline/backend/.env.example create mode 100644 use-cases/dealer-agreement-redline/backend/app/__init__.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/api/__init__.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/__init__.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/superdocs/__init__.py diff --git a/use-cases/dealer-agreement-redline/.gitignore b/use-cases/dealer-agreement-redline/.gitignore new file mode 100644 index 00000000..27d9f726 --- /dev/null +++ b/use-cases/dealer-agreement-redline/.gitignore @@ -0,0 +1,27 @@ +# Environment +.env +.env.* +!.env.example + +# Python +__pycache__/ +*.py[cod] +*.pyo +.venv/ +venv/ + +# Node +node_modules/ +dist/ +build/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/.env.example b/use-cases/dealer-agreement-redline/backend/.env.example new file mode 100644 index 00000000..00491afb --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/.env.example @@ -0,0 +1 @@ +SUPERDOCS_API_KEY= \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/__init__.py b/use-cases/dealer-agreement-redline/backend/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/dealer-agreement-redline/backend/app/api/__init__.py b/use-cases/dealer-agreement-redline/backend/app/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/dealer-agreement-redline/backend/app/services/__init__.py b/use-cases/dealer-agreement-redline/backend/app/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/dealer-agreement-redline/backend/app/superdocs/__init__.py b/use-cases/dealer-agreement-redline/backend/app/superdocs/__init__.py new file mode 100644 index 00000000..e69de29b From 53b66a2cda6b4e90782d2c32f0430ebe21daeaf0 Mon Sep 17 00:00:00 2001 From: Pritesh Umraniya Date: Thu, 20 Aug 2026 17:02:27 +0530 Subject: [PATCH 2/5] feat: complete dealer agreement redline backend --- use-cases/dealer-agreement-redline/.gitignore | 1 - use-cases/dealer-agreement-redline/README.md | 1276 +++++++++++++++++ .../backend/app/api/poc.py | 142 ++ .../backend/app/api/redline.py | 555 +++++++ .../backend/app/api/superdocs.py | 32 + .../backend/app/config.py | 18 + .../backend/app/db/base.py | 5 + .../backend/app/db/init_db.py | 14 + .../backend/app/db/session.py | 37 + .../backend/app/main.py | 29 + .../backend/app/models/redline_review.py | 114 ++ .../backend/app/services/agreement_parser.py | 44 + .../app/services/deviation_analyzer.py | 69 + .../app/services/playbook_classifier.py | 210 +++ .../backend/app/services/redline_service.py | 115 ++ .../backend/app/services/superdocs_redline.py | 64 + .../backend/app/superdocs/client.py | 289 ++++ .../backend/requirements.txt | 9 + .../test-data/dealer-agreement.docx | Bin 0 -> 36837 bytes .../test-data/master-agreement.docx | Bin 0 -> 36828 bytes .../test-data/negotiation-playbook.docx | Bin 0 -> 36804 bytes 21 files changed, 3022 insertions(+), 1 deletion(-) create mode 100644 use-cases/dealer-agreement-redline/README.md create mode 100644 use-cases/dealer-agreement-redline/backend/app/api/poc.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/api/redline.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/api/superdocs.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/config.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/db/base.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/db/init_db.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/db/session.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/main.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/models/redline_review.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/redline_service.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py create mode 100644 use-cases/dealer-agreement-redline/backend/app/superdocs/client.py create mode 100644 use-cases/dealer-agreement-redline/backend/requirements.txt create mode 100644 use-cases/dealer-agreement-redline/test-data/dealer-agreement.docx create mode 100644 use-cases/dealer-agreement-redline/test-data/master-agreement.docx create mode 100644 use-cases/dealer-agreement-redline/test-data/negotiation-playbook.docx diff --git a/use-cases/dealer-agreement-redline/.gitignore b/use-cases/dealer-agreement-redline/.gitignore index 27d9f726..b88ce475 100644 --- a/use-cases/dealer-agreement-redline/.gitignore +++ b/use-cases/dealer-agreement-redline/.gitignore @@ -1,7 +1,6 @@ # Environment .env .env.* -!.env.example # Python __pycache__/ diff --git a/use-cases/dealer-agreement-redline/README.md b/use-cases/dealer-agreement-redline/README.md new file mode 100644 index 00000000..ba9f0b19 --- /dev/null +++ b/use-cases/dealer-agreement-redline/README.md @@ -0,0 +1,1276 @@ +# Dealer Agreement Redline Desk + +A contract-redlining workflow built with FastAPI, PostgreSQL, and the SuperDocs API. + +The application compares a dealer-returned agreement against an approved master agreement and a negotiation playbook, detects deviations, classifies them, and routes them through a human-in-the-loop approval process before applying redlines and exporting the final document. + +--- + +## Current Status + +### Backend + +- [x] SuperDocs API integration +- [x] API key configuration +- [x] Dealer agreement upload +- [x] Master agreement attachment upload +- [x] Negotiation playbook attachment upload +- [x] DOCX section extraction +- [x] Agreement deviation detection +- [x] Playbook-based classification +- [x] Redline review persistence +- [x] Finding persistence +- [x] Human approve/reject workflow +- [x] Apply approved redlines through SuperDocs +- [x] Export final DOCX +- [x] Duplicate apply protection +- [x] Review-state validation +- [x] Backend compilation verification +- [x] End-to-end workflow verification + +### Frontend + +- [ ] React frontend +- [ ] Review dashboard +- [ ] Review summary +- [ ] Finding cards +- [ ] Master vs dealer comparison +- [ ] Approve/reject controls +- [ ] Apply approved changes +- [ ] Export DOCX + +--- + +## Architecture + +```text +┌─────────────────┐ +│ React UI │ +│ (Frontend) │ +└─────────┬────────┘ + │ HTTP + ▼ +┌─────────────────┐ +│ FastAPI Backend │ +└─────────┬────────┘ + │ + ┌──────┴──────┐ + ▼ ▼ +``` + +The frontend communicates only with the FastAPI backend. + +The SuperDocs API key remains server-side and is never exposed to the browser. + +## Workflow + +``` +Dealer Agreement + │ + ▼ +Create SuperDocs Session + │ + ▼ +Upload Dealer Agreement + │ + ├── Master Agreement → Attachment + │ + └── Negotiation Playbook → Attachment + │ + ▼ +Local Agreement Analysis + │ + ├── Extract sections + ├── Detect deviations + └── Classify against playbook + │ + ▼ +Create Redline Review + │ + ▼ +Human Review + │ + ├── Approve finding + └── Reject finding + │ + ▼ +All Findings Reviewed + │ + ▼ +Apply Approved Findings + │ + ▼ +SuperDocs Updates Active Document + │ + ▼ +Export Final DOCX +``` + +--- + +## Redline Classification + +Each detected deviation is classified into one of three categories: + +``` +PRE-APPROVED +ESCALATE +REFUSE +``` + +**PRE-APPROVED** +The requested change falls within the limits defined by the negotiation playbook. + +**ESCALATE** +The requested change requires human review or approval. + +**REFUSE** +The requested change exceeds the limits defined by the negotiation playbook and should not be accepted. + +The classification is determined by the backend playbook classifier. + +--- + +## Human Review + +Every detected deviation is persisted as a redline finding. + +The reviewer can make one of two decisions: + +``` +approve +reject +``` + +Only findings with: + +``` +decision = approve +``` + +are sent to SuperDocs for application. + +Rejected findings are not applied to the document. + +A finding cannot be reviewed more than once. + +A completed or applied review cannot receive additional decisions. + +--- + +## Review Status + +A review progresses through the following states: + +``` +pending + │ + ▼ +in_review + │ + ▼ +completed + │ + ▼ +applied +``` + +**pending** +The review has been created but human review has not started. + +**in_review** +At least one finding has been reviewed, but pending findings remain. + +**completed** +All findings have received a human decision. + +**applied** +All approved findings have been sent to SuperDocs and applied to the active document. + +--- + +## Database + +The backend uses PostgreSQL. + +### `redline_reviews` + +Stores the overall redline review. + +Fields: + +``` +id +session_id +status +created_at +updated_at +``` + +### `redline_findings` + +Stores individual deviations found during the analysis. + +Fields: + +``` +id +review_id +section +master_text +dealer_text +deviation_type +similarity +classification +reason +counter_position +decision +created_at +``` + +Relationship: + +``` +RedlineReview + │ + └── RedlineFinding + ├── Finding 1 + ├── Finding 2 + ├── Finding 3 + └── ... +``` + +`review_id` references the parent review. + +Deleting a review cascades to its findings. + +--- + +## Backend Structure + +``` +backend/ +├── app/ +│ ├── api/ +│ │ ├── __init__.py +│ │ ├── poc.py +│ │ ├── redline.py +│ │ └── superdocs.py +│ │ +│ ├── db/ +│ │ ├── base.py +│ │ ├── init_db.py +│ │ └── session.py +│ │ +│ ├── models/ +│ │ └── redline_review.py +│ │ +│ ├── services/ +│ │ ├── agreement_parser.py +│ │ ├── deviation_analyzer.py +│ │ ├── playbook_classifier.py +│ │ ├── redline_service.py +│ │ └── superdocs_redline.py +│ │ +│ ├── superdocs/ +│ │ ├── __init__.py +│ │ └── client.py +│ │ +│ ├── config.py +│ └── main.py +│ +├── .env +└── requirements.txt +``` + +`.env` is local-only and must never be committed. + +--- + +## Test Data + +The project contains three DOCX files used by the redline workflow: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +### Dealer Agreement + +The dealer-returned agreement contains four relevant deviations: + +``` +Section 4.2 — Payment Terms +Section 7.1 — Agreement Term +Section 9.3 — Territory +Section 12.1 — Termination +``` + +### Master Agreement + +The master agreement provides the approved baseline language. + +### Negotiation Playbook + +The negotiation playbook defines the allowed ranges and escalation/refusal rules. + +--- + +## Current Example Analysis + +For the provided test agreements, the backend produces: + +``` +Section 4.2 — Payment Terms +→ ESCALATE + +Section 7.1 — Agreement Term +→ REFUSE + +Section 9.3 — Territory +→ ESCALATE + +Section 12.1 — Termination +→ REFUSE +``` + +Summary: + +```json +{ + "total": 4, + "pre_approved": 0, + "escalate": 2, + "refuse": 2 +} +``` + +A human reviewer can approve the two escalation findings and reject the two refusal findings. + +The resulting workflow is: + +``` +Payment Terms +→ APPROVE +→ APPLY + +Agreement Term +→ REJECT +→ IGNORE + +Territory +→ APPROVE +→ APPLY + +Termination +→ REJECT +→ IGNORE +``` + +Only the two approved findings are sent to SuperDocs. + +--- + +## SuperDocs Integration + +The backend contains a dedicated `SuperDocsClient`. + +The client supports: + +- API key authentication +- API key verification +- Document upload +- Attachment upload +- Chat +- Human-in-the-loop approval +- Document export + +### SuperDocs Authentication + +The SuperDocs API uses the `Authorization` header: + +```http +Authorization: Bearer sk_YOUR_API_KEY +``` + +The API key is stored in the backend environment: + +```env +SUPERDOCS_API_KEY=sk_YOUR_API_KEY +``` + +The browser never receives this key. + +### API Key Verification + +SuperDocs provides: + +```http +GET /v1/sessions +``` + +as a safe way to verify an API key. + +The backend's SuperDocs client uses: + +``` +GET /v1/sessions +``` + +for verification. + +A successful `200` response confirms that the API key is valid. + +--- + +## Documents vs Attachments + +SuperDocs provides two different ways to load files. + +### Active Document + +The dealer agreement is uploaded using: + +``` +/v1/documents/upload +``` + +This makes it the active editable document. + +### Attachments + +The master agreement and negotiation playbook are uploaded using: + +``` +/v1/attachments/upload +``` + +These provide reference material for the workflow. + +The distinction is: + +``` +Dealer Agreement +→ Active editable document + +Master Agreement +→ Reference attachment + +Negotiation Playbook +→ Reference attachment +``` + +Attachments are useful as supporting context, while the document upload is used for the document that will actually be edited. + +--- + +## SuperDocs Session + +A unique SuperDocs session is created for each redline analysis. + +The same `session_id` is persisted in `RedlineReview`. + +This connects the application review to the SuperDocs active document: + +``` +RedlineReview + │ + └── session_id + │ + ▼ + SuperDocs Session + │ + ▼ + Active Dealer Document +``` + +When `/apply` is called, the backend uses the stored session ID to modify the correct active SuperDocs document. + +--- + +## SuperDocs Chat + +The backend uses SuperDocs chat/document editing capabilities to apply approved counter-positions. + +Only human-approved findings are passed to the application service. + +The frontend does not communicate directly with the SuperDocs chat API. + +--- + +## Human-in-the-Loop + +The application has its own human review layer. + +The workflow is: + +``` +AI / Backend Analysis + │ + ▼ +Redline Findings + │ + ▼ +Human Decision + ┌───┴───┐ + │ │ +Approve Reject + │ │ + ▼ ▼ + Apply Ignore +``` + +SuperDocs also supports human-in-the-loop approval for document changes. + +For the tested workflow, SuperDocs returned the applied edits as: + +``` +status: auto_approved +``` + +with: + +``` +requires_approval: null +pending_changes: null +``` + +Therefore, no additional SuperDocs approval call was required for the tested changes. + +The application does not artificially call the SuperDocs approval endpoint when SuperDocs reports that there are no pending changes requiring approval. + +--- + +## Applied Changes + +For the tested review, two findings were approved. + +SuperDocs returned: + +``` +2 sections edited, rest untouched +``` + +### Section 4.2 — Payment Terms + +Original dealer language: + +``` +Dealer payments are due within 60 days of invoice. +``` + +Applied language: + +``` +Dealer payments are due within 30 days of invoice. +Any request for 60-day terms must be escalated for approval. +``` + +### Section 9.3 — Territory + +Original dealer language: + +``` +Dealer shall operate within the assigned territory +and may sell to customers outside the territory. +``` + +Applied language: + +``` +Dealer shall operate within the assigned territory. +Expansion of territory is prohibited unless approved +through escalation. +``` + +The rejected findings remained unchanged. + +--- + +## API Endpoints + +### 1. Analyze Agreement + +```http +POST /redline/analyze +``` + +Creates a SuperDocs session, uploads the required documents, analyzes the agreements, and persists a redline review. + +Example: + +```powershell +curl -X POST http://localhost:8000/redline/analyze +``` + +Response includes: + +``` +review_id +session_id +dealer_document +master_attachment +playbook_attachment +master_sections +dealer_sections +playbook_sections +deviations +summary +``` + +### 2. Get Review + +```http +GET /redline/reviews/{review_id} +``` + +Returns: + +- review status +- SuperDocs session ID +- timestamps +- all findings +- classification +- reason +- counter-position +- human decision + +Example: + +```powershell +curl http://localhost:8000/redline/reviews/REVIEW_ID +``` + +### 3. Decide Finding + +```http +POST /redline/reviews/{review_id}/findings/{finding_id}/decision +``` + +Approve: + +```json +{ + "decision": "approve" +} +``` + +Reject: + +```json +{ + "decision": "reject" +} +``` + +The backend normalizes the decision using: + +``` +strip() +lower() +``` + +Therefore: + +``` +APPROVE +Approve + approve +``` + +are normalized to: + +``` +approve +``` + +Only `approve` and `reject` are accepted. + +### 4. Apply Approved Findings + +```http +POST /redline/reviews/{review_id}/apply +``` + +This endpoint: + +1. Loads the review. +2. Checks that the review has not already been applied. +3. Checks that no findings remain pending. +4. Loads all findings. +5. Passes approved findings to the SuperDocs redline service. +6. Applies approved changes to the active document. +7. Updates the review status to `applied`. + +Example: + +```powershell +curl -X POST http://localhost:8000/redline/reviews/REVIEW_ID/apply +``` + +A review cannot be applied twice. + +Attempting to apply an already-applied review returns HTTP `409`. + +### 5. Export + +```http +GET /redline/reviews/{review_id}/export +``` + +Exports the currently applied SuperDocs document. + +The generated file is: + +``` +dealer-agreement-redlined.docx +``` + +Example: + +```powershell +curl -X GET http://localhost:8000/redline/reviews/REVIEW_ID/export -o dealer-agreement-redlined.docx +``` + +Export is blocked until the review status is: + +``` +applied +``` + +--- + +## Validation Rules + +The backend currently validates: + +- Required test files exist. +- Review exists before review operations. +- Finding exists before a decision is made. +- Finding belongs to the requested review. +- Finding cannot be decided twice. +- Completed reviews cannot receive new decisions. +- Applied reviews cannot receive new decisions. +- Pending findings prevent applying. +- Already-applied reviews cannot be applied again. +- Export requires an applied review. +- SuperDocs API errors are handled by the backend. +- Invalid decisions return HTTP `400`. +- Missing reviews/findings return HTTP `404`. +- Invalid workflow state returns HTTP `409`. +- SuperDocs failures return HTTP `502`. + +--- + +## Error Responses + +### Invalid Decision + +```json +{ + "detail": "Decision must be either 'approve' or 'reject'." +} +``` + +HTTP: `400 Bad Request` + +### Review Not Found + +```json +{ + "detail": "Redline review not found" +} +``` + +HTTP: `404 Not Found` + +### Finding Already Reviewed + +```json +{ + "detail": "This finding has already been reviewed." +} +``` + +HTTP: `409 Conflict` + +### Pending Findings + +```json +{ + "detail": "2 finding(s) still require review." +} +``` + +HTTP: `409 Conflict` + +### Review Already Applied + +```json +{ + "detail": "This review has already been applied." +} +``` + +HTTP: `409 Conflict` + +### Export Before Apply + +```json +{ + "detail": "The review must be applied before exporting." +} +``` + +HTTP: `409 Conflict` + +### SuperDocs Error + +SuperDocs failures are returned as: + +``` +502 Bad Gateway +``` + +--- + +## Environment Variables + +Create: + +``` +backend/.env +``` + +Example: + +```env +DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5432/dealer_redline +SUPERDOCS_API_KEY=sk_YOUR_API_KEY +``` + +Never commit `.env`. + +The `.env` file contains secrets and must remain local. + +--- + +## Backend Setup + +From the project root: + +```powershell +cd backend +``` + +Create a virtual environment: + +```powershell +python -m venv .venv +``` + +Activate it on Windows PowerShell: + +```powershell +.\venv\Scripts\Activate.ps1 +``` + +Install dependencies: + +```powershell +pip install -r requirements.txt +``` + +Start the backend: + +```powershell +uvicorn app.main:app --reload +``` + +The API runs at: + +``` +http://127.0.0.1:8000 +``` + +Swagger documentation: + +``` +http://127.0.0.1:8000/docs +``` + +--- + +## Backend Verification + +Python compilation can be checked with: + +```powershell +python -m compileall app +``` + +A successful result finishes without syntax errors. + +The current backend has been verified through the following workflow: + +``` +POST /redline/analyze + ↓ +GET /redline/reviews/{review_id} + ↓ +POST /redline/reviews/{review_id}/findings/{finding_id}/decision + ↓ +remaining_findings = 0 + ↓ +POST /redline/reviews/{review_id}/apply + ↓ +status = applied + ↓ +GET /redline/reviews/{review_id}/export + ↓ +DOCX successfully exported +``` + +The duplicate-apply protection was also tested successfully. + +Attempting to apply an already-applied review returns HTTP `409`. + +--- + +## Backend Test Results + +The following functionality has been tested successfully: + +### Agreement Analysis + +- ✓ Master agreement parsed +- ✓ Dealer agreement parsed +- ✓ Negotiation playbook parsed +- ✓ Deviations detected +- ✓ Deviations classified + +### Database + +- ✓ Redline review persisted +- ✓ Redline findings persisted +- ✓ Review retrieved +- ✓ Finding decisions persisted + +### Human Review + +- ✓ Finding approve +- ✓ Finding reject +- ✓ Pending finding count +- ✓ Review transitions to completed +- ✓ Duplicate decision prevented + +### SuperDocs + +- ✓ Dealer document uploaded +- ✓ Master attachment uploaded +- ✓ Playbook attachment uploaded +- ✓ Approved redlines applied +- ✓ Rejected redlines left unchanged +- ✓ Final document exported + +### Workflow Protection + +- ✓ Duplicate apply prevented +- ✓ Apply blocked while findings are pending +- ✓ Decision blocked after review completion +- ✓ Decision blocked after review application +- ✓ Export blocked before apply + +--- + +## Frontend + +The frontend is maintained separately from the backend. + +The frontend directory is: + +``` +frontend/ +``` + +The frontend will be built using React and TypeScript with Vite. + +Planned structure: + +``` +frontend/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── index.html +└── src/ + ├── main.tsx + ├── App.tsx + ├── api/ + │ └── redline.ts + ├── components/ + │ ├── ReviewSummary.tsx + │ ├── FindingCard.tsx + │ └── ReviewActions.tsx + └── types/ + └── redline.ts +``` + +### Planned Frontend Workflow + +``` +Start Review + │ + ▼ +POST /redline/analyze + │ + ▼ +Review Dashboard + │ + ├── View summary + ├── View master language + ├── View dealer language + ├── View classification + ├── View reason + └── View counter-position + │ + ▼ +Approve / Reject findings + │ + ▼ +All findings reviewed + │ + ▼ +Apply Approved Changes + │ + ▼ +Export DOCX +``` + +### Planned Frontend UI + +``` +┌────────────────────────────────────────────────────────────┐ +│ Dealer Agreement Redline │ +│ Review and approve contract changes │ +├────────────────────────────────────────────────────────────┤ +│ │ +│ Review Summary │ +│ │ +│ ┌─────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ │ +│ │ 4 │ │ 2 │ │ 2 │ │ 0 │ │ +│ │Total│ │Escalate │ │Refuse │ │Reviewed │ │ +│ └─────┘ └──────────┘ └────────┘ └──────────┘ │ +│ │ +├────────────────────────────────────────────────────────────┤ +│ │ +│ Section 4.2 — Payment Terms ESCALATE │ +│ │ +│ Master: │ +│ Dealer payments are due within 30 days of invoice. │ +│ │ +│ Dealer: │ +│ Dealer payments are due within 60 days of invoice. │ +│ │ +│ Reason: │ +│ Payment terms exceed the 45-day pre-approved limit. │ +│ │ +│ Counter-position: │ +│ Maintain the standard 30-day payment term or │ +│ escalate the requested 60-day term for approval. │ +│ │ +│ [ Reject ] [ Approve ] │ +│ │ +├────────────────────────────────────────────────────────────┤ +│ [ Apply Approved Changes ] │ +│ [ Export DOCX ] │ +└────────────────────────────────────────────────────────────┘ +``` + +### Frontend API Flow + +The frontend will consume the following backend APIs: + +``` +React + │ + ├── POST /redline/analyze + │ + ├── GET /redline/reviews/{review_id} + │ + ├── POST /redline/reviews/{review_id}/findings/{finding_id}/decision + │ + ├── POST /redline/reviews/{review_id}/apply + │ + └── GET /redline/reviews/{review_id}/export + │ + ▼ + FastAPI + │ + ├── PostgreSQL + │ + └── SuperDocs API +``` + +The frontend never calls: + +``` +https://api.superdocs.app +``` + +directly. + +--- + +## Security + +The SuperDocs API key is server-side only. + +The frontend must never contain: + +``` +SUPERDOCS_API_KEY +``` + +The frontend must never make direct requests to: + +``` +https://api.superdocs.app +``` + +All SuperDocs operations must go through the FastAPI backend. + +The `.env` file must never be committed to Git. + +--- + +## Git Ignore + +The repository should ignore local environment files and generated files: + +```gitignore +# Environment +.env +.env.* + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node +node_modules/ +dist/ + +# IDE +.vscode/ +.idea/ + +# Generated export +dealer-agreement-redlined.docx +``` + +Do not use a blanket: + +```gitignore +*.docx +``` + +rule. + +The test DOCX files under `test-data/` are required project files and should remain tracked: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +--- + +## Project Directory + +The current project structure is: + +``` +dealer-agreement-redline/ +│ +├── backend/ +│ ├── app/ +│ ├── .env +│ └── requirements.txt +│ +├── frontend/ +│ +├── test-data/ +│ ├── dealer-agreement.docx +│ ├── master-agreement.docx +│ └── negotiation-playbook.docx +│ +├── .gitignore +└── README.md +``` + +--- + +## Development Notes + +This project is a focused proof of concept for the dealer-agreement redline workflow. + +The backend currently provides the complete functional workflow: + +``` +Analyze + ↓ +Persist Review + ↓ +Review Findings + ↓ +Approve / Reject + ↓ +Apply Approved Changes + ↓ +Export DOCX +``` + +The backend is complete and tested before starting frontend implementation. + +The frontend will consume the existing backend APIs and provide the user-facing review interface. + +The SuperDocs API key remains completely server-side. + +--- + +## Current Milestone + +### Task 2 Backend + +``` +STATUS: COMPLETE +``` + +Completed: + +- ✓ SuperDocs integration +- ✓ Document uploads +- ✓ Attachment uploads +- ✓ Agreement parsing +- ✓ Deviation analysis +- ✓ Playbook classification +- ✓ PostgreSQL persistence +- ✓ Human review +- ✓ Approve/reject decisions +- ✓ Apply approved changes +- ✓ Export DOCX +- ✓ Workflow validation +- ✓ Duplicate apply protection +- ✓ Backend verification + +### Next + +- Build the React frontend (dashboard, finding cards, comparison view, approve/reject controls, apply and export actions). diff --git a/use-cases/dealer-agreement-redline/backend/app/api/poc.py b/use-cases/dealer-agreement-redline/backend/app/api/poc.py new file mode 100644 index 00000000..d6b838b9 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/api/poc.py @@ -0,0 +1,142 @@ +import uuid +from pathlib import Path + +from fastapi import APIRouter, HTTPException + +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/poc", + tags=["poc"], +) + + +TEST_DATA = ( + Path(__file__).resolve().parents[3] + / "test-data" +) + + +@router.post("/dealer-redline") +async def dealer_redline_poc(): + """ + End-to-end SuperDocs POC. + + Current flow: + 1. Upload dealer agreement as active document. + 2. Upload master agreement as attachment. + 3. Upload negotiation playbook as attachment. + 4. Ask SuperDocs to analyze the agreement. + 5. Return the raw chat response. + + We intentionally do not approve or export yet. + First we inspect the exact proposed-change response. + """ + + dealer_file = ( + TEST_DATA / "dealer-agreement.docx" + ) + + master_file = ( + TEST_DATA / "master-agreement.docx" + ) + + playbook_file = ( + TEST_DATA / "negotiation-playbook.docx" + ) + + for file in ( + dealer_file, + master_file, + playbook_file, + ): + if not file.exists(): + raise HTTPException( + status_code=404, + detail=f"Test file not found: {file}", + ) + + client = SuperDocsClient() + + session_id = str(uuid.uuid4()) + + try: + # -------------------------------------------------- + # 1. Upload dealer agreement as active document. + # -------------------------------------------------- + + dealer_document = ( + await client.upload_document( + file_path=dealer_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 2. Upload master agreement as reference. + # -------------------------------------------------- + + master_attachment = ( + await client.upload_attachment( + file_path=master_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 3. Upload negotiation playbook as reference. + # -------------------------------------------------- + + playbook_attachment = ( + await client.upload_attachment( + file_path=playbook_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 4. Ask SuperDocs to analyze the dealer agreement. + # -------------------------------------------------- + + instruction = """ +Compare the active dealer agreement against the approved +master agreement and evaluate all substantive deviations +using the negotiation playbook. + +For every deviation: + +1. Identify the agreement section. +2. Describe the substantive change. +3. Explain the difference between the master language + and dealer language. +4. Determine whether the change is PRE-APPROVED, + ESCALATE, or REFUSE according to the playbook. +5. Draft a concise counter-position. + +Do not make any final changes to the document yet. + +Return the proposed changes for human review. +""".strip() + + chat_response = await client.chat( + session_id=session_id, + message=instruction, + ) + + return { + "session_id": session_id, + "dealer_document": dealer_document, + "master_attachment": master_attachment, + "playbook_attachment": playbook_attachment, + "chat_response": chat_response, + } + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/api/redline.py b/use-cases/dealer-agreement-redline/backend/app/api/redline.py new file mode 100644 index 00000000..cfd5993c --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/api/redline.py @@ -0,0 +1,555 @@ +import uuid +from pathlib import Path + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import Response +from pydantic import BaseModel +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.db.session import get_db +from app.models.redline_review import ( + RedlineFinding, + RedlineReview, +) +from app.services.redline_service import ( + analyze_agreements, + create_redline_review, +) +from app.services.superdocs_redline import ( + apply_approved_redlines, +) +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/redline", + tags=["redline"], +) + + +TEST_DATA = ( + Path(__file__).resolve().parents[3] + / "test-data" +) + + +class FindingDecisionRequest(BaseModel): + decision: str + + +@router.post("/analyze") +async def analyze_redline( + db: AsyncSession = Depends(get_db), +): + """ + Run the complete dealer agreement redline analysis. + + Flow: + 1. Create a SuperDocs session. + 2. Upload dealer agreement as active document. + 3. Upload master agreement as reference attachment. + 4. Upload negotiation playbook as reference attachment. + 5. Analyze the agreements locally. + 6. Persist the review and findings. + 7. Return the review and SuperDocs session IDs. + """ + + master_file = ( + TEST_DATA / "master-agreement.docx" + ) + + dealer_file = ( + TEST_DATA / "dealer-agreement.docx" + ) + + playbook_file = ( + TEST_DATA / "negotiation-playbook.docx" + ) + + for file in ( + master_file, + dealer_file, + playbook_file, + ): + if not file.exists(): + raise HTTPException( + status_code=404, + detail=f"File not found: {file}", + ) + + client = SuperDocsClient() + + # One session is used for the entire workflow. + session_id = str(uuid.uuid4()) + + try: + # -------------------------------------------------- + # 1. Upload dealer agreement as active document. + # -------------------------------------------------- + + dealer_document = ( + await client.upload_document( + file_path=dealer_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 2. Upload master agreement as reference. + # -------------------------------------------------- + + master_attachment = ( + await client.upload_attachment( + file_path=master_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 3. Upload negotiation playbook as reference. + # -------------------------------------------------- + + playbook_attachment = ( + await client.upload_attachment( + file_path=playbook_file, + session_id=session_id, + ) + ) + + # -------------------------------------------------- + # 4. Run local redline analysis. + # -------------------------------------------------- + + analysis = analyze_agreements( + master_path=master_file, + dealer_path=dealer_file, + playbook_path=playbook_file, + ) + + # -------------------------------------------------- + # 5. Persist review. + # + # IMPORTANT: + # Save the SAME SuperDocs session ID. + # This allows /apply to operate on the document + # that was actually uploaded above. + # -------------------------------------------------- + + review = await create_redline_review( + db=db, + session_id=session_id, + analysis=analysis, + ) + + return { + "review_id": str(review.id), + "session_id": session_id, + "dealer_document": dealer_document, + "master_attachment": master_attachment, + "playbook_attachment": playbook_attachment, + **analysis, + } + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) + + except Exception as exc: + raise HTTPException( + status_code=500, + detail=str(exc), + ) + + +@router.get("/reviews/{review_id}") +async def get_redline_review( + review_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + """ + Get a redline review and all of its findings. + """ + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + result = await db.execute( + select(RedlineFinding) + .where( + RedlineFinding.review_id == review_id + ) + .order_by(RedlineFinding.created_at) + ) + + findings = result.scalars().all() + + return { + "review_id": str(review.id), + "session_id": review.session_id, + "status": review.status, + "created_at": review.created_at, + "updated_at": review.updated_at, + "findings": [ + { + "id": str(finding.id), + "section": finding.section, + "master_text": finding.master_text, + "dealer_text": finding.dealer_text, + "deviation_type": finding.deviation_type, + "similarity": finding.similarity, + "classification": finding.classification, + "reason": finding.reason, + "counter_position": finding.counter_position, + "decision": finding.decision, + } + for finding in findings + ], + } + + +@router.post( + "/reviews/{review_id}/findings/{finding_id}/decision" +) +async def decide_redline_finding( + review_id: uuid.UUID, + finding_id: uuid.UUID, + payload: FindingDecisionRequest, + db: AsyncSession = Depends(get_db), +): + """ + Approve or reject an individual redline finding. + + A finding can only be decided once. + Decisions cannot be changed after the review + has been completed or applied. + """ + + # -------------------------------------------------- + # Normalize the decision. + # -------------------------------------------------- + + decision = payload.decision.strip().lower() + + if decision not in { + "approve", + "reject", + }: + raise HTTPException( + status_code=400, + detail=( + "Decision must be either " + "'approve' or 'reject'." + ), + ) + + # -------------------------------------------------- + # Verify review exists. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Prevent decisions after review completion. + # -------------------------------------------------- + + if review.status in { + "completed", + "applied", + }: + raise HTTPException( + status_code=409, + detail=( + "This review has already been " + "completed." + ), + ) + + # -------------------------------------------------- + # Find the requested finding. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.id == finding_id, + RedlineFinding.review_id == review_id, + ) + ) + + finding = result.scalar_one_or_none() + + if finding is None: + raise HTTPException( + status_code=404, + detail="Redline finding not found", + ) + + # -------------------------------------------------- + # Prevent changing an already-reviewed finding. + # -------------------------------------------------- + + if finding.decision != "pending": + raise HTTPException( + status_code=409, + detail=( + "This finding has already been " + "reviewed." + ), + ) + + finding.decision = decision + + # -------------------------------------------------- + # Check whether findings remain. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.review_id == review_id, + RedlineFinding.decision == "pending", + ) + ) + + remaining = result.scalars().all() + + if remaining: + review.status = "in_review" + else: + review.status = "completed" + + await db.commit() + await db.refresh(finding) + + return { + "review_id": str(review_id), + "finding_id": str(finding_id), + "decision": finding.decision, + "review_status": review.status, + "remaining_findings": len(remaining), + } + + +@router.post( + "/reviews/{review_id}/apply" +) +async def apply_review( + review_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + """ + Apply human-approved redlines to the active + SuperDocs document. + + All findings must have been reviewed first. + """ + + # -------------------------------------------------- + # Get review. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Prevent applying the same review twice. + # -------------------------------------------------- + + if review.status == "applied": + raise HTTPException( + status_code=409, + detail="This review has already been applied.", + ) + + # -------------------------------------------------- + # Make sure every finding has a decision. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding).where( + RedlineFinding.review_id == review_id, + RedlineFinding.decision == "pending", + ) + ) + + pending = result.scalars().all() + + if pending: + raise HTTPException( + status_code=409, + detail=( + f"{len(pending)} finding(s) " + "still require review." + ), + ) + + # -------------------------------------------------- + # Load all findings. + # -------------------------------------------------- + + result = await db.execute( + select(RedlineFinding) + .where( + RedlineFinding.review_id == review_id + ) + .order_by(RedlineFinding.created_at) + ) + + findings = result.scalars().all() + + finding_data = [ + { + "id": str(finding.id), + "section": finding.section, + "dealer_text": finding.dealer_text, + "counter_position": finding.counter_position, + "decision": finding.decision, + "classification": finding.classification, + } + for finding in findings + ] + + # -------------------------------------------------- + # Apply approved findings to SuperDocs. + # + # apply_approved_redlines() filters out findings + # whose decision is not "approve". + # -------------------------------------------------- + + client = SuperDocsClient() + + try: + result = await apply_approved_redlines( + client=client, + session_id=review.session_id, + findings=finding_data, + ) + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) + + except Exception as exc: + raise HTTPException( + status_code=502, + detail=f"SuperDocs error: {exc}", + ) + + review.status = "applied" + + await db.commit() + + return { + "review_id": str(review_id), + "session_id": review.session_id, + "status": review.status, + **result, + } + + +@router.get( + "/reviews/{review_id}/export" +) +async def export_review( + review_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + """ + Export the applied dealer agreement as DOCX. + """ + + result = await db.execute( + select(RedlineReview).where( + RedlineReview.id == review_id + ) + ) + + review = result.scalar_one_or_none() + + if review is None: + raise HTTPException( + status_code=404, + detail="Redline review not found", + ) + + # -------------------------------------------------- + # Export is only allowed after apply. + # -------------------------------------------------- + + if review.status != "applied": + raise HTTPException( + status_code=409, + detail=( + "The review must be applied " + "before exporting." + ), + ) + + client = SuperDocsClient() + + filename = "dealer-agreement-redlined.docx" + + try: + document = await client.export_document( + session_id=review.session_id, + filename=filename, + ) + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) + + return Response( + content=document, + media_type=( + "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.document" + ), + headers={ + "Content-Disposition": ( + f'attachment; filename="{filename}"' + ), + }, + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/api/superdocs.py b/use-cases/dealer-agreement-redline/backend/app/api/superdocs.py new file mode 100644 index 00000000..9d7d3272 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/api/superdocs.py @@ -0,0 +1,32 @@ +from fastapi import APIRouter, HTTPException + +from app.superdocs.client import ( + SuperDocsClient, + SuperDocsError, +) + + +router = APIRouter( + prefix="/superdocs", + tags=["superdocs"], +) + + +@router.get("/health") +async def superdocs_health(): + client = SuperDocsClient() + + try: + sessions = await client.verify_key() + + return { + "status": "ok", + "superdocs": "connected", + "sessions": sessions, + } + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=str(exc), + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/config.py b/use-cases/dealer-agreement-redline/backend/app/config.py new file mode 100644 index 00000000..7bea1ca0 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/config.py @@ -0,0 +1,18 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + superdocs_api_key: str + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + +@lru_cache +def get_settings() -> Settings: + return Settings() \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/db/base.py b/use-cases/dealer-agreement-redline/backend/app/db/base.py new file mode 100644 index 00000000..1c2dcc40 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/db/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/db/init_db.py b/use-cases/dealer-agreement-redline/backend/app/db/init_db.py new file mode 100644 index 00000000..87e1281c --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/db/init_db.py @@ -0,0 +1,14 @@ +from app.db.base import Base +from app.db.session import engine + +from app.models.redline_review import ( + RedlineReview, + RedlineFinding, +) + + +async def init_db(): + async with engine.begin() as connection: + await connection.run_sync( + Base.metadata.create_all + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/db/session.py b/use-cases/dealer-agreement-redline/backend/app/db/session.py new file mode 100644 index 00000000..830045f6 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/db/session.py @@ -0,0 +1,37 @@ +import os + +from dotenv import load_dotenv +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) + + +load_dotenv() + + +DATABASE_URL = os.getenv("DATABASE_URL") + +if not DATABASE_URL: + raise RuntimeError( + "DATABASE_URL environment variable is not set" + ) + + +engine = create_async_engine( + DATABASE_URL, + echo=False, +) + + +AsyncSessionLocal = async_sessionmaker( + engine, + class_=AsyncSession, + expire_on_commit=False, +) + + +async def get_db(): + async with AsyncSessionLocal() as session: + yield session \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/main.py b/use-cases/dealer-agreement-redline/backend/app/main.py new file mode 100644 index 00000000..77f4a2fd --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/main.py @@ -0,0 +1,29 @@ +from fastapi import FastAPI + +from app.api.redline import router as redline_router +from app.api.poc import router as poc_router +from app.api.superdocs import router as superdocs_router +from contextlib import asynccontextmanager + +from app.db.init_db import init_db + +@asynccontextmanager +async def lifespan(app: FastAPI): + await init_db() + yield + +app = FastAPI( + title="Dealer Agreement Redline Desk", + lifespan=lifespan, +) + + +app.include_router(superdocs_router) +app.include_router(poc_router) +app.include_router(redline_router) + +@app.get("/health") +async def health(): + return { + "status": "ok", + } \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/models/redline_review.py b/use-cases/dealer-agreement-redline/backend/app/models/redline_review.py new file mode 100644 index 00000000..f70b3df0 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/models/redline_review.py @@ -0,0 +1,114 @@ +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlalchemy import DateTime, ForeignKey, String, Text, func +from sqlalchemy.dialects.postgresql import UUID as PG_UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +class RedlineReview(Base): + __tablename__ = "redline_reviews" + + id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + primary_key=True, + default=uuid4, + ) + + session_id: Mapped[str] = mapped_column( + String(255), + nullable=False, + index=True, + ) + + status: Mapped[str] = mapped_column( + String(50), + nullable=False, + default="pending", + ) + + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + + +class RedlineFinding(Base): + __tablename__ = "redline_findings" + + id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + primary_key=True, + default=uuid4, + ) + + review_id: Mapped[UUID] = mapped_column( + PG_UUID(as_uuid=True), + ForeignKey( + "redline_reviews.id", + ondelete="CASCADE", + ), + nullable=False, + index=True, + ) + + section: Mapped[str] = mapped_column( + String(255), + nullable=False, + ) + + master_text: Mapped[str | None] = mapped_column( + Text, + nullable=True, + ) + + dealer_text: Mapped[str | None] = mapped_column( + Text, + nullable=True, + ) + + deviation_type: Mapped[str] = mapped_column( + String(50), + nullable=False, + ) + + similarity: Mapped[float | None] = mapped_column( + nullable=True, + ) + + classification: Mapped[str] = mapped_column( + String(50), + nullable=False, + ) + + reason: Mapped[str] = mapped_column( + Text, + nullable=False, + ) + + counter_position: Mapped[str] = mapped_column( + Text, + nullable=False, + ) + + decision: Mapped[str] = mapped_column( + String(50), + nullable=False, + default="pending", + ) + + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py b/use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py new file mode 100644 index 00000000..5a70343b --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py @@ -0,0 +1,44 @@ +from pathlib import Path + +from docx import Document + + +def extract_docx_sections(file_path: str | Path) -> list[dict]: + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Document not found: {path}" + ) + + document = Document(path) + + sections: list[dict] = [] + current_section: dict | None = None + + for paragraph in document.paragraphs: + text = paragraph.text.strip() + + if not text: + continue + + # Our test documents use Heading 2 for sections. + if paragraph.style.name.startswith("Heading 2"): + if current_section: + sections.append(current_section) + + current_section = { + "section": text, + "text": "", + } + + elif current_section: + if current_section["text"]: + current_section["text"] += "\n" + + current_section["text"] += text + + if current_section: + sections.append(current_section) + + return sections \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py b/use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py new file mode 100644 index 00000000..f8a730bd --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py @@ -0,0 +1,69 @@ +from difflib import SequenceMatcher + + +def normalize(text: str) -> str: + return " ".join(text.lower().split()) + + +def analyze_deviations( + master_sections: list[dict], + dealer_sections: list[dict], +) -> list[dict]: + + master_by_section = { + item["section"]: item["text"] + for item in master_sections + } + + dealer_by_section = { + item["section"]: item["text"] + for item in dealer_sections + } + + deviations: list[dict] = [] + + for section, dealer_text in dealer_by_section.items(): + master_text = master_by_section.get(section) + + if master_text is None: + deviations.append( + { + "section": section, + "master_text": None, + "dealer_text": dealer_text, + "deviation_type": "added_section", + } + ) + continue + + if normalize(master_text) == normalize(dealer_text): + continue + + similarity = SequenceMatcher( + None, + normalize(master_text), + normalize(dealer_text), + ).ratio() + + deviations.append( + { + "section": section, + "master_text": master_text, + "dealer_text": dealer_text, + "deviation_type": "modified_section", + "similarity": round(similarity, 3), + } + ) + + for section, master_text in master_by_section.items(): + if section not in dealer_by_section: + deviations.append( + { + "section": section, + "master_text": master_text, + "dealer_text": None, + "deviation_type": "removed_section", + } + ) + + return deviations \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py b/use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py new file mode 100644 index 00000000..07ba1eb3 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py @@ -0,0 +1,210 @@ +import re + + +def extract_section_number(section: str) -> str | None: + """ + Extract the numeric section identifier. + + Examples: + Section 7.1 — AgreementTerm -> 7.1 + Section 9.3— Territory -> 9.3 + Section 12.1 — Termination -> 12.1 + """ + match = re.search( + r"section\s+(\d+(?:\.\d+)*)", + section, + re.IGNORECASE, + ) + + if not match: + return None + + return match.group(1) + + +def classify_deviation( + deviation: dict, +) -> dict: + + section = deviation["section"] + dealer_text = ( + deviation.get("dealer_text") or "" + ).lower() + + section_number = extract_section_number(section) + + classification = "ESCALATE" + reason = "The change requires human review." + counter_position = ( + "Maintain the approved master agreement language." + ) + + # -------------------------------------------------- + # Section 4.2 — Payment Terms + # -------------------------------------------------- + + if section_number == "4.2": + + match = re.search( + r"(\d+)\s*days", + dealer_text, + ) + + if match: + days = int(match.group(1)) + + if days <= 45: + classification = "PRE-APPROVED" + reason = ( + "The playbook permits payment terms " + "up to 45 days." + ) + counter_position = ( + f"Accept payment terms of {days} days." + ) + + else: + classification = "ESCALATE" + reason = ( + "The dealer requested payment terms " + "above the 45-day pre-approved limit." + ) + counter_position = ( + "Maintain the standard 30-day payment " + "term or escalate the requested " + f"{days}-day term for approval." + ) + + # -------------------------------------------------- + # Section 7.1 — Agreement Term + # -------------------------------------------------- + + elif section_number == "7.1": + + year_match = re.search( + r"(\d+)\s*years?", + dealer_text, + ) + + if year_match: + years = int(year_match.group(1)) + + else: + word_to_number = { + "one": 1, + "two": 2, + "three": 3, + "four": 4, + "five": 5, + "six": 6, + "seven": 7, + "eight": 8, + "nine": 9, + "ten": 10, + "eleven": 11, + "twelve": 12, + "thirteen": 13, + "fourteen": 14, + "fifteen": 15, + } + + years = None + + for word, value in word_to_number.items(): + if re.search( + rf"\b{word}\s+years?\b", + dealer_text, + ): + years = value + break + + if years is not None: + + if years <= 7: + classification = "PRE-APPROVED" + + reason = ( + "The playbook permits agreement terms " + "up to 7 years." + ) + + counter_position = ( + f"Accept the {years}-year agreement term." + ) + + else: + classification = "REFUSE" + + reason = ( + "The requested agreement term exceeds " + "the 7-year playbook limit." + ) + + counter_position = ( + "Maintain the approved five-year " + "initial agreement term." + ) + + # -------------------------------------------------- + # Section 9.3 — Territory + # -------------------------------------------------- + + elif section_number == "9.3": + + if ( + "outside" in dealer_text + or "expand" in dealer_text + ): + classification = "ESCALATE" + reason = ( + "The dealer requests an expansion " + "of territory rights." + ) + counter_position = ( + "Maintain the assigned territory unless " + "the requested expansion is approved " + "through escalation." + ) + + # -------------------------------------------------- + # Section 12.1 — Termination + # -------------------------------------------------- + + elif section_number == "12.1": + + match = re.search( + r"(\d+)\s*days", + dealer_text, + ) + + if match: + days = int(match.group(1)) + + if days > 60: + classification = "REFUSE" + reason = ( + "The requested termination notice " + "period exceeds the 60-day limit." + ) + counter_position = ( + "Maintain the approved 30-day " + "termination notice period." + ) + + else: + classification = "ESCALATE" + reason = ( + "The termination notice change " + "requires review." + ) + counter_position = ( + "Review the requested termination " + "notice period before acceptance." + ) + + return { + **deviation, + "classification": classification, + "reason": reason, + "counter_position": counter_position, + } \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/services/redline_service.py b/use-cases/dealer-agreement-redline/backend/app/services/redline_service.py new file mode 100644 index 00000000..b26ac613 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/services/redline_service.py @@ -0,0 +1,115 @@ +from pathlib import Path + +from app.services.agreement_parser import ( + extract_docx_sections, +) +from app.services.deviation_analyzer import ( + analyze_deviations, +) +from app.services.playbook_classifier import ( + classify_deviation, +) + +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.redline_review import ( + RedlineFinding, + RedlineReview, +) + + +def analyze_agreements( + master_path: str | Path, + dealer_path: str | Path, + playbook_path: str | Path, +) -> dict: + + master_sections = extract_docx_sections( + master_path + ) + + dealer_sections = extract_docx_sections( + dealer_path + ) + + playbook_sections = extract_docx_sections( + playbook_path + ) + + deviations = analyze_deviations( + master_sections=master_sections, + dealer_sections=dealer_sections, + ) + + classified = [ + classify_deviation(deviation) + for deviation in deviations + ] + + return { + "master_sections": master_sections, + "dealer_sections": dealer_sections, + "playbook_sections": playbook_sections, + "deviations": classified, + "summary": { + "total": len(classified), + "pre_approved": sum( + 1 + for item in classified + if item["classification"] + == "PRE-APPROVED" + ), + "escalate": sum( + 1 + for item in classified + if item["classification"] + == "ESCALATE" + ), + "refuse": sum( + 1 + for item in classified + if item["classification"] + == "REFUSE" + ), + }, + } + +async def create_redline_review( + db: AsyncSession, + session_id: str, + analysis: dict, +) -> RedlineReview: + + review = RedlineReview( + session_id=session_id, + status="pending", + ) + + db.add(review) + + await db.flush() + + for deviation in analysis["deviations"]: + finding = RedlineFinding( + review_id=review.id, + section=deviation["section"], + master_text=deviation.get("master_text"), + dealer_text=deviation.get("dealer_text"), + deviation_type=deviation["deviation_type"], + similarity=deviation.get("similarity"), + classification=deviation["classification"], + reason=deviation["reason"], + counter_position=deviation["counter_position"], + decision="pending", + ) + + db.add(finding) + + await db.commit() + + await db.refresh(review) + + return review + diff --git a/use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py b/use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py new file mode 100644 index 00000000..afa662a5 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py @@ -0,0 +1,64 @@ +from app.superdocs.client import SuperDocsClient + + +async def apply_approved_redlines( + client: SuperDocsClient, + session_id: str, + findings: list[dict], +): + approved = [ + finding + for finding in findings + if finding["decision"] == "approve" + ] + + if not approved: + return { + "applied": False, + "message": "No approved findings to apply.", + } + + instructions = [] + + for finding in approved: + instructions.append( + f""" +Section: {finding["section"]} + +Current dealer language: +{finding["dealer_text"]} + +Approved counter-position: +{finding["counter_position"]} + +Human decision: +APPROVED +""".strip() + ) + + message = f""" +Update the active dealer agreement using the +human-approved redline decisions below. + +IMPORTANT: +- Only modify the sections explicitly listed below. +- Preserve all other document content. +- Do not modify formatting unnecessarily. +- Apply the approved counter-position as the + replacement language. +- Do not invent additional changes. + +Approved redlines: + +{"\n\n---\n\n".join(instructions)} +""".strip() + + response = await client.chat( + session_id=session_id, + message=message, + ) + + return { + "applied": True, + "chat_response": response, + } \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/superdocs/client.py b/use-cases/dealer-agreement-redline/backend/app/superdocs/client.py new file mode 100644 index 00000000..982c9ca5 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/app/superdocs/client.py @@ -0,0 +1,289 @@ +from pathlib import Path +from typing import Any + +import httpx + +from app.config import get_settings + + +class SuperDocsError(Exception): + """Raised when the SuperDocs API returns an error.""" + + +class SuperDocsClient: + BASE_URL = "https://api.superdocs.app/v1" + + def __init__(self) -> None: + settings = get_settings() + + self.api_key = settings.superdocs_api_key + + self.headers = { + "Authorization": f"Bearer {self.api_key}", + } + + async def verify_key(self) -> Any: + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=30.0, + ) as client: + response = await client.get( + "/sessions", + headers=self.headers, + ) + + if response.is_error: + raise SuperDocsError( + f"SuperDocs authentication failed: " + f"{response.status_code} {response.text}" + ) + + return response.json() + + async def upload_document( + self, + file_path: str | Path, + session_id: str, + ) -> dict[str, Any]: + + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Document not found: {path}" + ) + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + with path.open("rb") as file: + response = await client.post( + "/documents/upload", + headers=self.headers, + files={ + "file": ( + path.name, + file, + self._content_type(path), + ) + }, + data={ + "session_id": session_id, + }, + ) + + self._raise_for_error(response) + + return response.json() + + async def upload_attachment( + self, + file_path: str | Path, + session_id: str, + ) -> dict[str, Any]: + + path = Path(file_path) + + if not path.exists(): + raise FileNotFoundError( + f"Attachment not found: {path}" + ) + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + with path.open("rb") as file: + response = await client.post( + "/attachments/upload", + headers=self.headers, + files={ + "file": ( + path.name, + file, + self._content_type(path), + ) + }, + data={ + "session_id": session_id, + }, + ) + + self._raise_for_error(response) + + return response.json() + + async def get_job( + self, + job_id: str, + ) -> dict[str, Any]: + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=30.0, + ) as client: + + response = await client.get( + f"/jobs/{job_id}", + headers=self.headers, + ) + + self._raise_for_error(response) + + return response.json() + + async def chat( + self, + *, + session_id: str, + message: str, + document_html: str | None = None, + document_id: str | None = None, + approval_mode: str | None = None, + response_mode: str | None = None, + ) -> dict[str, Any]: + + payload: dict[str, Any] = { + "message": message, + "session_id": session_id, + } + + if document_html is not None: + payload["document_html"] = document_html + + if document_id is not None: + payload["document_id"] = document_id + + if approval_mode is not None: + payload["approval_mode"] = approval_mode + + if response_mode is not None: + payload["response_mode"] = response_mode + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=180.0, + ) as client: + + response = await client.post( + "/chat", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.json() + + async def approve_change( + self, + *, + session_id: str, + job_id: str, + change_id: str, + approved: bool, + feedback: str | None = None, + ) -> dict[str, Any]: + + payload: dict[str, Any] = { + "job_id": job_id, + "change_id": change_id, + "approved": approved, + } + + if feedback: + payload["feedback"] = feedback + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=120.0, + ) as client: + + response = await client.post( + f"/chat/{session_id}/approve", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.json() + + async def export_document( + self, + *, + session_id: str, + filename: str, + ) -> bytes: + + payload = { + "session_id": session_id, + "format": "docx", + "options": { + "filename": filename, + }, + } + + async with httpx.AsyncClient( + base_url=self.BASE_URL, + timeout=180.0, + ) as client: + + response = await client.post( + "/documents/export", + headers={ + **self.headers, + "Content-Type": "application/json", + }, + json=payload, + ) + + self._raise_for_error(response) + + return response.content + + @staticmethod + def _content_type(path: Path) -> str: + suffix = path.suffix.lower() + + mapping = { + ".pdf": "application/pdf", + ".docx": ( + "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.document" + ), + ".doc": "application/msword", + ".txt": "text/plain", + ".html": "text/html", + ".htm": "text/html", + ".md": "text/markdown", + ".rtf": "application/rtf", + } + + return mapping.get( + suffix, + "application/octet-stream", + ) + + @staticmethod + def _raise_for_error( + response: httpx.Response, + ) -> None: + + if response.is_success: + return + + raise SuperDocsError( + f"SuperDocs API error " + f"{response.status_code}: " + f"{response.text}" + ) \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/requirements.txt b/use-cases/dealer-agreement-redline/backend/requirements.txt new file mode 100644 index 00000000..6f69f070 --- /dev/null +++ b/use-cases/dealer-agreement-redline/backend/requirements.txt @@ -0,0 +1,9 @@ +fastapi +uvicorn[standard] +httpx +pydantic +pydantic-settings +python-dotenv +python-multipart +sqlalchemy +asyncpg \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/test-data/dealer-agreement.docx b/use-cases/dealer-agreement-redline/test-data/dealer-agreement.docx new file mode 100644 index 0000000000000000000000000000000000000000..398ac0ac164e17d3665c83dad7c2263b3fe3e965 GIT binary patch literal 36837 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
7Y{e@NwfS){SCn9PkabA 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 literal 0 HcmV?d00001 diff --git a/use-cases/dealer-agreement-redline/test-data/master-agreement.docx b/use-cases/dealer-agreement-redline/test-data/master-agreement.docx new file mode 100644 index 0000000000000000000000000000000000000000..f37070cff7323c3f4646a8010de9ffb7744d659a GIT binary patch literal 36828 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 HZV(Ruf1!?R literal 0 HcmV?d00001 From acff1148fcd5d07971012a3dc98742f24b8bbf90 Mon Sep 17 00:00:00 2001 From: Pritesh Umraniya Date: Thu, 20 Aug 2026 17:43:16 +0530 Subject: [PATCH 3/5] feat: add dealer redline frontend --- .../backend/.env.example | 1 + .../backend/app/main.py | 26 +- .../frontend/.gitignore | 24 + .../frontend/README.md | 75 + .../frontend/eslint.config.js | 22 + .../frontend/index.html | 13 + .../frontend/package-lock.json | 3551 +++++++++++++++++ .../frontend/package.json | 33 + .../frontend/public/favicon.svg | 1 + .../frontend/public/icons.svg | 24 + .../frontend/src/App.css | 3 + .../frontend/src/App.tsx | 262 ++ .../frontend/src/api/redline.ts | 67 + .../frontend/src/components/ActionBar.tsx | 61 + .../src/components/ClassificationBadge.tsx | 25 + .../src/components/ComparisonBlock.tsx | 18 + .../frontend/src/components/EmptyState.tsx | 34 + .../frontend/src/components/FindingCard.tsx | 125 + .../frontend/src/components/FindingList.tsx | 54 + .../frontend/src/components/Header.tsx | 33 + .../frontend/src/components/LoadingState.tsx | 18 + .../src/components/ReviewProgress.tsx | 39 + .../frontend/src/components/ReviewSummary.tsx | 22 + .../frontend/src/components/StatusBadge.tsx | 34 + .../src/components/SuccessMessage.tsx | 25 + .../frontend/src/components/SummaryCard.tsx | 18 + .../frontend/src/index.css | 33 + .../frontend/src/main.tsx | 11 + .../frontend/src/types/redline.ts | 118 + .../frontend/tsconfig.app.json | 26 + .../frontend/tsconfig.json | 7 + .../frontend/tsconfig.node.json | 23 + .../frontend/vite.config.ts | 7 + 33 files changed, 4822 insertions(+), 11 deletions(-) create mode 100644 use-cases/dealer-agreement-redline/frontend/.gitignore create mode 100644 use-cases/dealer-agreement-redline/frontend/README.md create mode 100644 use-cases/dealer-agreement-redline/frontend/eslint.config.js create mode 100644 use-cases/dealer-agreement-redline/frontend/index.html create mode 100644 use-cases/dealer-agreement-redline/frontend/package-lock.json create mode 100644 use-cases/dealer-agreement-redline/frontend/package.json create mode 100644 use-cases/dealer-agreement-redline/frontend/public/favicon.svg create mode 100644 use-cases/dealer-agreement-redline/frontend/public/icons.svg create mode 100644 use-cases/dealer-agreement-redline/frontend/src/App.css create mode 100644 use-cases/dealer-agreement-redline/frontend/src/App.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/api/redline.ts create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/index.css create mode 100644 use-cases/dealer-agreement-redline/frontend/src/main.tsx create mode 100644 use-cases/dealer-agreement-redline/frontend/src/types/redline.ts create mode 100644 use-cases/dealer-agreement-redline/frontend/tsconfig.app.json create mode 100644 use-cases/dealer-agreement-redline/frontend/tsconfig.json create mode 100644 use-cases/dealer-agreement-redline/frontend/tsconfig.node.json create mode 100644 use-cases/dealer-agreement-redline/frontend/vite.config.ts diff --git a/use-cases/dealer-agreement-redline/backend/.env.example b/use-cases/dealer-agreement-redline/backend/.env.example index 00491afb..17d4569b 100644 --- a/use-cases/dealer-agreement-redline/backend/.env.example +++ b/use-cases/dealer-agreement-redline/backend/.env.example @@ -1 +1,2 @@ +DATABASE_URL= SUPERDOCS_API_KEY= \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/main.py b/use-cases/dealer-agreement-redline/backend/app/main.py index 77f4a2fd..7630d171 100644 --- a/use-cases/dealer-agreement-redline/backend/app/main.py +++ b/use-cases/dealer-agreement-redline/backend/app/main.py @@ -1,26 +1,30 @@ from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware -from app.api.redline import router as redline_router from app.api.poc import router as poc_router +from app.api.redline import router as redline_router from app.api.superdocs import router as superdocs_router -from contextlib import asynccontextmanager -from app.db.init_db import init_db - -@asynccontextmanager -async def lifespan(app: FastAPI): - await init_db() - yield app = FastAPI( - title="Dealer Agreement Redline Desk", - lifespan=lifespan, + title="Dealer Agreement Redline", + version="1.0.0", ) +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:5173", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) -app.include_router(superdocs_router) app.include_router(poc_router) app.include_router(redline_router) +app.include_router(superdocs_router) + @app.get("/health") async def health(): diff --git a/use-cases/dealer-agreement-redline/frontend/.gitignore b/use-cases/dealer-agreement-redline/frontend/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/use-cases/dealer-agreement-redline/frontend/README.md b/use-cases/dealer-agreement-redline/frontend/README.md new file mode 100644 index 00000000..c3001356 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/README.md @@ -0,0 +1,75 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` diff --git a/use-cases/dealer-agreement-redline/frontend/eslint.config.js b/use-cases/dealer-agreement-redline/frontend/eslint.config.js new file mode 100644 index 00000000..ef614d25 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/use-cases/dealer-agreement-redline/frontend/index.html b/use-cases/dealer-agreement-redline/frontend/index.html new file mode 100644 index 00000000..0fca6f04 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/use-cases/dealer-agreement-redline/frontend/package-lock.json b/use-cases/dealer-agreement-redline/frontend/package-lock.json new file mode 100644 index 00000000..5433c73e --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/package-lock.json @@ -0,0 +1,3551 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/vite": "^4.3.3", + "axios": "^1.19.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "tailwindcss": "^4.3.3" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "eslint": "^10.8.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.146.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", + "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", + "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", + "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz", + "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz", + "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz", + "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz", + "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz", + "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz", + "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz", + "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz", + "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz", + "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz", + "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz", + "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz", + "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz", + "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz", + "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", + "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.411", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", + "integrity": "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz", + "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/rolldown": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", + "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.146.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.5", + "@rolldown/binding-android-arm64": "1.2.5", + "@rolldown/binding-darwin-arm64": "1.2.5", + "@rolldown/binding-darwin-x64": "1.2.5", + "@rolldown/binding-freebsd-x64": "1.2.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", + "@rolldown/binding-linux-arm64-gnu": "1.2.5", + "@rolldown/binding-linux-arm64-musl": "1.2.5", + "@rolldown/binding-linux-ppc64-gnu": "1.2.5", + "@rolldown/binding-linux-s390x-gnu": "1.2.5", + "@rolldown/binding-linux-x64-gnu": "1.2.5", + "@rolldown/binding-linux-x64-musl": "1.2.5", + "@rolldown/binding-openharmony-arm64": "1.2.5", + "@rolldown/binding-win32-arm64-msvc": "1.2.5", + "@rolldown/binding-win32-x64-msvc": "1.2.5" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/use-cases/dealer-agreement-redline/frontend/package.json b/use-cases/dealer-agreement-redline/frontend/package.json new file mode 100644 index 00000000..7c50e17c --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.3.3", + "axios": "^1.19.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "tailwindcss": "^4.3.3" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "eslint": "^10.8.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" + } +} diff --git a/use-cases/dealer-agreement-redline/frontend/public/favicon.svg b/use-cases/dealer-agreement-redline/frontend/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/frontend/public/icons.svg b/use-cases/dealer-agreement-redline/frontend/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/use-cases/dealer-agreement-redline/frontend/src/App.css b/use-cases/dealer-agreement-redline/frontend/src/App.css new file mode 100644 index 00000000..8c99abd3 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/App.css @@ -0,0 +1,3 @@ +#root { + min-height: 100vh; +} \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/frontend/src/App.tsx b/use-cases/dealer-agreement-redline/frontend/src/App.tsx new file mode 100644 index 00000000..2e0472d0 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/App.tsx @@ -0,0 +1,262 @@ +import { useState } from "react"; + +import { + analyzeRedline, + applyReview, + decideFinding, + exportReview, + getRedlineReview, +} from "./api/redline"; + +import type { AnalyzeResponse, RedlineReview } from "./types/redline"; + +import EmptyState from "./components/EmptyState"; +import FindingList from "./components/FindingList"; +import Header from "./components/Header"; +import LoadingState from "./components/LoadingState"; +import ReviewSummary from "./components/ReviewSummary"; +import ActionBar from "./components/ActionBar"; +import StatusBadge from "./components/StatusBadge"; +import ReviewProgress from "./components/ReviewProgress"; +import SuccessMessage from "./components/SuccessMessage"; + +function App() { + const [analysis, setAnalysis] = useState(null); + + const [review, setReview] = useState(null); + + const [loading, setLoading] = useState(false); + + const [processingFindingId, setProcessingFindingId] = useState< + string | null + >(null); + + const [error, setError] = useState(null); + + const [applying, setApplying] = useState(false); + + const [exporting, setExporting] = useState(false); + + const [success, setSuccess] = useState(null); + + async function handleStartReview() { + setLoading(true); + setError(null); + setSuccess(null); + + try { + const result = await analyzeRedline(); + + setAnalysis(result); + + const reviewResult = await getRedlineReview(result.review_id); + + setReview(reviewResult); + setSuccess("Agreement analysis completed successfully."); + } catch (err) { + console.error(err); + + setError("Failed to analyze the agreement."); + } finally { + setLoading(false); + } + } + + async function handleDecision( + findingId: string, + decision: "approve" | "reject", + ) { + if (!review) { + return; + } + + setProcessingFindingId(findingId); + setError(null); + + try { + const result = await decideFinding( + review.review_id, + findingId, + decision, + ); + + setReview((current) => { + if (!current) { + return current; + } + + return { + ...current, + status: result.review_status, + findings: current.findings.map((finding) => + finding.id === findingId + ? { + ...finding, + decision: result.decision, + } + : finding, + ), + }; + }); + setSuccess( + decision === "approve" + ? "Finding approved successfully." + : "Finding rejected successfully.", + ); + } catch (err) { + console.error(err); + + setError("Failed to save the finding decision."); + } finally { + setProcessingFindingId(null); + } + } + + async function handleApply() { + if (!review) { + return; + } + + if (review.status !== "completed") { + return; + } + + setApplying(true); + setError(null); + + try { + const result = await applyReview(review.review_id); + + setReview((current) => { + if (!current) { + return current; + } + + return { + ...current, + status: result.status, + }; + }); + setSuccess("Approved changes have been applied successfully."); + } catch (err) { + console.error(err); + + setError("Failed to apply the approved changes."); + } finally { + setApplying(false); + } + } + + async function handleExport() { + if (!review) { + return; + } + + if (review.status !== "applied") { + return; + } + + setExporting(true); + setError(null); + + try { + const blob = await exportReview(review.review_id); + + const url = window.URL.createObjectURL(blob); + + const link = document.createElement("a"); + + link.href = url; + link.download = "dealer-agreement-redlined.docx"; + + document.body.appendChild(link); + + link.click(); + + link.remove(); + + window.URL.revokeObjectURL(url); + + setSuccess("Redlined DOCX exported successfully."); + } catch (err) { + console.error(err); + + setError("Failed to export the document."); + } finally { + setExporting(false); + } + } + + return ( +
+
+ +
+ {success && ( + setSuccess(null)} + /> + )} + {error && ( +
+ {error} +
+ )} + + {!analysis && !loading && ( + + )} + + {loading && } + + {analysis && review && !loading && ( +
+
+
+
+

+ Review +

+ +

+ Agreement Findings +

+
+ + +
+
+ + + + finding.decision !== "pending", + ).length + } + /> + + + + +
+ )} +
+
+ ); +} + +export default App; diff --git a/use-cases/dealer-agreement-redline/frontend/src/api/redline.ts b/use-cases/dealer-agreement-redline/frontend/src/api/redline.ts new file mode 100644 index 00000000..8babc37f --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/api/redline.ts @@ -0,0 +1,67 @@ +import axios from "axios"; + +import type { + AnalyzeResponse, + ApplyReviewResponse, + FindingDecisionRequest, + FindingDecisionResponse, + RedlineReview, +} from "../types/redline"; + +const api = axios.create({ + baseURL: "http://localhost:8000", + headers: { + "Content-Type": "application/json", + }, +}); + +export async function analyzeRedline(): Promise { + const response = await api.post("/redline/analyze"); + + return response.data; +} + +export async function getRedlineReview( + reviewId: string, +): Promise { + const response = await api.get( + `/redline/reviews/${reviewId}`, + ); + + return response.data; +} + +export async function decideFinding( + reviewId: string, + findingId: string, + decision: "approve" | "reject", +): Promise { + const payload: FindingDecisionRequest = { + decision, + }; + + const response = await api.post( + `/redline/reviews/${reviewId}/findings/${findingId}/decision`, + payload, + ); + + return response.data; +} + +export async function applyReview( + reviewId: string, +): Promise { + const response = await api.post( + `/redline/reviews/${reviewId}/apply`, + ); + + return response.data; +} + +export async function exportReview(reviewId: string): Promise { + const response = await api.get(`/redline/reviews/${reviewId}/export`, { + responseType: "blob", + }); + + return response.data; +} diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx new file mode 100644 index 00000000..009d0db7 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx @@ -0,0 +1,61 @@ +interface ActionBarProps { + reviewStatus: string; + applying: boolean; + exporting: boolean; + onApply: () => void; + onExport: () => void; +} + +function ActionBar({ + reviewStatus, + applying, + exporting, + onApply, + onExport, +}: ActionBarProps) { + const canApply = reviewStatus === "completed"; + + const canExport = reviewStatus === "applied"; + + return ( +
+
+
+

+ Review Actions +

+ +

+ {canApply + ? "All findings have been reviewed." + : canExport + ? "Changes have been applied." + : "Review all findings before applying changes."} +

+
+ +
+ + + +
+
+
+ ); +} + +export default ActionBar; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx new file mode 100644 index 00000000..30774250 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx @@ -0,0 +1,25 @@ +import type { Classification } from "../types/redline"; + +interface ClassificationBadgeProps { + classification: Classification; +} + +const classes: Record = { + "PRE-APPROVED": "bg-emerald-50 text-emerald-700 border-emerald-200", + + ESCALATE: "bg-amber-50 text-amber-700 border-amber-200", + + REFUSE: "bg-red-50 text-red-700 border-red-200", +}; + +function ClassificationBadge({ classification }: ClassificationBadgeProps) { + return ( + + {classification} + + ); +} + +export default ClassificationBadge; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx new file mode 100644 index 00000000..80fe972f --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx @@ -0,0 +1,18 @@ +interface ComparisonBlockProps { + label: string; + text: string; +} + +function ComparisonBlock({ label, text }: ComparisonBlockProps) { + return ( +
+

+ {label} +

+ +

{text}

+
+ ); +} + +export default ComparisonBlock; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx new file mode 100644 index 00000000..8be1aaa6 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx @@ -0,0 +1,34 @@ +interface EmptyStateProps { + onStartReview: () => void; +} + +function EmptyState({ onStartReview }: EmptyStateProps) { + return ( +
+
+
+ § +
+ +

+ Ready to review the agreement +

+ +

+ Compare the dealer agreement against the approved master + agreement and negotiation playbook. +

+ + +
+
+ ); +} + +export default EmptyState; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx new file mode 100644 index 00000000..141a867b --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx @@ -0,0 +1,125 @@ +import type { Deviation, FindingDecision } from "../types/redline"; + +import ClassificationBadge from "./ClassificationBadge"; +import ComparisonBlock from "./ComparisonBlock"; + +interface FindingCardProps { + finding: Deviation & { + id?: string; + decision?: FindingDecision; + }; + index: number; + loading?: boolean; + onDecision?: (decision: "approve" | "reject") => void; +} + +function FindingCard({ + finding, + index, + loading = false, + onDecision, +}: FindingCardProps) { + const decision = finding.decision; + + return ( +
+ {/* Header */} +
+
+

+ Finding {index + 1} +

+ +

+ {finding.section} +

+
+ + +
+ + {/* Comparison */} +
+ + + +
+ + {/* Reason */} +
+

+ Reason +

+ +

+ {finding.reason} +

+
+ + {/* Counter position */} +
+

+ Counter-position +

+ +

+ {finding.counter_position} +

+
+ + {/* Decision */} +
+
+

+ Decision +

+ +

+ {decision === "approve" && "✓ Approved"} + {decision === "reject" && "✕ Rejected"} + {(!decision || decision === "pending") && + "Pending review"} +

+
+ + {(!decision || decision === "pending") && ( +
+ + + +
+ )} +
+
+ ); +} + +export default FindingCard; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx new file mode 100644 index 00000000..45f4b4be --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx @@ -0,0 +1,54 @@ +import type { Deviation, FindingDecision } from "../types/redline"; + +import FindingCard from "./FindingCard"; + +interface FindingListProps { + findings: (Deviation & { + id?: string; + decision?: FindingDecision; + })[]; + + processingFindingId?: string | null; + + onDecision: (findingId: string, decision: "approve" | "reject") => void; +} + +function FindingList({ + findings, + processingFindingId, + onDecision, +}: FindingListProps) { + return ( +
+
+

+ Findings +

+ +

+ Review each proposed deviation before applying changes. +

+
+ +
+ {findings.map((finding, index) => ( + { + if (!finding.id) { + return; + } + + onDecision(finding.id, decision); + }} + /> + ))} +
+
+ ); +} + +export default FindingList; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx new file mode 100644 index 00000000..0e3f0e25 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx @@ -0,0 +1,33 @@ +interface HeaderProps { + loading: boolean; + onStartReview: () => void; +} + +function Header({ loading, onStartReview }: HeaderProps) { + return ( +
+
+
+

+ Dealer Agreement Redline +

+ +

+ Review and approve proposed contract changes +

+
+ + +
+
+ ); +} + +export default Header; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx new file mode 100644 index 00000000..18ba3d99 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx @@ -0,0 +1,18 @@ +function LoadingState() { + return ( +
+
+ +

+ Analyzing agreement +

+ +

+ Comparing the dealer agreement against the master agreement and + playbook. +

+
+ ); +} + +export default LoadingState; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx new file mode 100644 index 00000000..94700d72 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx @@ -0,0 +1,39 @@ +interface ReviewProgressProps { + total: number; + reviewed: number; +} + +function ReviewProgress({ total, reviewed }: ReviewProgressProps) { + const percentage = total === 0 ? 0 : Math.round((reviewed / total) * 100); + + return ( +
+
+
+

+ Review Progress +

+ +

+ {reviewed} of {total} findings reviewed +

+
+ + + {percentage}% + +
+ +
+
+
+
+ ); +} + +export default ReviewProgress; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx new file mode 100644 index 00000000..13a7b4b2 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx @@ -0,0 +1,22 @@ +import type { ReviewSummary as ReviewSummaryType } from "../types/redline"; +import SummaryCard from "./SummaryCard"; + +interface ReviewSummaryProps { + summary: ReviewSummaryType; +} + +function ReviewSummary({ summary }: ReviewSummaryProps) { + return ( +
+ + + + + + + +
+ ); +} + +export default ReviewSummary; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx new file mode 100644 index 00000000..e02220d3 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx @@ -0,0 +1,34 @@ +import type { ReviewStatus } from "../types/redline"; + +interface StatusBadgeProps { + status: ReviewStatus; +} + +const styles: Record = { + pending: "bg-slate-100 text-slate-600 border-slate-200", + + in_review: "bg-amber-50 text-amber-700 border-amber-200", + + completed: "bg-blue-50 text-blue-700 border-blue-200", + + applied: "bg-emerald-50 text-emerald-700 border-emerald-200", +}; + +const labels: Record = { + pending: "Pending", + in_review: "In Review", + completed: "Ready to Apply", + applied: "Applied", +}; + +function StatusBadge({ status }: StatusBadgeProps) { + return ( + + {labels[status]} + + ); +} + +export default StatusBadge; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx new file mode 100644 index 00000000..92f5a3f3 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx @@ -0,0 +1,25 @@ +interface SuccessMessageProps { + message: string; + onDismiss?: () => void; +} + +function SuccessMessage({ message, onDismiss }: SuccessMessageProps) { + return ( +
+

{message}

+ + {onDismiss && ( + + )} +
+ ); +} + +export default SuccessMessage; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx new file mode 100644 index 00000000..5bef9f90 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx @@ -0,0 +1,18 @@ +interface SummaryCardProps { + label: string; + value: number; +} + +function SummaryCard({ label, value }: SummaryCardProps) { + return ( +
+

{label}

+ +

+ {value} +

+
+ ); +} + +export default SummaryCard; diff --git a/use-cases/dealer-agreement-redline/frontend/src/index.css b/use-cases/dealer-agreement-redline/frontend/src/index.css new file mode 100644 index 00000000..82af602b --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/index.css @@ -0,0 +1,33 @@ +@import "tailwindcss"; + +:root { + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + + color: #111827; + background: #f8fafc; + font-synthesis: none; + text-rendering: optimizeLegibility; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; + margin: 0; +} + +body { + min-width: 320px; + background: #f8fafc; +} \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/frontend/src/main.tsx b/use-cases/dealer-agreement-redline/frontend/src/main.tsx new file mode 100644 index 00000000..17cb9016 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import "./index.css"; +import App from "./App"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts b/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts new file mode 100644 index 00000000..44aa9e9f --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts @@ -0,0 +1,118 @@ +export type Classification = "PRE-APPROVED" | "ESCALATE" | "REFUSE"; + +export type FindingDecision = "pending" | "approve" | "reject"; + +export type ReviewStatus = "pending" | "in_review" | "completed" | "applied"; + +export interface AgreementSection { + section: string; + text: string; +} + +export interface Deviation { + section: string; + master_text: string; + dealer_text: string; + deviation_type: string; + similarity: number; + classification: Classification; + reason: string; + counter_position: string; +} + +export interface ReviewSummary { + total: number; + pre_approved: number; + escalate: number; + refuse: number; +} + +export interface AnalyzeResponse { + review_id: string; + session_id: string; + + dealer_document?: { + html: string; + session_id: string; + filename: string; + chunks_count: number; + version_id: string; + document_id: string; + }; + + master_attachment?: { + job_id: string; + filename: string; + status: string; + message: string; + }; + + playbook_attachment?: { + job_id: string; + filename: string; + status: string; + message: string; + }; + + master_sections: AgreementSection[]; + dealer_sections: AgreementSection[]; + playbook_sections: AgreementSection[]; + deviations: Deviation[]; + summary: ReviewSummary; +} + +export interface RedlineFinding extends Deviation { + id: string; + decision: FindingDecision; +} + +export interface RedlineReview { + review_id: string; + session_id: string; + status: ReviewStatus; + created_at: string; + updated_at: string; + findings: RedlineFinding[]; +} + +export interface FindingDecisionRequest { + decision: "approve" | "reject"; +} + +export interface FindingDecisionResponse { + review_id: string; + finding_id: string; + decision: FindingDecision; + review_status: ReviewStatus; + remaining_findings: number; +} + +export interface ApplyReviewResponse { + review_id: string; + status: "applied"; + applied: boolean; + chat_response?: { + response?: string; + session_id?: string; + document_changes?: { + updated_html?: string; + version_id?: string; + changes_summary?: string; + requires_approval?: boolean | null; + pending_changes?: unknown; + changes?: unknown[]; + chunk_diffs?: unknown; + concurrent_merges?: unknown; + focused_document_id?: string; + }; + usage?: { + ops_charged?: number; + monthly_used?: number; + monthly_remaining?: number; + }; + }; +} + +export interface ApiError { + detail: string; +} diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.app.json b/use-cases/dealer-agreement-redline/frontend/tsconfig.app.json new file mode 100644 index 00000000..6830b6f7 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.json b/use-cases/dealer-agreement-redline/frontend/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.node.json b/use-cases/dealer-agreement-redline/frontend/tsconfig.node.json new file mode 100644 index 00000000..8455dcbc --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/use-cases/dealer-agreement-redline/frontend/vite.config.ts b/use-cases/dealer-agreement-redline/frontend/vite.config.ts new file mode 100644 index 00000000..1f4caca4 --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); From 515cc310ac92cb8f94abad04e8524720caa918c7 Mon Sep 17 00:00:00 2001 From: Pritesh Umraniya Date: Thu, 20 Aug 2026 18:21:07 +0530 Subject: [PATCH 4/5] feat: complete dealer agreement redline workflow --- use-cases/dealer-agreement-redline/.gitignore | 4 +- .../backend/app/api/redline.py | 372 +++++++++++------- .../frontend/package-lock.json | 10 + .../frontend/package.json | 1 + .../frontend/src/App.tsx | 69 +++- .../frontend/src/api/redline.ts | 123 ++++-- .../src/components/DocumentUpload.tsx | 155 ++++++++ .../frontend/src/components/EmptyState.tsx | 38 +- .../frontend/src/components/FindingCard.tsx | 249 +++++++----- .../frontend/src/components/FindingList.tsx | 46 ++- .../frontend/src/components/Header.tsx | 40 +- .../src/components/SuccessMessage.tsx | 12 +- .../frontend/src/types/redline.ts | 15 + 13 files changed, 761 insertions(+), 373 deletions(-) create mode 100644 use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx diff --git a/use-cases/dealer-agreement-redline/.gitignore b/use-cases/dealer-agreement-redline/.gitignore index b88ce475..ea4a3afb 100644 --- a/use-cases/dealer-agreement-redline/.gitignore +++ b/use-cases/dealer-agreement-redline/.gitignore @@ -23,4 +23,6 @@ build/ Thumbs.db # Logs -*.log \ No newline at end of file +*.log + +backend/uploads/ \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/backend/app/api/redline.py b/use-cases/dealer-agreement-redline/backend/app/api/redline.py index cfd5993c..ef20e320 100644 --- a/use-cases/dealer-agreement-redline/backend/app/api/redline.py +++ b/use-cases/dealer-agreement-redline/backend/app/api/redline.py @@ -1,7 +1,15 @@ +import shutil import uuid from pathlib import Path - -from fastapi import APIRouter, Depends, HTTPException +from uuid import UUID + +from fastapi import ( + APIRouter, + Depends, + File, + HTTPException, + UploadFile, +) from fastapi.responses import Response from pydantic import BaseModel from sqlalchemy import select @@ -31,112 +39,185 @@ ) -TEST_DATA = ( - Path(__file__).resolve().parents[3] - / "test-data" +UPLOAD_DIR = ( + Path(__file__).resolve().parents[2] + / "uploads" ) +ALLOWED_EXTENSIONS = { + ".docx", +} + + class FindingDecisionRequest(BaseModel): decision: str +def validate_docx_file( + file: UploadFile, + field_name: str, +) -> None: + if not file.filename: + raise HTTPException( + status_code=400, + detail=f"{field_name} is required.", + ) + + extension = Path( + file.filename + ).suffix.lower() + + if extension not in ALLOWED_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=( + f"{field_name} must be a DOCX file." + ), + ) + + +async def save_upload( + file: UploadFile, + destination: Path, +) -> None: + destination.parent.mkdir( + parents=True, + exist_ok=True, + ) + + with destination.open("wb") as output: + shutil.copyfileobj( + file.file, + output, + ) + + @router.post("/analyze") async def analyze_redline( + dealer_file: UploadFile = File(...), + master_file: UploadFile = File(...), + playbook_file: UploadFile = File(...), db: AsyncSession = Depends(get_db), ): """ - Run the complete dealer agreement redline analysis. - - Flow: - 1. Create a SuperDocs session. - 2. Upload dealer agreement as active document. - 3. Upload master agreement as reference attachment. - 4. Upload negotiation playbook as reference attachment. - 5. Analyze the agreements locally. - 6. Persist the review and findings. - 7. Return the review and SuperDocs session IDs. + Analyze uploaded dealer, master, and playbook + documents. + + The dealer agreement is also loaded into a + SuperDocs session so that the same session can + later be used for applying approved redlines + and exporting the updated document. """ - master_file = ( - TEST_DATA / "master-agreement.docx" - ) + # -------------------------------------------------- + # Validate uploaded files + # -------------------------------------------------- - dealer_file = ( - TEST_DATA / "dealer-agreement.docx" + validate_docx_file( + dealer_file, + "dealer_file", ) - playbook_file = ( - TEST_DATA / "negotiation-playbook.docx" + validate_docx_file( + master_file, + "master_file", ) - for file in ( - master_file, - dealer_file, + validate_docx_file( playbook_file, - ): - if not file.exists(): - raise HTTPException( - status_code=404, - detail=f"File not found: {file}", - ) + "playbook_file", + ) - client = SuperDocsClient() + # -------------------------------------------------- + # Create temporary upload directory + # -------------------------------------------------- + + request_id = uuid.uuid4() - # One session is used for the entire workflow. - session_id = str(uuid.uuid4()) + request_upload_dir = ( + UPLOAD_DIR / str(request_id) + ) + + dealer_path = ( + request_upload_dir + / "dealer-agreement.docx" + ) + + master_path = ( + request_upload_dir + / "master-agreement.docx" + ) + + playbook_path = ( + request_upload_dir + / "negotiation-playbook.docx" + ) try: # -------------------------------------------------- - # 1. Upload dealer agreement as active document. + # Save uploaded documents temporarily # -------------------------------------------------- - dealer_document = ( - await client.upload_document( - file_path=dealer_file, - session_id=session_id, - ) + await save_upload( + dealer_file, + dealer_path, ) - # -------------------------------------------------- - # 2. Upload master agreement as reference. - # -------------------------------------------------- + await save_upload( + master_file, + master_path, + ) - master_attachment = ( - await client.upload_attachment( - file_path=master_file, - session_id=session_id, - ) + await save_upload( + playbook_file, + playbook_path, ) # -------------------------------------------------- - # 3. Upload negotiation playbook as reference. + # Analyze documents locally # -------------------------------------------------- - playbook_attachment = ( - await client.upload_attachment( - file_path=playbook_file, - session_id=session_id, - ) + analysis = analyze_agreements( + master_path=master_path, + dealer_path=dealer_path, + playbook_path=playbook_path, ) # -------------------------------------------------- - # 4. Run local redline analysis. + # Create one SuperDocs session for this review # -------------------------------------------------- - analysis = analyze_agreements( - master_path=master_file, - dealer_path=dealer_file, - playbook_path=playbook_file, - ) + session_id = str(uuid.uuid4()) + + client = SuperDocsClient() # -------------------------------------------------- - # 5. Persist review. + # Load dealer agreement into SuperDocs # - # IMPORTANT: - # Save the SAME SuperDocs session ID. - # This allows /apply to operate on the document - # that was actually uploaded above. + # This is important because the same session is + # later used by apply and export. + # -------------------------------------------------- + + try: + dealer_document = ( + await client.upload_document( + file_path=dealer_path, + session_id=session_id, + ) + ) + + except SuperDocsError as exc: + raise HTTPException( + status_code=502, + detail=( + "SuperDocs document upload failed: " + f"{exc}" + ), + ) + + # -------------------------------------------------- + # Create database review # -------------------------------------------------- review = await create_redline_review( @@ -145,20 +226,19 @@ async def analyze_redline( analysis=analysis, ) + # -------------------------------------------------- + # Return analysis + SuperDocs document information + # -------------------------------------------------- + return { "review_id": str(review.id), "session_id": session_id, "dealer_document": dealer_document, - "master_attachment": master_attachment, - "playbook_attachment": playbook_attachment, **analysis, } - except SuperDocsError as exc: - raise HTTPException( - status_code=502, - detail=str(exc), - ) + except HTTPException: + raise except Exception as exc: raise HTTPException( @@ -166,16 +246,22 @@ async def analyze_redline( detail=str(exc), ) + finally: + # -------------------------------------------------- + # Delete temporary uploaded files + # -------------------------------------------------- + + shutil.rmtree( + request_upload_dir, + ignore_errors=True, + ) + @router.get("/reviews/{review_id}") async def get_redline_review( - review_id: uuid.UUID, + review_id: UUID, db: AsyncSession = Depends(get_db), ): - """ - Get a redline review and all of its findings. - """ - result = await db.execute( select(RedlineReview).where( RedlineReview.id == review_id @@ -228,26 +314,16 @@ async def get_redline_review( "/reviews/{review_id}/findings/{finding_id}/decision" ) async def decide_redline_finding( - review_id: uuid.UUID, - finding_id: uuid.UUID, + review_id: UUID, + finding_id: UUID, payload: FindingDecisionRequest, db: AsyncSession = Depends(get_db), ): - """ - Approve or reject an individual redline finding. - - A finding can only be decided once. - Decisions cannot be changed after the review - has been completed or applied. - """ - # -------------------------------------------------- - # Normalize the decision. + # Validate decision # -------------------------------------------------- - decision = payload.decision.strip().lower() - - if decision not in { + if payload.decision not in { "approve", "reject", }: @@ -260,7 +336,7 @@ async def decide_redline_finding( ) # -------------------------------------------------- - # Verify review exists. + # Verify review exists # -------------------------------------------------- result = await db.execute( @@ -278,23 +354,7 @@ async def decide_redline_finding( ) # -------------------------------------------------- - # Prevent decisions after review completion. - # -------------------------------------------------- - - if review.status in { - "completed", - "applied", - }: - raise HTTPException( - status_code=409, - detail=( - "This review has already been " - "completed." - ), - ) - - # -------------------------------------------------- - # Find the requested finding. + # Find finding # -------------------------------------------------- result = await db.execute( @@ -313,7 +373,7 @@ async def decide_redline_finding( ) # -------------------------------------------------- - # Prevent changing an already-reviewed finding. + # Prevent duplicate decisions # -------------------------------------------------- if finding.decision != "pending": @@ -325,10 +385,10 @@ async def decide_redline_finding( ), ) - finding.decision = decision + finding.decision = payload.decision # -------------------------------------------------- - # Check whether findings remain. + # Check remaining pending findings # -------------------------------------------------- result = await db.execute( @@ -361,18 +421,11 @@ async def decide_redline_finding( "/reviews/{review_id}/apply" ) async def apply_review( - review_id: uuid.UUID, + review_id: UUID, db: AsyncSession = Depends(get_db), ): - """ - Apply human-approved redlines to the active - SuperDocs document. - - All findings must have been reviewed first. - """ - # -------------------------------------------------- - # Get review. + # Get review # -------------------------------------------------- result = await db.execute( @@ -390,17 +443,19 @@ async def apply_review( ) # -------------------------------------------------- - # Prevent applying the same review twice. + # Prevent duplicate apply # -------------------------------------------------- if review.status == "applied": raise HTTPException( status_code=409, - detail="This review has already been applied.", + detail=( + "This review has already been applied." + ), ) # -------------------------------------------------- - # Make sure every finding has a decision. + # Make sure all findings were reviewed # -------------------------------------------------- result = await db.execute( @@ -422,7 +477,7 @@ async def apply_review( ) # -------------------------------------------------- - # Load all findings. + # Load findings # -------------------------------------------------- result = await db.execute( @@ -448,10 +503,7 @@ async def apply_review( ] # -------------------------------------------------- - # Apply approved findings to SuperDocs. - # - # apply_approved_redlines() filters out findings - # whose decision is not "approve". + # Apply approved redlines in SuperDocs # -------------------------------------------------- client = SuperDocsClient() @@ -463,40 +515,37 @@ async def apply_review( findings=finding_data, ) - except SuperDocsError as exc: - raise HTTPException( - status_code=502, - detail=str(exc), - ) - except Exception as exc: raise HTTPException( status_code=502, detail=f"SuperDocs error: {exc}", ) + # -------------------------------------------------- + # Mark review as applied + # -------------------------------------------------- + review.status = "applied" await db.commit() return { "review_id": str(review_id), - "session_id": review.session_id, "status": review.status, **result, } -@router.get( +@router.post( "/reviews/{review_id}/export" ) async def export_review( - review_id: uuid.UUID, + review_id: UUID, db: AsyncSession = Depends(get_db), ): - """ - Export the applied dealer agreement as DOCX. - """ + # -------------------------------------------------- + # Get review + # -------------------------------------------------- result = await db.execute( select(RedlineReview).where( @@ -513,7 +562,7 @@ async def export_review( ) # -------------------------------------------------- - # Export is only allowed after apply. + # Export only after apply # -------------------------------------------------- if review.status != "applied": @@ -521,28 +570,53 @@ async def export_review( status_code=409, detail=( "The review must be applied " - "before exporting." + "before it can be exported." ), ) + # -------------------------------------------------- + # Export updated document from SuperDocs + # + # The review.session_id is the same session used + # during /analyze to load the dealer document. + # -------------------------------------------------- + client = SuperDocsClient() - filename = "dealer-agreement-redlined.docx" + filename = ( + "dealer-agreement-redlined.docx" + ) try: - document = await client.export_document( - session_id=review.session_id, - filename=filename, + document_bytes = ( + await client.export_document( + session_id=review.session_id, + filename=filename, + ) ) except SuperDocsError as exc: raise HTTPException( status_code=502, - detail=str(exc), + detail=( + f"SuperDocs export failed: {exc}" + ), + ) + + except Exception as exc: + raise HTTPException( + status_code=502, + detail=( + f"SuperDocs export failed: {exc}" + ), ) + # -------------------------------------------------- + # Return DOCX + # -------------------------------------------------- + return Response( - content=document, + content=document_bytes, media_type=( "application/vnd.openxmlformats-" "officedocument.wordprocessingml.document" diff --git a/use-cases/dealer-agreement-redline/frontend/package-lock.json b/use-cases/dealer-agreement-redline/frontend/package-lock.json index 5433c73e..f6aebf6b 100644 --- a/use-cases/dealer-agreement-redline/frontend/package-lock.json +++ b/use-cases/dealer-agreement-redline/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@tailwindcss/vite": "^4.3.3", "axios": "^1.19.0", + "lucide-react": "^1.33.0", "react": "^19.2.8", "react-dom": "^19.2.8", "tailwindcss": "^4.3.3" @@ -2650,6 +2651,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.33.0.tgz", + "integrity": "sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/use-cases/dealer-agreement-redline/frontend/package.json b/use-cases/dealer-agreement-redline/frontend/package.json index 7c50e17c..0f39a215 100644 --- a/use-cases/dealer-agreement-redline/frontend/package.json +++ b/use-cases/dealer-agreement-redline/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "@tailwindcss/vite": "^4.3.3", "axios": "^1.19.0", + "lucide-react": "^1.33.0", "react": "^19.2.8", "react-dom": "^19.2.8", "tailwindcss": "^4.3.3" diff --git a/use-cases/dealer-agreement-redline/frontend/src/App.tsx b/use-cases/dealer-agreement-redline/frontend/src/App.tsx index 2e0472d0..edf21e9d 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/App.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/App.tsx @@ -10,14 +10,14 @@ import { import type { AnalyzeResponse, RedlineReview } from "./types/redline"; +import ActionBar from "./components/ActionBar"; import EmptyState from "./components/EmptyState"; import FindingList from "./components/FindingList"; import Header from "./components/Header"; import LoadingState from "./components/LoadingState"; +import ReviewProgress from "./components/ReviewProgress"; import ReviewSummary from "./components/ReviewSummary"; -import ActionBar from "./components/ActionBar"; import StatusBadge from "./components/StatusBadge"; -import ReviewProgress from "./components/ReviewProgress"; import SuccessMessage from "./components/SuccessMessage"; function App() { @@ -31,32 +31,45 @@ function App() { string | null >(null); - const [error, setError] = useState(null); - const [applying, setApplying] = useState(false); const [exporting, setExporting] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); - async function handleStartReview() { + async function handleAnalyze( + dealerFile: File, + masterFile: File, + playbookFile: File, + ) { setLoading(true); setError(null); setSuccess(null); try { - const result = await analyzeRedline(); + const result = await analyzeRedline( + dealerFile, + masterFile, + playbookFile, + ); setAnalysis(result); const reviewResult = await getRedlineReview(result.review_id); setReview(reviewResult); + setSuccess("Agreement analysis completed successfully."); } catch (err) { console.error(err); - setError("Failed to analyze the agreement."); + setError( + err instanceof Error + ? err.message + : "Failed to analyze the agreement.", + ); } finally { setLoading(false); } @@ -72,6 +85,7 @@ function App() { setProcessingFindingId(findingId); setError(null); + setSuccess(null); try { const result = await decideFinding( @@ -98,6 +112,7 @@ function App() { ), }; }); + setSuccess( decision === "approve" ? "Finding approved successfully." @@ -106,7 +121,11 @@ function App() { } catch (err) { console.error(err); - setError("Failed to save the finding decision."); + setError( + err instanceof Error + ? err.message + : "Failed to save the finding decision.", + ); } finally { setProcessingFindingId(null); } @@ -123,6 +142,7 @@ function App() { setApplying(true); setError(null); + setSuccess(null); try { const result = await applyReview(review.review_id); @@ -137,11 +157,16 @@ function App() { status: result.status, }; }); + setSuccess("Approved changes have been applied successfully."); } catch (err) { console.error(err); - setError("Failed to apply the approved changes."); + setError( + err instanceof Error + ? err.message + : "Failed to apply the approved changes.", + ); } finally { setApplying(false); } @@ -158,6 +183,7 @@ function App() { setExporting(true); setError(null); + setSuccess(null); try { const blob = await exportReview(review.review_id); @@ -181,23 +207,32 @@ function App() { } catch (err) { console.error(err); - setError("Failed to export the document."); + setError( + err instanceof Error + ? err.message + : "Failed to export the document.", + ); } finally { setExporting(false); } } + const reviewedCount = + review?.findings.filter((finding) => finding.decision !== "pending") + .length ?? 0; + return (
-
+
-
+
{success && ( setSuccess(null)} /> )} + {error && (
{error} @@ -205,7 +240,7 @@ function App() { )} {!analysis && !loading && ( - + )} {loading && } @@ -213,7 +248,7 @@ function App() { {analysis && review && !loading && (
-
+

Review @@ -232,11 +267,7 @@ function App() { finding.decision !== "pending", - ).length - } + reviewed={reviewedCount} /> (response: Response): Promise { + if (!response.ok) { + let message = `Request failed with status ${response.status}`; + + try { + const data = await response.json(); + + if (data.detail) { + message = data.detail; + } + } catch { + // Keep default error message. + } + + throw new Error(message); + } + + return response.json() as Promise; +} + +export async function analyzeRedline( + dealerFile: File, + masterFile: File, + playbookFile: File, +): Promise { + const formData = new FormData(); + + formData.append("dealer_file", dealerFile); + + formData.append("master_file", masterFile); -export async function analyzeRedline(): Promise { - const response = await api.post("/redline/analyze"); + formData.append("playbook_file", playbookFile); - return response.data; + const response = await fetch(`${API_BASE_URL}/redline/analyze`, { + method: "POST", + body: formData, + }); + + return handleResponse(response); } export async function getRedlineReview( reviewId: string, ): Promise { - const response = await api.get( - `/redline/reviews/${reviewId}`, - ); + const response = await fetch(`${API_BASE_URL}/redline/reviews/${reviewId}`); - return response.data; + return handleResponse(response); } export async function decideFinding( reviewId: string, findingId: string, decision: "approve" | "reject", -): Promise { - const payload: FindingDecisionRequest = { - decision, - }; - - const response = await api.post( - `/redline/reviews/${reviewId}/findings/${findingId}/decision`, - payload, +): Promise { + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/findings/${findingId}/decision`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + decision, + }), + }, ); - return response.data; + return handleResponse(response); } -export async function applyReview( - reviewId: string, -): Promise { - const response = await api.post( - `/redline/reviews/${reviewId}/apply`, +export async function applyReview(reviewId: string): Promise { + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/apply`, + { + method: "POST", + }, ); - return response.data; + return handleResponse(response); } export async function exportReview(reviewId: string): Promise { - const response = await api.get(`/redline/reviews/${reviewId}/export`, { - responseType: "blob", - }); + const response = await fetch( + `${API_BASE_URL}/redline/reviews/${reviewId}/export`, + { + method: "POST", + }, + ); + + if (!response.ok) { + let message = "Failed to export the document."; + + try { + const data = await response.json(); + + if (data.detail) { + message = data.detail; + } + } catch { + // Response was not JSON. + } + + throw new Error(message); + } - return response.data; + return response.blob(); } diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx new file mode 100644 index 00000000..327c0aae --- /dev/null +++ b/use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx @@ -0,0 +1,155 @@ +import { useState } from "react"; + +interface DocumentUploadProps { + onAnalyze: (dealerFile: File, masterFile: File, playbookFile: File) => void; + + loading: boolean; +} + +interface FilePickerProps { + label: string; + description: string; + file: File | null; + onChange: (file: File | null) => void; + disabled: boolean; +} + +function FilePicker({ + label, + description, + file, + onChange, + disabled, +}: FilePickerProps) { + function handleChange(event: React.ChangeEvent) { + const selectedFile = event.target.files?.[0] ?? null; + + onChange(selectedFile); + } + + return ( +

+
+

{label}

+ +

{description}

+
+ + +
+ ); +} + +function DocumentUpload({ onAnalyze, loading }: DocumentUploadProps) { + const [dealerFile, setDealerFile] = useState(null); + + const [masterFile, setMasterFile] = useState(null); + + const [playbookFile, setPlaybookFile] = useState(null); + + const canAnalyze = + dealerFile !== null && + masterFile !== null && + playbookFile !== null && + !loading; + + function handleAnalyze() { + if (!dealerFile || !masterFile || !playbookFile) { + return; + } + + onAnalyze(dealerFile, masterFile, playbookFile); + } + + return ( +
+
+

+ Document Setup +

+ +

+ Upload agreement documents +

+ +

+ Upload the dealer agreement, approved master agreement, and + negotiation playbook to begin the redline review. +

+
+ +
+ + + + + +
+ +
+ +
+
+ ); +} + +export default DocumentUpload; diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx index 8be1aaa6..4ccf7068 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx @@ -1,33 +1,27 @@ +import DocumentUpload from "./DocumentUpload"; + interface EmptyStateProps { - onStartReview: () => void; + loading: boolean; + + onAnalyze: (dealerFile: File, masterFile: File, playbookFile: File) => void; } -function EmptyState({ onStartReview }: EmptyStateProps) { +function EmptyState({ loading, onAnalyze }: EmptyStateProps) { return ( -
-
-
- § -
- -

- Ready to review the agreement +
+
+

+ Start a new redline review

-

- Compare the dealer agreement against the approved master - agreement and negotiation playbook. +

+ Provide the three documents required to compare and review + the dealer agreement.

- -
-

+ + +
); } diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx index 141a867b..e88b9312 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx @@ -1,13 +1,13 @@ -import type { Deviation, FindingDecision } from "../types/redline"; +import { Check, ChevronDown, ChevronRight, X } from "lucide-react"; +import { useState } from "react"; + +import type { FindingDecision, RedlineFinding } from "../types/redline"; import ClassificationBadge from "./ClassificationBadge"; import ComparisonBlock from "./ComparisonBlock"; interface FindingCardProps { - finding: Deviation & { - id?: string; - decision?: FindingDecision; - }; + finding: RedlineFinding; index: number; loading?: boolean; onDecision?: (decision: "approve" | "reject") => void; @@ -19,105 +19,164 @@ function FindingCard({ loading = false, onDecision, }: FindingCardProps) { - const decision = finding.decision; + const [expanded, setExpanded] = useState( + index === 0 && finding.decision === "pending", + ); + + const decision: FindingDecision = finding.decision; + + const reviewed = decision !== "pending"; return ( -
- {/* Header */} -
-
-

- Finding {index + 1} -

- -

+
+ {/* Collapsed header */} +

- - {/* Comparison */} -
- - - -
- - {/* Reason */} -
-

- Reason -

- -

- {finding.reason} -

-
- - {/* Counter position */} -
-

- Counter-position -

- -

- {finding.counter_position} -

-
- - {/* Decision */} -
-
-

- Decision -

- -

- {decision === "approve" && "✓ Approved"} - {decision === "reject" && "✕ Rejected"} - {(!decision || decision === "pending") && - "Pending review"} -

+
+ {decision === "approve" && ( + + + Approved + + )} + + {decision === "reject" && ( + + + Rejected + + )} + + {decision === "pending" && ( + + Pending + + )}
+ + + {/* Expanded content */} + {expanded && ( +
+
+ + + +
+ +
+

+ Reason +

+ +

+ {finding.reason} +

+
+ +
+

+ Counter-position +

- {(!decision || decision === "pending") && ( -
- - - +

+ {finding.counter_position} +

- )} -
+ + {!reviewed && ( +
+

+ Review this finding before applying changes. +

+ +
+ + + +
+
+ )} + + {reviewed && ( +
+

+ {decision === "approve" ? ( + <> + + Finding approved + + ) : ( + <> + + Finding rejected + + )} +

+
+ )} +
+ )}
); } diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx index 45f4b4be..13a12d50 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx @@ -1,15 +1,10 @@ -import type { Deviation, FindingDecision } from "../types/redline"; +import type { RedlineFinding } from "../types/redline"; import FindingCard from "./FindingCard"; interface FindingListProps { - findings: (Deviation & { - id?: string; - decision?: FindingDecision; - })[]; - + findings: RedlineFinding[]; processingFindingId?: string | null; - onDecision: (findingId: string, decision: "approve" | "reject") => void; } @@ -20,30 +15,33 @@ function FindingList({ }: FindingListProps) { return (
-
-

- Findings -

- -

- Review each proposed deviation before applying changes. -

+
+
+

+ Findings +

+ +

+ Expand a finding to review its details. +

+
+ + + {findings.length}{" "} + {findings.length === 1 ? "finding" : "findings"} +
-
+
{findings.map((finding, index) => ( { - if (!finding.id) { - return; - } - - onDecision(finding.id, decision); - }} + onDecision={(decision) => + onDecision(finding.id, decision) + } /> ))}
diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx index 0e3f0e25..7c751b51 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx @@ -1,30 +1,24 @@ -interface HeaderProps { - loading: boolean; - onStartReview: () => void; -} +import { FileText } from "lucide-react"; -function Header({ loading, onStartReview }: HeaderProps) { +function Header() { return ( -
-
-
-

- Dealer Agreement Redline -

+
+
+
+
+ +
-

- Review and approve proposed contract changes -

-
+
+

+ Dealer Agreement Redline +

- +

+ Review and approve contract changes +

+
+
); diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx b/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx index 92f5a3f3..04815bc6 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx +++ b/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx @@ -1,3 +1,5 @@ +import { CheckCircle2, X } from "lucide-react"; + interface SuccessMessageProps { message: string; onDismiss?: () => void; @@ -6,16 +8,20 @@ interface SuccessMessageProps { function SuccessMessage({ message, onDismiss }: SuccessMessageProps) { return (
-

{message}

+
+ + +

{message}

+
{onDismiss && ( )}
diff --git a/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts b/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts index 44aa9e9f..2a236267 100644 --- a/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts +++ b/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts @@ -116,3 +116,18 @@ export interface ApplyReviewResponse { export interface ApiError { detail: string; } + +export interface DecisionResponse { + review_id: string; + finding_id: string; + decision: "approve" | "reject"; + review_status: ReviewStatus; + remaining_findings: number; +} + +export interface ApplyResponse { + review_id: string; + status: "applied"; + applied: boolean; + chat_response: unknown; +} From 7502a15474fa4fad777cae18c0c70da58810e023 Mon Sep 17 00:00:00 2001 From: Pritesh Umraniya Date: Thu, 20 Aug 2026 22:56:45 +0530 Subject: [PATCH 5/5] feat: add dealer agreement redline desk --- .../backend/.env.example | 2 - .../dealer-agreement-redline/.gitignore | 0 .../dealer-agreement-redline/README.md | 0 .../README_updated.md | 288 ++++++++++++++++++ .../backend/app/__init__.py | 0 .../backend/app/api/__init__.py | 0 .../backend/app/api/poc.py | 0 .../backend/app/api/redline.py | 0 .../backend/app/api/superdocs.py | 0 .../backend/app/config.py | 0 .../backend/app/db/base.py | 0 .../backend/app/db/init_db.py | 0 .../backend/app/db/session.py | 0 .../backend/app/main.py | 0 .../backend/app/models/redline_review.py | 0 .../backend/app/services/__init__.py | 0 .../backend/app/services/agreement_parser.py | 0 .../app/services/deviation_analyzer.py | 0 .../app/services/playbook_classifier.py | 0 .../backend/app/services/redline_service.py | 0 .../backend/app/services/superdocs_redline.py | 0 .../backend/app/superdocs/__init__.py | 0 .../backend/app/superdocs/client.py | 0 .../backend/requirements.txt | 0 .../frontend/.gitignore | 0 .../frontend/README.md | 0 .../frontend/eslint.config.js | 0 .../frontend/index.html | 0 .../frontend/package-lock.json | 0 .../frontend/package.json | 0 .../frontend/public/favicon.svg | 0 .../frontend/public/icons.svg | 0 .../frontend/src/App.css | 0 .../frontend/src/App.tsx | 0 .../frontend/src/api/redline.ts | 0 .../frontend/src/components/ActionBar.tsx | 0 .../src/components/ClassificationBadge.tsx | 0 .../src/components/ComparisonBlock.tsx | 0 .../src/components/DocumentUpload.tsx | 0 .../frontend/src/components/EmptyState.tsx | 0 .../frontend/src/components/FindingCard.tsx | 0 .../frontend/src/components/FindingList.tsx | 0 .../frontend/src/components/Header.tsx | 0 .../frontend/src/components/LoadingState.tsx | 0 .../src/components/ReviewProgress.tsx | 0 .../frontend/src/components/ReviewSummary.tsx | 0 .../frontend/src/components/StatusBadge.tsx | 0 .../src/components/SuccessMessage.tsx | 0 .../frontend/src/components/SummaryCard.tsx | 0 .../frontend/src/index.css | 0 .../frontend/src/main.tsx | 0 .../frontend/src/types/redline.ts | 0 .../frontend/tsconfig.app.json | 0 .../frontend/tsconfig.json | 0 .../frontend/tsconfig.node.json | 0 .../frontend/vite.config.ts | 0 .../test-data/dealer-agreement.docx | Bin .../test-data/master-agreement.docx | Bin .../test-data/negotiation-playbook.docx | Bin 59 files changed, 288 insertions(+), 2 deletions(-) delete mode 100644 use-cases/dealer-agreement-redline/backend/.env.example rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/.gitignore (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/README.md (100%) create mode 100644 use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/__init__.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/api/__init__.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/api/poc.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/api/redline.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/api/superdocs.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/config.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/db/base.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/db/init_db.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/db/session.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/main.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/models/redline_review.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/__init__.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/agreement_parser.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/deviation_analyzer.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/playbook_classifier.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/redline_service.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/services/superdocs_redline.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/superdocs/__init__.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/app/superdocs/client.py (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/backend/requirements.txt (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/.gitignore (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/README.md (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/eslint.config.js (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/index.html (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/package-lock.json (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/package.json (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/public/favicon.svg (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/public/icons.svg (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/App.css (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/App.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/api/redline.ts (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/ActionBar.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/EmptyState.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/FindingCard.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/FindingList.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/Header.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/LoadingState.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/index.css (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/main.tsx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/src/types/redline.ts (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/tsconfig.app.json (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/tsconfig.json (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/tsconfig.node.json (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/frontend/vite.config.ts (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/test-data/dealer-agreement.docx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/test-data/master-agreement.docx (100%) rename use-cases/{ => pritesh2564u}/dealer-agreement-redline/test-data/negotiation-playbook.docx (100%) diff --git a/use-cases/dealer-agreement-redline/backend/.env.example b/use-cases/dealer-agreement-redline/backend/.env.example deleted file mode 100644 index 17d4569b..00000000 --- a/use-cases/dealer-agreement-redline/backend/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -DATABASE_URL= -SUPERDOCS_API_KEY= \ No newline at end of file diff --git a/use-cases/dealer-agreement-redline/.gitignore b/use-cases/pritesh2564u/dealer-agreement-redline/.gitignore similarity index 100% rename from use-cases/dealer-agreement-redline/.gitignore rename to use-cases/pritesh2564u/dealer-agreement-redline/.gitignore diff --git a/use-cases/dealer-agreement-redline/README.md b/use-cases/pritesh2564u/dealer-agreement-redline/README.md similarity index 100% rename from use-cases/dealer-agreement-redline/README.md rename to use-cases/pritesh2564u/dealer-agreement-redline/README.md diff --git a/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md b/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md new file mode 100644 index 00000000..21400e14 --- /dev/null +++ b/use-cases/pritesh2564u/dealer-agreement-redline/README_updated.md @@ -0,0 +1,288 @@ +# Dealer Agreement Redline Desk + +A contract-redlining workflow built with FastAPI, PostgreSQL, and the SuperDocs API. + +The application compares a dealer-returned agreement against an approved master agreement and a negotiation playbook, detects deviations, classifies them, and routes them through a human-in-the-loop approval process before applying redlines and exporting the final document. + +--- + +## Current Status + +### Backend + +- [x] SuperDocs API integration +- [x] API key configuration +- [x] Dealer agreement upload +- [x] Master agreement attachment upload +- [x] Negotiation playbook attachment upload +- [x] DOCX section extraction +- [x] Agreement deviation detection +- [x] Playbook-based classification +- [x] Redline review persistence +- [x] Finding persistence +- [x] Human approve/reject workflow +- [x] Apply approved redlines through SuperDocs +- [x] Export final DOCX +- [x] Duplicate apply protection +- [x] Review-state validation +- [x] Backend compilation verification +- [x] End-to-end workflow verification + +### Frontend + +The frontend is implemented with React, TypeScript, Vite, Tailwind CSS, and Lucide React icons. + +It provides the complete user-facing workflow: + +- Dynamic upload of dealer, master, and playbook DOCX files +- Review summary +- Finding classification badges +- Collapsible finding cards +- Master vs dealer comparison +- Reason and counter-position display +- Approve/reject controls +- Review progress +- Apply approved changes +- Export final DOCX +- Loading, success, and error states +- Fixed header +- Responsive layout +- Lucide icons + +### Frontend Structure + +```text +frontend/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── index.html +└── src/ + ├── main.tsx + ├── App.tsx + ├── api/ + │ └── redline.ts + ├── components/ + │ ├── DocumentUpload.tsx + │ ├── EmptyState.tsx + │ ├── FindingCard.tsx + │ ├── FindingList.tsx + │ ├── Header.tsx + │ └── SuccessMessage.tsx + └── types/ + └── redline.ts +``` + +### Frontend Workflow + +```text +Upload Documents + │ + ▼ +POST /redline/analyze + │ + ▼ +Review Dashboard + │ + ├── Summary + ├── Classification + ├── Master language + ├── Dealer language + ├── Reason + └── Counter-position + │ + ▼ +Approve / Reject findings + │ + ▼ +All findings reviewed + │ + ▼ +Apply Approved Changes + │ + ▼ +Export DOCX +``` + +### Finding UX + +Findings are collapsed by default so reviews with many deviations remain compact. + +The collapsed card shows the finding number, section, classification, and decision status. Expanding a card reveals the master text, dealer text, reason, counter-position, and approve/reject actions. + +### Frontend Development + +From the frontend directory: + +```powershell +npm install +npm run dev +``` + +The Vite development server runs at: + +```text +http://localhost:5173 +``` + +Production build: + +```powershell +npm run build +``` + +The frontend communicates only with the FastAPI backend. It never receives or exposes the SuperDocs API key. + +## Security + +The SuperDocs API key is server-side only. + +The frontend must never contain: + +``` +SUPERDOCS_API_KEY +``` + +The frontend must never make direct requests to: + +``` +https://api.superdocs.app +``` + +All SuperDocs operations must go through the FastAPI backend. + +The `.env` file must never be committed to Git. + +--- + +## Git Ignore + +The repository should ignore local environment files and generated files: + +```gitignore +# Environment +.env +.env.* + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node +node_modules/ +dist/ + +# IDE +.vscode/ +.idea/ + +# Generated export +dealer-agreement-redlined.docx +``` + +Do not use a blanket: + +```gitignore +*.docx +``` + +rule. + +The test DOCX files under `test-data/` are required project files and should remain tracked: + +``` +test-data/ +├── dealer-agreement.docx +├── master-agreement.docx +└── negotiation-playbook.docx +``` + +--- + +## Project Directory + +The current project structure is: + +``` +dealer-agreement-redline/ +│ +├── backend/ +│ ├── app/ +│ ├── .env +│ └── requirements.txt +│ +├── frontend/ +│ +├── test-data/ +│ ├── dealer-agreement.docx +│ ├── master-agreement.docx +│ └── negotiation-playbook.docx +│ +├── .gitignore +└── README.md +``` + +--- + +## Development Notes + +This project is a focused proof of concept for the dealer-agreement redline workflow. + +The backend currently provides the complete functional workflow: + +``` +Analyze + ↓ +Persist Review + ↓ +Review Findings + ↓ +Approve / Reject + ↓ +Apply Approved Changes + ↓ +Export DOCX +``` + +The backend is complete and tested before starting frontend implementation. + +The frontend will consume the existing backend APIs and provide the user-facing review interface. + +The SuperDocs API key remains completely server-side. + +--- + +## Current Milestone + +### Task 2 + +```text +STATUS: COMPLETE +``` + +Completed: + +- ✓ SuperDocs integration +- ✓ Dynamic dealer/master/playbook uploads +- ✓ Dealer document loaded into the SuperDocs session +- ✓ Agreement parsing +- ✓ Deviation analysis +- ✓ Playbook classification +- ✓ PostgreSQL persistence +- ✓ Human review +- ✓ Approve/reject decisions +- ✓ Apply approved changes +- ✓ Export DOCX +- ✓ Workflow validation +- ✓ Duplicate apply protection +- ✓ Responsive React frontend +- ✓ Collapsible findings +- ✓ Fixed header +- ✓ Lucide icons +- ✓ User-friendly loading/success/error states +- ✓ End-to-end workflow verification +- ✓ Backend compilation verification + +The complete Task 2 workflow is implemented and tested. diff --git a/use-cases/dealer-agreement-redline/backend/app/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/__init__.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/__init__.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/__init__.py diff --git a/use-cases/dealer-agreement-redline/backend/app/api/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/__init__.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/api/__init__.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/__init__.py diff --git a/use-cases/dealer-agreement-redline/backend/app/api/poc.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/poc.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/api/poc.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/poc.py diff --git a/use-cases/dealer-agreement-redline/backend/app/api/redline.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/redline.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/api/redline.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/redline.py diff --git a/use-cases/dealer-agreement-redline/backend/app/api/superdocs.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/superdocs.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/api/superdocs.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/api/superdocs.py diff --git a/use-cases/dealer-agreement-redline/backend/app/config.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/config.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/config.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/config.py diff --git a/use-cases/dealer-agreement-redline/backend/app/db/base.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/base.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/db/base.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/base.py diff --git a/use-cases/dealer-agreement-redline/backend/app/db/init_db.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/init_db.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/db/init_db.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/init_db.py diff --git a/use-cases/dealer-agreement-redline/backend/app/db/session.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/session.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/db/session.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/db/session.py diff --git a/use-cases/dealer-agreement-redline/backend/app/main.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/main.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/main.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/main.py diff --git a/use-cases/dealer-agreement-redline/backend/app/models/redline_review.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/models/redline_review.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/models/redline_review.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/models/redline_review.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/__init__.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/__init__.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/__init__.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/agreement_parser.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/agreement_parser.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/agreement_parser.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/deviation_analyzer.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/deviation_analyzer.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/deviation_analyzer.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/playbook_classifier.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/playbook_classifier.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/playbook_classifier.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/redline_service.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/redline_service.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/redline_service.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/redline_service.py diff --git a/use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/superdocs_redline.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/services/superdocs_redline.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/services/superdocs_redline.py diff --git a/use-cases/dealer-agreement-redline/backend/app/superdocs/__init__.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/__init__.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/superdocs/__init__.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/__init__.py diff --git a/use-cases/dealer-agreement-redline/backend/app/superdocs/client.py b/use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/client.py similarity index 100% rename from use-cases/dealer-agreement-redline/backend/app/superdocs/client.py rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/app/superdocs/client.py diff --git a/use-cases/dealer-agreement-redline/backend/requirements.txt b/use-cases/pritesh2564u/dealer-agreement-redline/backend/requirements.txt similarity index 100% rename from use-cases/dealer-agreement-redline/backend/requirements.txt rename to use-cases/pritesh2564u/dealer-agreement-redline/backend/requirements.txt diff --git a/use-cases/dealer-agreement-redline/frontend/.gitignore b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/.gitignore similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/.gitignore rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/.gitignore diff --git a/use-cases/dealer-agreement-redline/frontend/README.md b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/README.md similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/README.md rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/README.md diff --git a/use-cases/dealer-agreement-redline/frontend/eslint.config.js b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/eslint.config.js similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/eslint.config.js rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/eslint.config.js diff --git a/use-cases/dealer-agreement-redline/frontend/index.html b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/index.html similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/index.html rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/index.html diff --git a/use-cases/dealer-agreement-redline/frontend/package-lock.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package-lock.json similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/package-lock.json rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/package-lock.json diff --git a/use-cases/dealer-agreement-redline/frontend/package.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/package.json similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/package.json rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/package.json diff --git a/use-cases/dealer-agreement-redline/frontend/public/favicon.svg b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/favicon.svg similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/public/favicon.svg rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/favicon.svg diff --git a/use-cases/dealer-agreement-redline/frontend/public/icons.svg b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/icons.svg similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/public/icons.svg rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/public/icons.svg diff --git a/use-cases/dealer-agreement-redline/frontend/src/App.css b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.css similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/App.css rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.css diff --git a/use-cases/dealer-agreement-redline/frontend/src/App.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/App.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/App.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/api/redline.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/api/redline.ts similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/api/redline.ts rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/api/redline.ts diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ActionBar.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/ActionBar.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ActionBar.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ClassificationBadge.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ComparisonBlock.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/DocumentUpload.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/EmptyState.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/EmptyState.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/EmptyState.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingCard.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/FindingCard.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingCard.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingList.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/FindingList.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/FindingList.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/Header.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/Header.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/Header.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/LoadingState.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/LoadingState.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/LoadingState.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewProgress.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/ReviewSummary.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/StatusBadge.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SuccessMessage.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/components/SummaryCard.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/index.css b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/index.css similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/index.css rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/index.css diff --git a/use-cases/dealer-agreement-redline/frontend/src/main.tsx b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/main.tsx similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/main.tsx rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/main.tsx diff --git a/use-cases/dealer-agreement-redline/frontend/src/types/redline.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/types/redline.ts similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/src/types/redline.ts rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/src/types/redline.ts diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.app.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.app.json similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/tsconfig.app.json rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.app.json diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.json similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/tsconfig.json rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.json diff --git a/use-cases/dealer-agreement-redline/frontend/tsconfig.node.json b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.node.json similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/tsconfig.node.json rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/tsconfig.node.json diff --git a/use-cases/dealer-agreement-redline/frontend/vite.config.ts b/use-cases/pritesh2564u/dealer-agreement-redline/frontend/vite.config.ts similarity index 100% rename from use-cases/dealer-agreement-redline/frontend/vite.config.ts rename to use-cases/pritesh2564u/dealer-agreement-redline/frontend/vite.config.ts diff --git a/use-cases/dealer-agreement-redline/test-data/dealer-agreement.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/dealer-agreement.docx similarity index 100% rename from use-cases/dealer-agreement-redline/test-data/dealer-agreement.docx rename to use-cases/pritesh2564u/dealer-agreement-redline/test-data/dealer-agreement.docx diff --git a/use-cases/dealer-agreement-redline/test-data/master-agreement.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/master-agreement.docx similarity index 100% rename from use-cases/dealer-agreement-redline/test-data/master-agreement.docx rename to use-cases/pritesh2564u/dealer-agreement-redline/test-data/master-agreement.docx diff --git a/use-cases/dealer-agreement-redline/test-data/negotiation-playbook.docx b/use-cases/pritesh2564u/dealer-agreement-redline/test-data/negotiation-playbook.docx similarity index 100% rename from use-cases/dealer-agreement-redline/test-data/negotiation-playbook.docx rename to use-cases/pritesh2564u/dealer-agreement-redline/test-data/negotiation-playbook.docx