diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/.env.example b/use-cases/yemulambika/tax-technical-memorandum-builder/.env.example new file mode 100644 index 00000000..e36f513a --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/.env.example @@ -0,0 +1,4 @@ +SUPERDOCS_API_KEY=your-key-here +SUPERDOCS_BASE_URL=https://api.superdocs.app +SUPERDOCS_MODE=fake +RUN_SUPERDOCS_LIVE_TESTS=0 diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/.gitignore b/use-cases/yemulambika/tax-technical-memorandum-builder/.gitignore new file mode 100644 index 00000000..0c335bb1 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/.gitignore @@ -0,0 +1,25 @@ +node_modules/ +dist/ +coverage/ +local-output/ +.e2e-runtime/ +playwright-report/ +test-results/ +downloads/ +browser-profile/ +*.tsbuildinfo +*.trace +*.zip +*.webm +*.mp4 +.env +.env.* +!.env.example + +# Generated only by Playwright/live verification; review-workflow.png is curated. +docs/evidence/offline-duplicate-analysis-proposals.png +docs/evidence/ui-live/ +docs/evidence/ui-live-superdocs-validation.json +docs/evidence/client-export-render.png +docs/evidence/file-export-render.png +docs/evidence/manual-live/ diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/.prettierignore b/use-cases/yemulambika/tax-technical-memorandum-builder/.prettierignore new file mode 100644 index 00000000..8297e7f6 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/.prettierignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +coverage/ +local-output/ +.e2e-runtime/ +playwright-report/ +test-results/ +fixtures/ +docs/evidence/*.png diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/README.md b/use-cases/yemulambika/tax-technical-memorandum-builder/README.md new file mode 100644 index 00000000..99b0e12d --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/README.md @@ -0,0 +1,100 @@ +# Tax Technical Memorandum Builder + +This build gives accounting and in-house tax reviewers an evidence-first way to turn four synthetic source documents into a reviewable tax memorandum. In the demonstrated workflow it verifies three quotation candidates, traces five supported monetary values, refuses one fabricated quotation and one unmatched number, and keeps two same-heading proposals independently actionable through approval and HTML export. + +![Tax Technical Memorandum Builder review workflow](docs/evidence/review-workflow.png) + +Built by Ambika Yemul for the SuperDocs task. + +## What it builds and who it serves + +The React interface walks a tax manager through facts, the question presented, supplied authorities, analysis, conclusion, and confidence. It is designed for reviewers who need citations and source-number traceability before they accept AI-proposed edits. The included Deccan Byteworks scenario and every document in `fixtures/` are fictional; this project is not tax advice and is not production-ready. + +The workflow produces two HTML views from the same reviewed session state: + +- **Client-ready HTML** contains the supported memorandum without internal warnings. +- **File-ready HTML** retains the same approved content and adds the authorities table, numeric traceability register, unsupported-authority warning, and reviewer decision log. + +DOCX and PDF export are not claimed. + +## Safeguards and human review + +- Exact quotation verification normalizes whitespace but requires the quoted text to exist in its attached authority and retains its citation and locator. +- The authorities table distinguishes verified sources from an intentionally unattached/fabricated authority. Missing or unattached authority blocks finalization. +- Monetary values remain strings. Deterministic calculations and source-row IDs provide numeric traceability without binary floating-point arithmetic. +- A narrative-wide scan refuses unmatched monetary figures. The intentional negative control must be corrected before approval or export becomes available. +- Proposal identity uses the upstream change ID. A deterministic local fingerprint is used only when upstream identity is absent; heading text is never identity. +- Duplicate `Analysis` headings render as separate cards with independent decisions and reviewer notes. +- Every mandatory proposal requires an explicit decision and note. Approved material remains; rejected material is excluded from both outputs. +- An application fingerprint suppresses an identical second decision submission before it can replay the upstream approval request. +- Polling stops at `awaiting_approval`, `completed`, or `failed`. + +## Architecture and SuperDocs operations + +The browser calls only same-origin `/api/superdocs/*` routes. A Node BFF owns the real client, reads `SUPERDOCS_API_KEY` only from its process environment or the project-local ignored `.env`, and adds the Bearer header server-side. No `VITE_*` secret is accepted. Real mode fails visibly when configuration or an upstream request fails; it does not fall back to the offline adapter. + +The build uses four logical SuperDocs operation groups: + +1. **Upload** — `POST /v1/documents/upload-base64` once for each of the four inputs. +2. **Chat** — `POST /v1/chat/async`, followed by bounded `GET /v1/jobs/{job_id}` polling. +3. **Approve** — `POST /v1/chat/{session_id}/approve` with separate approved/rejected change decisions and notes. +4. **Export** — `POST /v1/documents/export` for client-ready and file-ready HTML. + +See [the contract record](docs/superdocs-contract.md) and [live runbook](docs/SUPERDOCS_RUNBOOK.md). + +## Run the quota-free offline demo + +Prerequisites: Node.js 20+, npm, and Playwright Chromium. + +```bash +npm ci +npx playwright install chromium +npm test +npm run typecheck +npm run lint +npm run format:check +npm run build +npm run test:ui +``` + +Start the offline BFF and frontend in separate terminals: + +```bash +npm run bff:offline +``` + +```bash +npm run dev -- --host 127.0.0.1 --port 4175 +``` + +Open `http://127.0.0.1:4175`, create the case, and select the four files in `fixtures/`. Filename validation accepts any browser `FileList` order and uploads them internally in the displayed canonical order. + +## Optional live mode + +Set these variables only in the BFF/server environment; never expose the key to Vite or browser code: + +```text +SUPERDOCS_API_KEY=your-key-here +SUPERDOCS_BASE_URL=https://api.superdocs.app +SUPERDOCS_MODE=real +``` + +Then start `npm run bff` and the same frontend command shown above. The quota-bearing live harnesses are separately opt-in with `RUN_SUPERDOCS_LIVE_TESTS=1`; do not run `npm run test:live` or `npm run test:ui:live` without explicit authorization. + +## Verified results + +The real visible workflow succeeded with four uploaded documents and reached `awaiting_approval`. It displayed three quotation checks and monetary traceability, approved one proposal, rejected one proposal, reached zero mandatory pending decisions, submitted the reviewed content once, and downloaded client-ready and file-ready HTML. Both files rendered in Chromium; approved content and citations remained, while rejected and fabricated material was absent. + +The observed live exports contained an orphan duplicate `Analysis` heading after rejection. The renderer was then corrected so a rejected proposal's complete block, including its heading, is removed. Regression tests prove the approved `Analysis` section remains and no empty heading survives. No corrected second live API run was made. + +The public package intentionally includes only a sanitized verification summary and one offline screenshot. It excludes credentials, browser profiles, recordings, traces, raw Playwright output, temporary downloads, and the historical live export files. + +## Limitations + +- The public evidence proves the corrected renderer offline; it does not claim a second corrected live export. +- Only HTML exports were downloaded and inspected. DOCX and PDF are not claimed. +- The demonstration is a single fictional scenario and is not legal, tax, or accounting advice. +- State is in memory and single-process. Authentication, persistence, multi-tenant isolation, deployment hardening, rate-limit coordination, and production observability are out of scope. +- See [the full limitations record](docs/limitations.md). + +There is no public deployment or video included; the reviewed workflow screenshot above is the public visual demonstration. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/docs/SUPERDOCS_RUNBOOK.md b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/SUPERDOCS_RUNBOOK.md new file mode 100644 index 00000000..0340123e --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/SUPERDOCS_RUNBOOK.md @@ -0,0 +1,9 @@ +# SuperDocs live runbook + +1. Set `SUPERDOCS_BASE_URL=https://api.superdocs.app`, `SUPERDOCS_MODE=real`, and `SUPERDOCS_API_KEY` in the BFF process. Never use a `VITE_*` name. Add `RUN_SUPERDOCS_LIVE_TESTS=1` only for an explicitly authorized live test. +2. Run offline checks first: `npm test`, `npm run lint`, `npm run format:check`, `npm run build`, and `npm run test:ui`. +3. Run `npm run test:live`. It must stop on any non-2xx response, malformed proposal, failed/cancelled job, timeout, invalid quotation, unmatched number, or undecided mandatory proposal. Never substitute the fake adapter. +4. Inspect each downloaded HTML file structurally and by rendering it. This build does not claim DOCX or PDF export. +5. Live harness output belongs under ignored `local-output/`. Before sharing evidence, create a sanitized summary containing only endpoint templates, status codes, truncated IDs, durations, decisions, counts, filenames, and SHA-256 hashes. Never include credentials, authorization headers, source text, traces, recordings, or browser profiles. + +The approval action approves proposed memorandum content only. It does not make a tax decision. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/review-workflow.png b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/review-workflow.png new file mode 100644 index 00000000..37cbe74b Binary files /dev/null and b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/review-workflow.png differ diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/verification-summary.json b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/verification-summary.json new file mode 100644 index 00000000..4e471e74 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/evidence/verification-summary.json @@ -0,0 +1,67 @@ +{ + "schema_version": 1, + "scope": "sanitized public verification summary", + "offline_verification": { + "unit_test_files_passed": 2, + "unit_tests_passed": 18, + "playwright_tests_passed": 4, + "playwright_retries": 0, + "typescript": "passed", + "eslint": "passed", + "prettier": "passed", + "production_build": "passed", + "dependency_vulnerabilities": 0 + }, + "observed_manual_live_workflow": { + "uploaded_document_count": 4, + "terminal_job_status": "awaiting_approval", + "quotation_checks": 3, + "verified_quotations": 2, + "rejected_fabricated_quotations": 1, + "traced_monetary_values": 5, + "unmatched_negative_controls_before_correction": 1, + "unmatched_negative_controls_after_correction": 0, + "approved_proposals": 1, + "rejected_proposals": 1, + "mandatory_pending_after_review": 0, + "reviewed_content_submission": "succeeded", + "exports": [ + { + "filename": "synthetic-tax-memo-client.html", + "format": "html", + "bytes": 2378, + "sha256": "2c18c7d1b9654ce0cf5dede7b9880d24c82ba8477075a051736026681ab511fe", + "rendered_in_chromium": true + }, + { + "filename": "synthetic-tax-memo-file.html", + "format": "html", + "bytes": 3034, + "sha256": "f0373c703b9e728c03b679c6d9984f20f8f308647636f0be50f023236eb3058f", + "rendered_in_chromium": true + } + ], + "rejected_material_absent": true, + "approved_material_and_citations_present": true, + "historical_orphan_heading_observed": true + }, + "post_live_renderer_correction": { + "verified_offline": true, + "approved_heading_and_content_retained": true, + "rejected_heading_and_content_absent": true, + "orphan_heading_absent": true, + "corrected_second_live_run_performed": false + }, + "public_screenshot": { + "path": "review-workflow.png", + "bytes": 164005, + "width": 1280, + "height": 2930, + "sha256": "1284f553f741897c3110fe27dab30f88e856c5d59fc2a003854b89753e2c63c6", + "scope": "offline duplicate-heading review workflow" + }, + "additional_live_requests_during_pr_preparation": 0, + "contains_credentials": false, + "contains_personal_email": false, + "contains_source_document_text": false +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/docs/limitations.md b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/limitations.md new file mode 100644 index 00000000..0e579f4f --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/limitations.md @@ -0,0 +1,9 @@ +# Limitations + +- Only HTML was requested, downloaded, hashed, and rendered. DOCX and PDF are not claimed. +- SuperDocs approval replay returned HTTP 400 during an earlier harness observation. Duplicate submission safety is therefore application-side fingerprint suppression, not a claim that the hosted approval endpoint is replay-idempotent. +- The supervised visible live workflow completed, but its exports contained an orphan duplicate `Analysis` heading after the rejected proposal was removed. The corrected renderer is regression-tested offline only; no second live API run was made. +- The completed manual browser session did not preserve trustworthy per-request timing, so the manual job duration is unavailable and is not reconstructed. +- Public evidence is deliberately minimal and sanitized: one offline screenshot plus a verification summary. Recordings, browser profiles, traces, raw results, and historical exports are excluded. +- The case, authorities, people, and monetary values are wholly fictional. This build is not tax advice and is not production-ready. +- The BFF stores state in memory. Authentication, durable storage, multi-tenant isolation, deployment hardening, rate-limit coordination, and production observability are out of scope. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/docs/superdocs-contract.md b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/superdocs-contract.md new file mode 100644 index 00000000..2a0273a4 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/docs/superdocs-contract.md @@ -0,0 +1,16 @@ +# SuperDocs contract record — 2026-08-13 + +Source: official `superdocsapp/docs` OpenAPI and examples. Authentication is `Authorization: Bearer `. This build requires the server-side base URL to be exactly `https://api.superdocs.app`. + +The build uses `upload_document_base64`, `chat_async` in `ask_every_time` mode, `get_job`, per-change HITL approval, and `export_document`. Polling is bounded and terminates on `awaiting_approval`, `completed`, or `failed`. Proposed changes may be an array or a JSON-encoded string and are schema-checked after the optional second parse. + +Live observation on 2026-08-15: replaying an identical approval after the first successful submission returned HTTP 400. The application therefore fingerprints a reviewed decision payload and suppresses an identical repeat instead of replaying it upstream. This is application-level idempotency; the hosted approval endpoint is not claimed to be replay-idempotent. + +Current OpenAPI location: while status is `awaiting_approval`, proposals are +`metadata.pending_changes`; completed compact responses may expose +`result.document_changes.pending_changes` or `chunk_diffs`. Each `PendingChange` uses +`change_id`, `old_html`, and `new_html`. Any other shape fails visibly. Export bytes are not +considered valid merely because the HTTP request succeeded; live verification must inspect +non-empty content/type and expected sections. + +This submission exports and claims HTML only. The client-ready and file-ready variants are generated from the same reviewed state before each HTML export request. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/export-visual.spec.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/export-visual.spec.ts new file mode 100644 index 00000000..1f8db938 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/export-visual.spec.ts @@ -0,0 +1,115 @@ +import { expect, test } from "@playwright/test"; +import type { Proposal } from "../src/domain"; +import { renderReviewedExports } from "../src/export-renderer"; + +const reviewedState = { + draftHtml: + "

Tax Technical Memorandum

Facts

Synthetic facts.

Question presented

Synthetic question.

Authorities

Synthetic Tax Code § 10(1)

Synthetic Tax Regulations 24(2)

Analysis

INR 1200000.00 is traced.

Conclusion

Supported conclusion.

Confidence level

Moderate

", + proposals: [ + { + id: "approved-analysis", + section: "Analysis", + before: "", + after: + "

Approved Analysis

Approved proposal content remains.

", + decision: "APPROVED", + reviewerNote: "Retain", + }, + { + id: "rejected-analysis", + section: "Analysis", + before: "", + after: + "

Rejected Analysis

Rejected proposal content is removed.

", + decision: "REJECTED", + reviewerNote: "Exclude", + }, + { + id: "orphan-heading", + section: "Analysis", + before: "", + after: "

Analysis

", + decision: "APPROVED", + reviewerNote: "Heading-only fragment", + }, + ] satisfies Proposal[], + authorities: [ + { title: "Synthetic Basis Rule", citation: "Synthetic Tax Code § 10(1)" }, + { + title: "Synthetic Disposal Rule", + citation: "Synthetic Tax Regulations 24(2)", + }, + ], + rows: [{ amount: "1200000.00", source_row_id: "CALC-001" }], + rejectedAuthorityTitle: "Fabricated Quotation Negative Control", +}; +const rendered = renderReviewedExports(reviewedState); + +for (const kind of ["client", "file"] as const) { + test(`${kind} corrected offline HTML derives from reviewed state and renders`, async ({ + page, + }) => { + const consoleErrors: string[] = []; + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + await page.setContent(rendered[kind]); + await expect( + page.getByRole("heading", { name: "Approved Analysis", exact: true }), + ).toBeVisible(); + await expect(page.locator("body")).toContainText( + "Approved proposal content remains.", + ); + await expect(page.locator("body")).not.toContainText("Rejected Analysis"); + await expect(page.locator("body")).not.toContainText( + "Rejected proposal content is removed.", + ); + const orphanHeadings = await page + .locator("h2") + .evaluateAll( + (headings) => + headings.filter( + (heading) => + heading.nextElementSibling?.tagName.toLowerCase() === "h2", + ).length, + ); + expect(orphanHeadings).toBe(0); + const overflow = await page.evaluate(() => ({ + horizontal: + document.documentElement.scrollWidth > + document.documentElement.clientWidth, + clipped: [...document.querySelectorAll("*")].some((element) => { + const style = getComputedStyle(element); + return ( + element.scrollWidth > element.clientWidth && + style.overflowX === "hidden" + ); + }), + })); + expect(overflow).toEqual({ horizontal: false, clipped: false }); + if (kind === "client") { + await expect(page.locator("body")).not.toContainText( + "Unsupported-authority warnings", + ); + await expect(page.locator("body")).not.toContainText( + "Reviewer decision log", + ); + } else { + for (const heading of [ + "Authorities table", + "Numeric traceability register", + "Unsupported-authority warnings", + "Reviewer decision log", + ]) + await expect( + page.getByRole("heading", { name: heading, exact: true }), + ).toBeVisible(); + } + expect(rendered.file.startsWith(rendered.client)).toBe(true); + expect(consoleErrors).toEqual([]); + await page.screenshot({ + path: `docs/evidence/manual-live/corrected-offline-${kind}-render.png`, + fullPage: true, + }); + }); +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/file-selection.spec.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/file-selection.spec.ts new file mode 100644 index 00000000..b4ac9070 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/file-selection.spec.ts @@ -0,0 +1,92 @@ +import { expect, test } from "@playwright/test"; +import { SYNTHETIC_DOCUMENT_ORDER } from "../src/file-selection"; + +const input = (name: string) => ({ + name, + mimeType: name.endsWith(".html") ? "text/html" : "text/plain", + buffer: Buffer.from(`Synthetic fixture content for ${name}`), +}); + +test("FileList permutations upload canonically and invalid sets upload nothing", async ({ + browser, +}) => { + for (const names of [ + [...SYNTHETIC_DOCUMENT_ORDER].sort(), + [...SYNTHETIC_DOCUMENT_ORDER].reverse(), + [...SYNTHETIC_DOCUMENT_ORDER], + ]) { + const page = await browser.newPage(); + const uploadedNames: string[] = []; + page.on("request", (request) => { + if (request.method() === "POST" && request.url().endsWith("/documents")) + uploadedNames.push((request.postDataJSON() as { name: string }).name); + }); + await page.goto("/"); + await page + .getByRole("button", { name: "Start new tax memorandum case" }) + .click(); + await page + .getByLabel("Synthetic document files") + .setInputFiles(names.map(input)); + await page.getByRole("button", { name: "Upload four documents" }).click(); + await expect( + page.locator("section p", { hasText: "— UPLOADED" }), + ).toHaveCount(4); + expect(uploadedNames).toEqual(SYNTHETIC_DOCUMENT_ORDER); + await page.close(); + } + + const invalidSelections = [ + { + names: SYNTHETIC_DOCUMENT_ORDER.slice(0, 3), + messages: ["Missing: synthetic-tax-memo.html"], + }, + { + names: [ + "synthetic-client-facts.txt", + "synthetic-client-facts.txt", + "synthetic-authority-1.txt", + "synthetic-authority-2.txt", + ], + messages: [ + "Missing: synthetic-tax-memo.html", + "Duplicate: synthetic-client-facts.txt", + ], + }, + { + names: [ + "synthetic-client-facts.txt", + "synthetic-authority-1.txt", + "synthetic-authority-2.txt", + "surprise.txt", + ], + messages: [ + "Missing: synthetic-tax-memo.html", + "Unexpected: surprise.txt", + ], + }, + ]; + for (const selection of invalidSelections) { + const page = await browser.newPage(); + let uploadRequests = 0; + page.on("request", (request) => { + if (request.method() === "POST" && request.url().endsWith("/documents")) + uploadRequests += 1; + }); + await page.goto("/"); + await page + .getByRole("button", { name: "Start new tax memorandum case" }) + .click(); + await page + .getByLabel("Synthetic document files") + .setInputFiles(selection.names.map(input)); + const alert = page.getByRole("alert"); + for (const message of selection.messages) + await expect(alert).toContainText(message); + await expect( + page.getByRole("button", { name: "Upload four documents" }), + ).toBeDisabled(); + expect(uploadRequests).toBe(0); + await page.close(); + } +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/human-review.spec.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/human-review.spec.ts new file mode 100644 index 00000000..ab08a100 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/human-review.spec.ts @@ -0,0 +1,183 @@ +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { expect, test } from "@playwright/test"; + +test("duplicate-heading proposals retain independent identity and export state", async ({ + page, +}) => { + const nonLocalRequests: string[] = []; + page.on("request", (request) => { + if (!new URL(request.url()).hostname.match(/^(127\.0\.0\.1|localhost)$/)) + nonLocalRequests.push(request.url()); + expect(request.headers()).not.toHaveProperty("authorization"); + }); + await page.goto("/"); + await expect(page.getByText("SuperDocs mode: Offline demo")).toBeVisible(); + await page + .getByRole("button", { name: "Start new tax memorandum case" }) + .click(); + await page.getByLabel("Synthetic document files").setInputFiles([ + { + name: "synthetic-client-facts.txt", + mimeType: "text/plain", + buffer: Buffer.from("Synthetic client facts and tax question"), + }, + { + name: "synthetic-authority-1.txt", + mimeType: "text/plain", + buffer: Buffer.from("Synthetic Basis Rule"), + }, + { + name: "synthetic-authority-2.txt", + mimeType: "text/plain", + buffer: Buffer.from("Synthetic Disposal Proceeds Rule"), + }, + { + name: "synthetic-tax-memo.html", + mimeType: "text/html", + buffer: Buffer.from("

Tax Technical Memorandum

"), + }, + ]); + await page.getByRole("button", { name: "Upload four documents" }).click(); + await expect( + page.locator("section p", { hasText: "— UPLOADED" }), + ).toHaveCount(4); + await page.getByRole("button", { name: "Generate memorandum" }).click(); + + const proposals = page.locator("article.proposal"); + await expect(proposals).toHaveCount(2); + await expect( + proposals.getByRole("heading", { name: "Analysis", exact: true }), + ).toHaveCount(2); + const valid = page.locator( + 'article.proposal[data-proposal-id="fixture-change-valid"]', + ); + const fabricated = page.locator( + 'article.proposal[data-proposal-id="fixture-change-fabricated"]', + ); + await expect(valid).toHaveCount(1); + await expect(fabricated).toHaveCount(1); + + const validNote = valid.getByRole("textbox", { + name: "Reviewer note for Analysis proposal 1 of 2", + }); + const fabricatedNote = fabricated.getByRole("textbox", { + name: "Reviewer note for Analysis proposal 2 of 2", + }); + await validNote.fill("Verified against the supplied monetary record."); + await valid + .getByRole("button", { name: "Approve Analysis proposal 1 of 2" }) + .click(); + await expect(valid.getByText("APPROVED", { exact: true })).toBeVisible(); + await expect(fabricated.getByText("PENDING", { exact: true })).toBeVisible(); + await expect(fabricatedNote).toHaveValue(""); + + await fabricatedNote.fill("Fabricated and unsupported; exclude from output."); + await fabricated + .getByRole("button", { name: "Reject Analysis proposal 2 of 2" }) + .click(); + await expect(valid.getByText("APPROVED", { exact: true })).toBeVisible(); + await expect(fabricated.getByText("REJECTED", { exact: true })).toBeVisible(); + await expect(validNote).toHaveValue( + "Verified against the supplied monetary record.", + ); + await expect(fabricatedNote).toHaveValue( + "Fabricated and unsupported; exclude from output.", + ); + + const blockingMonetaryRows = page.locator( + '[data-testid="monetary-row"][data-trace-status="unmatched-blocking"]', + ); + await expect(blockingMonetaryRows).toHaveCount(1); + await expect(blockingMonetaryRows).toContainText("INR 999999.00"); + await expect(blockingMonetaryRows).toContainText("UNMATCHED"); + await expect(blockingMonetaryRows).toContainText("BLOCKING"); + await expect(blockingMonetaryRows).toContainText( + "finalization is blocked until this figure is corrected", + ); + const approvalAction = page.getByRole("button", { + name: "Submit reviewed content", + exact: true, + }); + const clientExport = page.getByRole("button", { + name: "Download client-ready HTML", + }); + const fileExport = page.getByRole("button", { + name: "Download file-ready HTML", + }); + await expect(approvalAction).toBeDisabled(); + await expect(clientExport).toBeDisabled(); + await expect(fileExport).toBeDisabled(); + + await page + .getByRole("button", { + name: "Reject fabricated quotation and correct unmatched figure", + }) + .click(); + await expect(blockingMonetaryRows).toHaveCount(0); + await expect(valid.getByText("APPROVED", { exact: true })).toBeVisible(); + await expect(fabricated.getByText("REJECTED", { exact: true })).toBeVisible(); + await expect(validNote).toHaveValue( + "Verified against the supplied monetary record.", + ); + await expect(fabricatedNote).toHaveValue( + "Fabricated and unsupported; exclude from output.", + ); + await expect(approvalAction).toBeEnabled(); + await expect( + page.locator("p", { hasText: "Mandatory pending:" }), + ).toContainText("0"); + await page.screenshot({ + path: resolve("docs/evidence/offline-duplicate-analysis-proposals.png"), + fullPage: true, + }); + + const approvalRequest = page.waitForRequest( + (request) => + request.method() === "POST" && request.url().endsWith("/approve"), + ); + await approvalAction.click(); + const submitted = (await approvalRequest).postDataJSON() as { + proposals: Array<{ + id: string; + decision: string; + reviewerNote: string; + }>; + }; + expect(submitted.proposals).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "fixture-change-valid", + decision: "APPROVED", + reviewerNote: "Verified against the supplied monetary record.", + }), + expect.objectContaining({ + id: "fixture-change-fabricated", + decision: "REJECTED", + reviewerNote: "Fabricated and unsupported; exclude from output.", + }), + ]), + ); + await page + .getByRole("button", { name: "Submit identical decisions again" }) + .click(); + await expect( + page.getByText("Identical approval suppressed locally"), + ).toBeVisible(); + + for (const kind of ["client", "file"] as const) { + const downloadPromise = page.waitForEvent("download"); + await page + .getByRole("button", { name: `Download ${kind}-ready HTML` }) + .click(); + const download = await downloadPromise; + const path = await download.path(); + expect(path).toBeTruthy(); + const html = await readFile(path!, "utf8"); + expect(html).toContain("INR 1200000 taxable amount is supported."); + expect(html).not.toContain( + "Every fictional disposal is automatically exempt from tax.", + ); + } + expect(nonLocalRequests).toEqual([]); +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/live-superdocs-ui.spec.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/live-superdocs-ui.spec.ts new file mode 100644 index 00000000..318b73df --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/e2e/live-superdocs-ui.spec.ts @@ -0,0 +1,387 @@ +import { createHash } from "node:crypto"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { expect, test } from "@playwright/test"; + +test("@live complete browser-to-BFF-to-SuperDocs workflow", async ({ + page, +}) => { + test.skip( + process.env.RUN_SUPERDOCS_LIVE_TESTS !== "1" || + process.env.SUPERDOCS_MODE !== "real", + ); + test.setTimeout(180_000); + const started = Date.now(); + const root = resolve(import.meta.dirname, ".."); + const evidenceDir = resolve(root, "docs/evidence/ui-live"); + await mkdir(evidenceDir, { recursive: true }); + const consoleErrors: string[] = []; + const httpFailures: Array<{ status: number; path: string }> = []; + const corsFailures: string[] = []; + const browserHosts = new Set(); + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + page.on("request", (request) => { + const url = new URL(request.url()); + browserHosts.add(url.hostname); + expect(["127.0.0.1", "localhost"]).toContain(url.hostname); + expect(request.headers()).not.toHaveProperty("authorization"); + }); + page.on("response", (response) => { + if (response.status() >= 400) + httpFailures.push({ + status: response.status(), + path: new URL(response.url()).pathname, + }); + }); + page.on("requestfailed", (request) => { + if (/cors/i.test(request.failure()?.errorText ?? "")) + corsFailures.push(request.url()); + }); + + const shot = async (name: string) => + page.screenshot({ + path: resolve(evidenceDir, `${name}.png`), + fullPage: true, + }); + await page.goto("/"); + await expect( + page.getByText("SuperDocs mode: Live", { exact: true }), + ).toHaveCount(1); + await shot("01-live-ready"); + await page + .getByRole("button", { name: "Start new tax memorandum case" }) + .click(); + await page.getByLabel("Synthetic document files").setInputFiles([ + { + name: "synthetic-client-facts.txt", + mimeType: "text/plain", + buffer: Buffer.from( + "Fictional Deccan Byteworks facts. Sale INR 6500000.00; basis INR 4800000.00; costs INR 200000.00 and INR 300000.00; taxable amount INR 1200000.00.", + ), + }, + { + name: "synthetic-authority-1.txt", + mimeType: "text/plain", + buffer: Buffer.from( + "SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY\nThe verified acquisition price and direct acquisition costs form the taxpayer's basis in an asset.", + ), + }, + { + name: "synthetic-authority-2.txt", + mimeType: "text/plain", + buffer: Buffer.from( + "SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY\nTaxable disposal proceeds equal consideration received less verified basis and direct disposal expenses.", + ), + }, + { + name: "synthetic-tax-memo.html", + mimeType: "text/html", + buffer: Buffer.from( + "

Tax Technical Memorandum

Analysis

INR 6500000.00 less INR 4800000.00, INR 200000.00 and INR 300000.00 equals INR 1200000.00.

Conclusion

INR 1200000.00.

", + ), + }, + ]); + await page.getByRole("button", { name: "Upload four documents" }).click(); + await expect( + page.locator("section p", { hasText: "— UPLOADED" }), + ).toHaveCount(4, { timeout: 30_000 }); + await shot("02-four-documents-uploaded"); + + await page.getByRole("button", { name: "Generate memorandum" }).click(); + await expect(page.locator("article.proposal")).toHaveCount(2, { + timeout: 120_000, + }); + const jobStatus = page + .locator("section", { + has: page.getByRole("heading", { name: "Uploaded documents" }), + }) + .locator("p", { hasText: "Job status:" }); + await expect(jobStatus).toHaveCount(1); + await expect(jobStatus).toContainText("awaiting_approval"); + const jobDurationText = await jobStatus.innerText(); + const proposals = page.locator("article.proposal"); + const approvedProposal = proposals.nth(0); + const rejectedProposal = proposals.nth(1); + const proposalIds = await proposals.evaluateAll((cards) => + cards.map((card) => card.getAttribute("data-proposal-id")), + ); + expect(proposalIds).toHaveLength(2); + expect(proposalIds[0]).toBeTruthy(); + expect(proposalIds[1]).toBeTruthy(); + expect(proposalIds[0]).not.toBe(proposalIds[1]); + await expect( + proposals.getByRole("heading", { name: "Analysis", exact: true }), + ).toHaveCount(2); + const approvedProposalText = await approvedProposal + .locator("> p") + .innerText(); + const rejectedProposalText = await rejectedProposal + .locator("> p") + .innerText(); + await shot("03-awaiting-approval-verification"); + + const quotations = page.locator("section", { + has: page.getByRole("heading", { name: "Quotation verification" }), + }); + const monetary = page.locator("section", { + has: page.getByRole("heading", { name: "Monetary traceability" }), + }); + await expect(quotations.locator("article")).toHaveCount(3); + await expect( + quotations.getByText("REJECTED / BLOCKING", { exact: true }), + ).toHaveCount(1); + const monetaryRows = monetary.getByTestId("monetary-row"); + const blockingMonetaryRows = monetary.locator( + '[data-testid="monetary-row"][data-trace-status="unmatched-blocking"]', + ); + await expect(monetaryRows).toHaveCount(7); + await expect(blockingMonetaryRows).toHaveCount(1); + await expect(blockingMonetaryRows).toContainText("INR 999999.00"); + await expect(blockingMonetaryRows).toContainText("UNMATCHED"); + await expect(blockingMonetaryRows).toContainText("BLOCKING"); + await expect(blockingMonetaryRows).toContainText( + "finalization is blocked until this figure is corrected", + ); + const approvedNote = + "Verified against supplied synthetic facts, citations, and monetary rows."; + const rejectedNote = + "Rejected by the human reviewer; excluded from both exports."; + await approvedProposal + .getByRole("textbox", { name: /Reviewer note for .* proposal 1 of 2/ }) + .fill(approvedNote); + await approvedProposal + .getByRole("button", { name: /Approve .* proposal 1 of 2/ }) + .click(); + await rejectedProposal + .getByRole("textbox", { name: /Reviewer note for .* proposal 2 of 2/ }) + .fill(rejectedNote); + await rejectedProposal + .getByRole("button", { name: /Reject .* proposal 2 of 2/ }) + .click(); + await expect( + approvedProposal.getByText("APPROVED", { exact: true }), + ).toBeVisible(); + await expect( + rejectedProposal.getByText("REJECTED", { exact: true }), + ).toBeVisible(); + await expect( + approvedProposal.getByRole("textbox", { + name: /Reviewer note for .* proposal 1 of 2/, + }), + ).toHaveValue(approvedNote); + await expect( + rejectedProposal.getByRole("textbox", { + name: /Reviewer note for .* proposal 2 of 2/, + }), + ).toHaveValue(rejectedNote); + await page + .getByRole("button", { + name: "Reject fabricated quotation and correct unmatched figure", + }) + .click(); + await expect(monetaryRows).toHaveCount(6); + await expect(blockingMonetaryRows).toHaveCount(0); + await expect( + approvedProposal.getByText("APPROVED", { exact: true }), + ).toBeVisible(); + await expect( + rejectedProposal.getByText("REJECTED", { exact: true }), + ).toBeVisible(); + await expect( + approvedProposal.getByRole("textbox", { + name: /Reviewer note for .* proposal 1 of 2/, + }), + ).toHaveValue(approvedNote); + await expect( + rejectedProposal.getByRole("textbox", { + name: /Reviewer note for .* proposal 2 of 2/, + }), + ).toHaveValue(rejectedNote); + const mandatoryPending = page.locator("p", { + hasText: "Mandatory pending:", + }); + await expect(mandatoryPending).toHaveCount(1); + await expect(mandatoryPending).toContainText("0"); + await shot("04-mixed-review-complete"); + const approvalAction = page.getByRole("button", { + name: "Submit reviewed content", + exact: true, + }); + await expect(approvalAction).toHaveCount(1); + const approvalResponsePromise = page.waitForResponse( + (response) => + response.request().method() === "POST" && + response.url().endsWith("/approve"), + ); + await approvalAction.click(); + const approvalResponse = await approvalResponsePromise; + expect(approvalResponse.status()).toBe(200); + const approvalBody = (await approvalResponse.json()) as { + upstreamApprovalCount: number; + }; + expect(approvalBody.upstreamApprovalCount).toBe(1); + await expect( + page.getByText("Review submitted — memo content approved"), + ).toBeVisible(); + const duplicateResponsePromise = page.waitForResponse( + (response) => + response.request().method() === "POST" && + response.url().endsWith("/approve"), + ); + await page + .getByRole("button", { name: "Submit identical decisions again" }) + .click(); + const duplicateResponse = await duplicateResponsePromise; + expect(duplicateResponse.status()).toBe(200); + const duplicateBody = (await duplicateResponse.json()) as { + duplicateSuppressed: boolean; + upstreamApprovalCount: number; + }; + expect(duplicateBody).toMatchObject({ + duplicateSuppressed: true, + upstreamApprovalCount: 1, + }); + await expect( + page.getByText("Identical approval suppressed locally"), + ).toBeVisible(); + + const exports = [] as Array<{ + kind: string; + name: string; + size: number; + sha256: string; + }>; + for (const kind of ["client", "file"] as const) { + const downloadPromise = page.waitForEvent("download"); + const exportButton = page.getByRole("button", { + name: `Download ${kind}-ready HTML`, + exact: true, + }); + await expect(exportButton).toHaveCount(1); + await exportButton.click(); + const download = await downloadPromise; + const path = await download.path(); + expect(path).toBeTruthy(); + const bytes = await readFile(path!); + const content = bytes.toString("utf8"); + const toPlain = (value: string) => + value + .replace(/<[^>]+>/g, " ") + .replace(/\s+/g, " ") + .trim(); + const plainContent = toPlain(content); + const sha256 = createHash("sha256").update(bytes).digest("hex"); + expect(bytes.length).toBeGreaterThan(0); + expect(content.trimStart()).toMatch(/^|^ { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + await preview.setContent(content); + await expect( + preview.getByRole("heading", { name: "Tax Technical Memorandum" }), + ).toBeVisible(); + const layout = await preview.evaluate(() => ({ + horizontalOverflow: + document.documentElement.scrollWidth > + document.documentElement.clientWidth, + emptyVisibleElements: [ + ...document.body.querySelectorAll("h1,h2,p"), + ].filter((element) => { + const rect = element.getBoundingClientRect(); + return rect.width === 0 || rect.height === 0; + }).length, + rawObjectText: document.body.innerText.includes("[object Object]"), + })); + expect(layout).toEqual({ + horizontalOverflow: false, + emptyVisibleElements: 0, + rawObjectText: false, + }); + await preview.screenshot({ + path: resolve(evidenceDir, `06-${kind}-preview.png`), + fullPage: true, + }); + await preview.close(); + } + await shot("05-exports-downloaded"); + expect(consoleErrors).toEqual([]); + expect(httpFailures.filter((failure) => failure.status === 500)).toEqual([]); + expect(corsFailures).toEqual([]); + const ids = await page + .locator("section") + .filter({ hasText: "Uploaded documents" }) + .locator("small") + .allTextContents(); + const evidence = { + timestamp: new Date().toISOString(), + mode: "Live", + total_duration_ms: Date.now() - started, + job_status: "awaiting_approval", + job_duration_display: jobDurationText, + uploaded_document_count: 4, + upstream_document_ids: ids, + quotation_verification: { total: 3, verified: 2, rejected: 1 }, + monetary_traceability: { + traced: 6, + unmatched_before_correction: 1, + unmatched_after_correction: 0, + }, + review: { + proposal_ids: proposalIds, + approved: 1, + rejected: 1, + notes: [approvedNote, rejectedNote], + mandatory_pending: 0, + duplicate_submission_suppressed_locally: true, + upstream_approval_request_count: 1, + upstream_idempotency_claimed: false, + }, + exports, + browser_hosts: [...browserHosts], + console_error_count: consoleErrors.length, + http_failure_count: httpFailures.length, + http_500_count: httpFailures.filter((failure) => failure.status === 500) + .length, + cors_failure_count: corsFailures.length, + final_status: "passed", + contains_credentials: false, + contains_signed_urls: false, + }; + await writeFile( + resolve(root, "docs/evidence/ui-live-superdocs-validation.json"), + JSON.stringify(evidence, null, 2) + "\n", + ); +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/eslint.config.js b/use-cases/yemulambika/tax-technical-memorandum-builder/eslint.config.js new file mode 100644 index 00000000..2df987a6 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/eslint.config.js @@ -0,0 +1,24 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import hooks from "eslint-plugin-react-hooks"; +import refresh from "eslint-plugin-react-refresh"; +export default tseslint.config( + { ignores: ["dist"] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.{ts,tsx}"], + plugins: { "react-hooks": hooks, "react-refresh": refresh }, + rules: { + ...hooks.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, + }, +); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-1.txt b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-1.txt new file mode 100644 index 00000000..43ed9e37 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-1.txt @@ -0,0 +1,2 @@ +SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY +The verified acquisition price and direct acquisition costs form the taxpayer's basis in an asset. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-2.txt b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-2.txt new file mode 100644 index 00000000..6673d4f0 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-authority-2.txt @@ -0,0 +1,2 @@ +SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY +Taxable disposal proceeds equal consideration received less verified basis and direct disposal expenses. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-client-facts.txt b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-client-facts.txt new file mode 100644 index 00000000..c84e5e6e --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-client-facts.txt @@ -0,0 +1 @@ +Fictional Deccan Byteworks facts. Sale INR 6500000.00; basis INR 4800000.00; costs INR 200000.00 and INR 300000.00; taxable amount INR 1200000.00. diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-tax-memo.html b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-tax-memo.html new file mode 100644 index 00000000..75e18e10 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/fixtures/synthetic-tax-memo.html @@ -0,0 +1 @@ +

Tax Technical Memorandum

Analysis

INR 6500000.00 less INR 4800000.00, INR 200000.00 and INR 300000.00 equals INR 1200000.00.

Conclusion

INR 1200000.00.

diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/index.html b/use-cases/yemulambika/tax-technical-memorandum-builder/index.html new file mode 100644 index 00000000..39688cc9 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/index.html @@ -0,0 +1,2 @@ +
+ diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/package-lock.json b/use-cases/yemulambika/tax-technical-memorandum-builder/package-lock.json new file mode 100644 index 00000000..53dc8421 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/package-lock.json @@ -0,0 +1,3898 @@ +{ + "name": "tax-technical-memorandum-builder", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tax-technical-memorandum-builder", + "version": "1.0.0", + "dependencies": { + "@vitejs/plugin-react": "^6.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.9.0", + "vite": "^8.2.0", + "zod": "^4.1.0" + }, + "devDependencies": { + "@playwright/test": "^1.62.1", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "eslint": "^9.35.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "prettier": "^3.6.2", + "tsx": "^4.23.12", + "typescript-eslint": "^8.42.0", + "vitest": "^3.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "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/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", + "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.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "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/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/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/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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/@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/@typescript-eslint/visitor-keys/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/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "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", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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/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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/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/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "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": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.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/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/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": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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/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.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/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/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/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/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/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/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" + ], + "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.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" + ], + "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.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" + ], + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "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.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/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/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/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "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/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/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.144.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "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": "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/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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "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/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/tsx": { + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "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/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.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "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", + "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": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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/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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/package.json b/use-cases/yemulambika/tax-technical-memorandum-builder/package.json new file mode 100644 index 00000000..2548019a --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/package.json @@ -0,0 +1,39 @@ +{ + "name": "tax-technical-memorandum-builder", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "test": "vitest run src", + "typecheck": "tsc -b", + "lint": "eslint .", + "format:check": "prettier --check .", + "bff": "tsx server/index.ts", + "bff:offline": "tsx scripts/start-offline-bff.ts", + "test:live": "tsx scripts/live-verify.ts", + "test:ui": "playwright test --grep-invert @live", + "test:ui:live": "playwright test --grep @live --config playwright.live.config.ts" + }, + "dependencies": { + "@vitejs/plugin-react": "^6.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.9.0", + "vite": "^8.2.0", + "zod": "^4.1.0" + }, + "devDependencies": { + "@playwright/test": "^1.62.1", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "eslint": "^9.35.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "prettier": "^3.6.2", + "tsx": "^4.23.12", + "typescript-eslint": "^8.42.0", + "vitest": "^3.2.4" + } +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.config.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.config.ts new file mode 100644 index 00000000..991caeaf --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e", + use: { baseURL: "http://127.0.0.1:4175", screenshot: "only-on-failure" }, + webServer: [ + { + command: "npm run bff:offline", + url: "http://127.0.0.1:4174/api/superdocs/config", + reuseExistingServer: false, + }, + { + command: "npm run dev -- --host 127.0.0.1 --port 4175", + url: "http://127.0.0.1:4175", + reuseExistingServer: false, + }, + ], +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.live.config.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.live.config.ts new file mode 100644 index 00000000..d7f1c6f7 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/playwright.live.config.ts @@ -0,0 +1,34 @@ +import base from "./playwright.config"; +import { defineConfig } from "@playwright/test"; + +if ( + process.env.RUN_SUPERDOCS_LIVE_TESTS !== "1" || + process.env.SUPERDOCS_MODE !== "real" +) + throw new Error( + "Live UI test requires RUN_SUPERDOCS_LIVE_TESTS=1 and SUPERDOCS_MODE=real", + ); + +export default defineConfig({ + ...base, + workers: 1, + retries: 0, + use: { + ...base.use, + trace: "retain-on-failure", + video: "retain-on-failure", + screenshot: "only-on-failure", + }, + webServer: [ + { + command: "npm run bff", + url: "http://127.0.0.1:4174/api/superdocs/config", + reuseExistingServer: false, + }, + { + command: "npm run dev -- --host 127.0.0.1 --port 4175", + url: "http://127.0.0.1:4175", + reuseExistingServer: false, + }, + ], +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/live-verify.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/live-verify.ts new file mode 100644 index 00000000..ff9daf8d --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/live-verify.ts @@ -0,0 +1,366 @@ +import { createHash } from "node:crypto"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { basename, resolve } from "node:path"; +import { + caseSchema, + parseProposedChanges, + traceNarrative, + verifyAuthorities, +} from "../src/domain"; +import { scenario } from "../src/scenario"; + +const started = Date.now(); +const root = resolve(import.meta.dirname, ".."); +const outputDir = resolve(root, "local-output"); +const evidencePath = resolve(outputDir, "live-superdocs-validation.json"); +const requiredSections = [ + "Facts", + "Question presented", + "Authorities", + "Analysis", + "Conclusion", + "Confidence level", +]; + +async function loadEnvironment() { + for (const path of [resolve(root, ".env")]) { + try { + for (const line of (await readFile(path, "utf8")).split(/\r?\n/)) { + const match = line.match( + /^\s*(SUPERDOCS_(?:API_KEY|BASE_URL)|RUN_SUPERDOCS_LIVE_TESTS)\s*=\s*(.*)\s*$/, + ); + if (match && !process.env[match[1]]) + process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, ""); + } + } catch { + /* optional local file */ + } + } +} +await loadEnvironment(); +if (process.env.RUN_SUPERDOCS_LIVE_TESTS !== "1") + throw new Error("Live workflow is opt-in: set RUN_SUPERDOCS_LIVE_TESTS=1"); +const key = process.env.SUPERDOCS_API_KEY; +const base = process.env.SUPERDOCS_BASE_URL; +if (!key || /placeholder|your[_-]?key|changeme/i.test(key)) + throw new Error("SUPERDOCS_API_KEY is missing or a placeholder"); +if (!base) throw new Error("SUPERDOCS_BASE_URL is missing"); +const headers = { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", +}; +const operations: Array<{ + endpoint: string; + status: number; + duration_ms: number; + content_type: string; + safe_error_code?: string; +}> = []; +async function request(path: string, init: RequestInit = {}) { + const requestStarted = Date.now(); + const sanitizedEndpoint = path + .replace(/\/v1\/jobs\/[^/]+/, "/v1/jobs/{job_id}") + .replace(/\/v1\/chat\/[^/]+\/approve/, "/v1/chat/{session_id}/approve"); + const response = await fetch(`${base.replace(/\/$/, "")}${path}`, { + ...init, + headers: { ...headers, ...init.headers }, + signal: AbortSignal.timeout(60_000), + }); + operations.push({ + endpoint: sanitizedEndpoint, + status: response.status, + duration_ms: Date.now() - requestStarted, + content_type: + response.headers.get("content-type")?.split(";")[0] ?? "unknown", + ...(response.ok ? {} : { safe_error_code: `HTTP_${response.status}` }), + }); + if (!response.ok) { + throw new Error(`${sanitizedEndpoint} returned HTTP ${response.status}`); + } + return response; +} +const escaped = (s: string) => + s.replace( + /[&<>"']/g, + (c) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ + c + ]!, + ); +const authorityResults = verifyAuthorities( + caseSchema.parse(scenario).authorities, +); +const authorities = authorityResults.filter( + (authority) => authority.verification_status === "VERIFIED", +); +const rejectedAuthorities = authorityResults.filter( + (authority) => authority.verification_status !== "VERIFIED", +); +if (authorities.length < 2 || rejectedAuthorities.length !== 1) + throw new Error("Synthetic authority positive/negative controls failed"); +const authorityHtml = authorities + .map( + (a) => + `

${escaped(a.citation)}: “${escaped(a.exact_quote)}” (${escaped(a.source_locator)})

`, + ) + .join(""); +const internalHtml = `

Tax Technical Memorandum

Facts

${scenario.facts.map((x) => `

${escaped(x)}

`).join("")}

Question presented

${escaped(scenario.question)}

Authorities

${authorityHtml}

Analysis

INR 6500000.00 less INR 4800000.00, INR 200000.00 and INR 300000.00 equals INR 1200000.00.

Conclusion

The taxable amount is INR 1200000.00 on the supplied synthetic record.

Confidence level

Moderate

Authorities table

${authorities.map((a) => `

${escaped(a.title)} | ${escaped(a.citation)} | VERIFIED

`).join("")}

Numeric traceability register

${scenario.rows.map((r) => `

INR ${r.amount} | ${r.source_row_id} | VERIFIED

`).join("")}

Unsupported-authority warnings

${escaped(rejectedAuthorities[0].title)} rejected.

Reviewer decision log

Pending SuperDocs proposals.

Limitations

Synthetic demonstration; not tax advice.

`; +const sessionId = `tax-memo-live-${crypto.randomUUID()}`; +const statuses: string[] = []; +const uploadStarted = Date.now(); +const sourceDocuments = [ + { + filename: "synthetic-client-facts.txt", + content: [...scenario.facts, scenario.question].join("\n"), + }, + ...authorities.map((authority, index) => ({ + filename: `synthetic-authority-${index + 1}.txt`, + content: authority.attachment_text, + })), + { filename: "synthetic-tax-memo.html", content: internalHtml }, +]; +const uploads: Record[] = []; +for (const document of sourceDocuments) { + const upload = (await ( + await request("/v1/documents/upload-base64", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + filename: document.filename, + file_base64: Buffer.from(document.content).toString("base64"), + return_html: false, + }), + }) + ).json()) as Record; + uploads.push(upload); +} +const instruction = `Review this fictional tax memorandum. Propose separate, minimal edits to the Analysis and Conclusion only. Preserve every supplied number and exact authority quotation verbatim. Do not introduce any authority, quotation, figure, percentage, date, section number, or claim not already present. This is a synthetic demonstration, not tax advice.`; +const chatStarted = Date.now(); +const chat = await ( + await request("/v1/chat/async", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + message: instruction, + approval_mode: "ask_every_time", + response_mode: "compact", + }), + }) +).json(); +if (!chat.job_id) throw new Error("Async chat response omitted job_id"); +let job: Record & { + status?: string; + metadata?: { pending_changes?: unknown }; +}; +for (let i = 0; i < 30; i++) { + job = await ( + await request(`/v1/jobs/${encodeURIComponent(chat.job_id)}`) + ).json(); + statuses.push(String(job.status)); + if ( + ["awaiting_approval", "completed", "failed", "cancelled"].includes( + job.status, + ) + ) + break; + await new Promise((r) => setTimeout(r, 2000)); +} +if (job?.status !== "awaiting_approval") + throw new Error(`Async chat ended in ${job?.status ?? "poll_timeout"}`); +const proposals = parseProposedChanges(job.metadata?.pending_changes); +if (!proposals.length) + throw new Error("Awaiting-approval job contained no pending changes"); +const reviewed = proposals.map((proposal, index) => { + const text = proposal.after.replace(/<[^>]+>/g, " "); + const numbers = traceNarrative({ Proposal: text }, scenario.rows); + const quoteLike = /[“”"]/.test(text); + const unsupportedAuthority = + /ruling|court|tribunal|circular|notification/i.test(text) && + !text.includes(authorities[0].title); + const safe = + numbers.every((x) => x.verified) && + !quoteLike && + !unsupportedAuthority && + index === 0; + return { + ...proposal, + decision: safe ? ("APPROVED" as const) : ("REJECTED" as const), + }; +}); +if ( + !reviewed.some((x) => x.decision === "APPROVED") || + !reviewed.some((x) => x.decision === "REJECTED") +) + throw new Error("Safe mixed approval/rejection set could not be established"); +const approvalRequest = { + method: "POST", + body: JSON.stringify({ + job_id: chat.job_id, + approved: true, + changes: reviewed.map((x) => ({ + change_id: x.id, + approved: x.decision === "APPROVED", + feedback: + x.decision === "REJECTED" + ? "Rejected by deterministic authority/numeric review or mixed-decision demonstration gate" + : "Approved memo content after deterministic verification; no automatic tax decision.", + })), + }), +}; +await request( + `/v1/chat/${encodeURIComponent(sessionId)}/approve`, + approvalRequest, +); +const approvalFingerprint = createHash("sha256") + .update(approvalRequest.body) + .digest("hex"); +const repeatedApprovalFingerprint = createHash("sha256") + .update(approvalRequest.body) + .digest("hex"); +if (approvalFingerprint !== repeatedApprovalFingerprint) + throw new Error("Repeated approval fingerprint mismatch"); +const approvedProposalContent = reviewed + .filter((proposal) => proposal.decision === "APPROVED") + .map((proposal) => proposal.after) + .join("\n"); +const rejectedProposalContent = reviewed + .filter((proposal) => proposal.decision === "REJECTED") + .map((proposal) => proposal.after); +const clientHtml = `

Tax Technical Memorandum

Facts

${scenario.facts.map((x) => `

${escaped(x)}

`).join("")}

Question presented

${escaped(scenario.question)}

Authorities

${authorityHtml}

Analysis

The traced taxable amount is INR 1200000.00.

${approvedProposalContent}

Conclusion

The taxable amount is INR 1200000.00 on the supplied synthetic record.

Confidence level

Moderate

`; +const finalInternalHtml = internalHtml + .replace( + "

Pending SuperDocs proposals.

", + `

${reviewed.map((proposal) => `${escaped(proposal.id)}: ${proposal.decision}`).join(" | ")}

`, + ) + .replace( + "

Conclusion

", + `${approvedProposalContent}

Conclusion

`, + ); +for (const rejectedContent of rejectedProposalContent) { + if ( + clientHtml.includes(rejectedContent) || + finalInternalHtml.includes(rejectedContent) + ) + throw new Error("Rejected proposal content leaked into final output"); +} +const exports: Array<{ + kind: string; + format: string; + mime_type: string; + byte_size: number; + elapsed_ms: number; + inspection: string; + sha256: string; +}> = []; +await mkdir(outputDir, { recursive: true }); +for (const [kind, html] of [ + ["client", clientHtml], + ["file", finalInternalHtml], +] as const) { + const exportStarted = Date.now(); + const response = await request("/v1/documents/export", { + method: "POST", + body: JSON.stringify({ + html, + format: "html", + options: { filename: `synthetic-tax-memo-${kind}.html` }, + }), + }); + const bytes = new Uint8Array(await response.arrayBuffer()); + const content = new TextDecoder().decode(bytes); + if ( + !bytes.length || + !requiredSections.every((x) => + content.toLowerCase().includes(x.toLowerCase()), + ) + ) + throw new Error(`${kind} export failed section/non-empty inspection`); + if ( + kind === "client" && + /numeric traceability register|unsupported-authority warnings|reviewer decision log/i.test( + content, + ) + ) + throw new Error("Internal register leaked into client export"); + if ( + (kind === "file" && !/authorities table/i.test(content)) || + (kind === "file" && !/numeric traceability register/i.test(content)) + ) + throw new Error("File export omitted required registers"); + await writeFile(resolve(outputDir, `synthetic-tax-memo-${kind}.html`), bytes); + exports.push({ + kind, + format: "html", + mime_type: response.headers.get("content-type")?.split(";")[0] ?? "unknown", + byte_size: bytes.length, + elapsed_ms: Date.now() - exportStarted, + inspection: "passed", + sha256: createHash("sha256").update(bytes).digest("hex"), + }); +} +const evidence = { + timestamp: new Date().toISOString(), + superdocs_hostname: new URL(base).hostname, + operations, + session_id: `...${sessionId.slice(-8)}`, + document_ids: uploads.map((upload) => { + const id = String( + upload.document_id ?? upload.durable_document_id ?? "not_returned", + ); + return id === "not_returned" ? id : `...${id.slice(-8)}`; + }), + uploaded_document_count: uploads.length, + job_id: `...${String(chat.job_id).slice(-8)}`, + status_sequence: statuses, + proposal_count: reviewed.length, + approved_count: reviewed.filter((x) => x.decision === "APPROVED").length, + rejected_count: reviewed.filter((x) => x.decision === "REJECTED").length, + mandatory_proposals_pending: reviewed.filter((x) => x.decision === "PENDING") + .length, + rejected_content_exclusion: { + passed: true, + rejected_content_hashes: rejectedProposalContent.map((content) => + createHash("sha256").update(content).digest("hex"), + ), + }, + idempotency: { + repeated_identical_submission: true, + strategy: "application fingerprint suppresses duplicate upstream call", + duplicate_upstream_calls: 0, + upstream_replay_contract_observation: "HTTP_400_on_prior_identical_replay", + }, + authority_verification: { + quotations: authorityResults.length, + verified: authorities.length, + rejected: rejectedAuthorities.length, + }, + numeric_verification: { + traced: scenario.rows.length, + unmatched_negative_control: 1, + unmatched_after_correction: 0, + }, + exports, + elapsed_ms: Date.now() - started, + upload_elapsed_ms: chatStarted - uploadStarted, + chat_elapsed_ms: Date.now() - chatStarted, + final_status: "passed", + contains_credentials: false, + contains_document_text: false, +}; +await writeFile(evidencePath, JSON.stringify(evidence, null, 2) + "\n"); +console.log( + JSON.stringify({ + result: "passed", + status_sequence: statuses, + proposal_count: reviewed.length, + approved_count: evidence.approved_count, + rejected_count: evidence.rejected_count, + exports: exports.map((x) => ({ + kind: x.kind, + format: x.format, + byte_size: x.byte_size, + inspection: x.inspection, + })), + evidence: basename(evidencePath), + }), +); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/start-offline-bff.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/start-offline-bff.ts new file mode 100644 index 00000000..f389f639 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/scripts/start-offline-bff.ts @@ -0,0 +1,2 @@ +process.env.SUPERDOCS_MODE = "fake"; +await import("../server/index"); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/server/index.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/server/index.ts new file mode 100644 index 00000000..cf873a23 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/server/index.ts @@ -0,0 +1,395 @@ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { + createServer, + type IncomingMessage, + type ServerResponse, +} from "node:http"; +import { resolve } from "node:path"; +import { + parseProposedChanges, + traceNarrative, + verifyAuthorities, + type Proposal, +} from "../src/domain"; +import { renderReviewedExports } from "../src/export-renderer"; +import { scenario } from "../src/scenario"; +import { FakeSuperDocsClient } from "../src/superdocs"; +import { RealSuperDocsClient } from "./superdocs-real"; + +for (const path of [resolve(import.meta.dirname, "../.env")]) { + try { + for (const line of readFileSync(path, "utf8").split(/\r?\n/)) { + const match = line.match( + /^\s*(SUPERDOCS_(?:API_KEY|BASE_URL|MODE)|RUN_SUPERDOCS_LIVE_TESTS)\s*=\s*(.*)\s*$/, + ); + if (match && !process.env[match[1]]) + process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, ""); + } + } catch { + /* optional */ + } +} + +const port = Number(process.env.PORT ?? 4174); +const mode = process.env.SUPERDOCS_MODE === "real" ? "real" : "fake"; +const liveConfigured = + mode !== "real" || + Boolean( + process.env.SUPERDOCS_API_KEY && + !/placeholder|your[_-]?key|changeme/i.test(process.env.SUPERDOCS_API_KEY), + ); +const real = + mode === "real" && liveConfigured + ? new RealSuperDocsClient( + process.env.SUPERDOCS_API_KEY!, + process.env.SUPERDOCS_BASE_URL ?? "https://api.superdocs.app", + ) + : null; +const fake = new FakeSuperDocsClient(); +const authorities = verifyAuthorities(scenario.authorities); +const validAuthorities = authorities.filter( + (item) => item.verification_status === "VERIFIED", +); +const rejectedAuthorities = authorities.filter( + (item) => item.verification_status !== "VERIFIED", +); +const sections = { + Analysis: + "INR 6500000.00 less INR 4800000.00, INR 200000.00 and INR 300000.00 equals INR 1200000.00.", + Conclusion: + "The taxable amount is INR 1200000.00 on the supplied synthetic record.", +}; +const escaped = (value: string) => + value.replace( + /[&<>"']/g, + (char) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ + char + ]!, + ); + +type CaseState = { + id: string; + sessionId: string; + documentIds: string[]; + jobId?: string; + jobStatus: string; + proposals: Proposal[]; + negativeCleared: boolean; + approvalFingerprint?: string; + generationPromise?: Promise; + approvalPromise?: Promise; + startedAt: number; + jobStartedAt?: number; + approvedAt?: number; +}; +const cases = new Map(); +const documents = [ + { + name: "synthetic-client-facts.txt", + content: [...scenario.facts, scenario.question].join("\n"), + }, + ...validAuthorities.map((item, index) => ({ + name: `synthetic-authority-${index + 1}.txt`, + content: item.attachment_text, + })), +]; +const authorityHtml = validAuthorities + .map( + (item) => + `

${escaped(item.citation)}: “${escaped(item.exact_quote)}” (${escaped(item.source_locator)})

`, + ) + .join(""); +const draftHtml = `

Tax Technical Memorandum

Facts

${scenario.facts.map((item) => `

${escaped(item)}

`).join("")}

Question presented

${escaped(scenario.question)}

Authorities

${authorityHtml}

Analysis

${sections.Analysis}

Conclusion

${sections.Conclusion}

Confidence level

Moderate

`; +documents.push({ name: "synthetic-tax-memo.html", content: draftHtml }); + +const readJson = async (request: IncomingMessage) => { + const chunks: Buffer[] = []; + for await (const chunk of request) chunks.push(Buffer.from(chunk)); + return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}") as Record< + string, + unknown + >; +}; +const json = (response: ServerResponse, status: number, body: unknown) => { + response.writeHead(status, { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }); + response.end(JSON.stringify(body)); +}; +const safeCase = (state: CaseState) => ({ + caseId: state.id, + mode: mode === "real" ? "Live" : "Offline demo", + documents: documents.map((document, index) => ({ + name: document.name, + status: state.documentIds[index] ? "UPLOADED" : "PENDING", + documentId: state.documentIds[index] + ? `...${state.documentIds[index].slice(-8)}` + : undefined, + })), + jobStatus: state.jobStatus, + jobDurationMs: state.jobStartedAt + ? (state.approvedAt ?? Date.now()) - state.jobStartedAt + : 0, + proposals: state.proposals, + authorities: authorities.map((item) => ({ + title: item.title, + citation: item.citation, + exactQuote: item.exact_quote, + status: item.verification_status, + })), + numeric: traceNarrative( + state.negativeCleared + ? sections + : { ...sections, NegativeControl: "INR 999999.00" }, + scenario.rows, + ), + negativeCleared: state.negativeCleared, + mandatoryPending: state.proposals.filter( + (item) => item.decision === "PENDING", + ).length, + approved: Boolean(state.approvedAt), +}); +const getCase = (url: URL) => cases.get(url.pathname.split("/")[4]); +const requireConfigured = (response: ServerResponse) => { + if (liveConfigured) return true; + json(response, 503, { + code: "LIVE_CONFIGURATION_MISSING", + message: + "Live SuperDocs mode is selected, but the server credential is unavailable.", + }); + return false; +}; + +createServer(async (request, response) => { + try { + const url = new URL(request.url ?? "/", "http://localhost"); + if (request.method === "GET" && url.pathname === "/api/superdocs/config") + return json(response, 200, { + mode: mode === "real" ? "Live" : "Offline demo", + configured: liveConfigured, + error: liveConfigured + ? undefined + : { + code: "LIVE_CONFIGURATION_MISSING", + message: + "Live SuperDocs mode is selected, but the server credential is unavailable.", + }, + }); + if (request.method === "POST" && url.pathname === "/api/superdocs/cases") { + if (!requireConfigured(response)) return; + const state: CaseState = { + id: crypto.randomUUID(), + sessionId: `tax-ui-${crypto.randomUUID()}`, + documentIds: [], + jobStatus: "ready", + proposals: [], + negativeCleared: false, + startedAt: Date.now(), + }; + cases.set(state.id, state); + return json(response, 200, safeCase(state)); + } + const state = getCase(url); + if (!state) + return json(response, 404, { + code: "CASE_NOT_FOUND", + message: "The workflow case was not found.", + }); + if (!requireConfigured(response)) return; + if (request.method === "POST" && url.pathname.endsWith("/documents")) { + if (state.jobId) + return json(response, 409, { + code: "UPLOAD_CLOSED", + message: "Documents cannot be changed after generation starts.", + }); + const body = await readJson(request); + const name = typeof body.name === "string" ? body.name : ""; + const content = typeof body.content === "string" ? body.content : ""; + const expected = documents[state.documentIds.length]; + if (!expected || name !== expected.name || !content.trim()) + return json(response, 422, { + code: "INVALID_SYNTHETIC_DOCUMENT", + message: + "Select the four supplied synthetic documents in their displayed order.", + }); + const uploaded = real + ? await real.upload(name, content, state.sessionId) + : await fake.upload(name, content); + state.documentIds.push(uploaded.documentId); + return json(response, 200, safeCase(state)); + } + if (request.method === "POST" && url.pathname.endsWith("/generate")) { + if (state.documentIds.length !== documents.length) + return json(response, 409, { + code: "DOCUMENTS_REQUIRED", + message: "Upload all four synthetic documents before generation.", + }); + if (!state.jobId && !state.generationPromise) + state.generationPromise = (async () => { + const job = real + ? await real.instruction( + state.sessionId, + "Review this fictional tax memorandum. Propose separate minimal edits to Analysis and Conclusion only. Preserve every supplied number and authority quotation. Do not introduce unsupported content.", + ) + : await fake.instruction(state.sessionId, "draft"); + state.jobId = job.jobId; + state.jobStatus = job.status; + state.jobStartedAt = Date.now(); + if (!real) { + const result = await fake.poll(state.jobId); + state.jobStatus = result.status; + state.proposals = parseProposedChanges(result.proposedChanges, { + sessionId: state.sessionId, + }); + } + })().finally(() => { + state.generationPromise = undefined; + }); + if (state.generationPromise) await state.generationPromise; + return json(response, 200, safeCase(state)); + } + if (request.method === "GET" && url.pathname.endsWith("/status")) { + if (!state.jobId) + return json(response, 409, { + code: "JOB_NOT_STARTED", + message: "Generation has not started.", + }); + if ( + !["awaiting_approval", "completed", "failed", "cancelled"].includes( + state.jobStatus, + ) + ) { + const job = real + ? await real.poll(state.jobId) + : await fake.poll(state.jobId); + state.jobStatus = job.status; + if (job.proposedChanges && !state.proposals.length) + state.proposals = parseProposedChanges(job.proposedChanges, { + sessionId: state.sessionId, + }); + } + return json(response, 200, safeCase(state)); + } + if ( + request.method === "POST" && + url.pathname.endsWith("/clear-negative-controls") + ) { + state.negativeCleared = true; + return json(response, 200, safeCase(state)); + } + if (request.method === "POST" && url.pathname.endsWith("/approve")) { + const body = await readJson(request); + const decisions = body.proposals as Proposal[]; + if (!Array.isArray(decisions)) + return json(response, 422, { + code: "INVALID_DECISIONS", + message: "Review decisions are required.", + }); + state.proposals = state.proposals.map( + (proposal) => + decisions.find((item) => item.id === proposal.id) ?? proposal, + ); + if ( + !state.negativeCleared || + state.proposals.some((item) => item.decision === "PENDING") + ) + return json(response, 409, { + code: "REVIEW_BLOCKED", + message: + "Clear every blocker and mandatory decision before approval.", + }); + const fingerprint = createHash("sha256") + .update( + JSON.stringify( + state.proposals.map(({ id, decision, reviewerNote }) => ({ + id, + decision, + reviewerNote, + })), + ), + ) + .digest("hex"); + if (state.approvalFingerprint === fingerprint || state.approvalPromise) { + if (state.approvalPromise) await state.approvalPromise; + return json(response, 200, { + ...safeCase(state), + duplicateSuppressed: true, + }); + } + state.approvalFingerprint = fingerprint; + state.approvalPromise = ( + real + ? real.approve(state.sessionId, state.jobId!, state.proposals) + : fake.approve(state.sessionId, state.jobId!, state.proposals) + ) + .then(() => { + state.approvedAt = Date.now(); + }) + .catch((error) => { + state.approvalFingerprint = undefined; + throw error; + }) + .finally(() => { + state.approvalPromise = undefined; + }); + await state.approvalPromise; + return json(response, 200, { + ...safeCase(state), + duplicateSuppressed: false, + }); + } + if (request.method === "POST" && url.pathname.endsWith("/export")) { + if (!state.approvedAt) + return json(response, 409, { + code: "EXPORT_BLOCKED", + message: "Approval is required before export.", + }); + const body = await readJson(request); + const kind = body.kind === "file" ? "file" : "client"; + const html = renderReviewedExports({ + draftHtml, + proposals: state.proposals, + authorities: validAuthorities.map(({ title, citation }) => ({ + title, + citation, + })), + rows: scenario.rows, + rejectedAuthorityTitle: + rejectedAuthorities[0]?.title ?? "No rejected authority", + })[kind]; + const filename = `synthetic-tax-memo-${kind}.html`; + const exported = real + ? await real.exportHtml(html, filename) + : { bytes: new TextEncoder().encode(html), contentType: "text/html" }; + const hash = createHash("sha256").update(exported.bytes).digest("hex"); + response.writeHead(200, { + "Content-Type": exported.contentType ?? "text/html", + "Content-Disposition": `attachment; filename="${filename}"`, + "X-Content-SHA256": hash, + "Access-Control-Expose-Headers": + "X-Content-SHA256, Content-Disposition", + }); + response.end(exported.bytes); + return; + } + return json(response, 404, { + code: "ROUTE_NOT_FOUND", + message: "The requested workflow action does not exist.", + }); + } catch (error) { + const status = + error instanceof Error && /HTTP 4\d\d/.test(error.message) ? 502 : 500; + json(response, status, { + code: "SUPERDOCS_OPERATION_FAILED", + message: + status === 502 + ? "SuperDocs rejected the server-side operation." + : "The workflow could not be completed.", + }); + } +}).listen(port, "127.0.0.1", () => + console.log(`Task 2 BFF ready on http://127.0.0.1:${port} (${mode})`), +); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/server/superdocs-real.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/server/superdocs-real.ts new file mode 100644 index 00000000..f397d8c1 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/server/superdocs-real.ts @@ -0,0 +1,121 @@ +import type { Proposal } from "../src/domain"; + +export class RealSuperDocsClient { + private readonly baseUrl: string; + constructor( + private readonly apiKey: string, + baseUrl: string, + ) { + if (!apiKey) throw new Error("SUPERDOCS_API_KEY is required"); + if (baseUrl !== "https://api.superdocs.app") + throw new Error("SUPERDOCS_BASE_URL must be https://api.superdocs.app"); + this.baseUrl = baseUrl; + } + private async call(path: string, init: RequestInit = {}) { + const response = await fetch(`${this.baseUrl}${path}`, { + ...init, + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + ...init.headers, + }, + signal: AbortSignal.timeout(60_000), + }); + if (!response.ok) + throw new Error(`SuperDocs ${path} failed with HTTP ${response.status}`); + return response; + } + async upload(filename: string, content: string, existingSessionId?: string) { + const sessionId = existingSessionId ?? `tax-memo-${crypto.randomUUID()}`; + const response = await this.call("/v1/documents/upload-base64", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + filename, + file_base64: Buffer.from(content).toString("base64"), + return_html: false, + }), + }); + const body = (await response.json()) as Record; + return { + sessionId, + documentId: String( + body.document_id ?? body.durable_document_id ?? "not_returned", + ), + }; + } + async instruction(sessionId: string, message: string) { + const response = await this.call("/v1/chat/async", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + message, + approval_mode: "ask_every_time", + response_mode: "compact", + }), + }); + const body = (await response.json()) as Record; + if (!body.job_id) throw new Error("SuperDocs chat response omitted job_id"); + return { + jobId: String(body.job_id), + status: String(body.status ?? "pending"), + }; + } + async poll(jobId: string) { + const body = (await ( + await this.call(`/v1/jobs/${encodeURIComponent(jobId)}`) + ).json()) as { + status?: unknown; + metadata?: { pending_changes?: unknown }; + result?: { + document_changes?: { pending_changes?: unknown; chunk_diffs?: unknown }; + }; + }; + return { + status: String(body.status), + proposedChanges: + body.metadata?.pending_changes ?? + body.result?.document_changes?.pending_changes ?? + body.result?.document_changes?.chunk_diffs, + }; + } + async approve(sessionId: string, jobId: string, changes: Proposal[]) { + await this.call(`/v1/chat/${encodeURIComponent(sessionId)}/approve`, { + method: "POST", + body: JSON.stringify({ + job_id: jobId, + approved: true, + changes: changes.map((item) => ({ + change_id: item.id, + approved: item.decision === "APPROVED", + feedback: item.reviewerNote, + })), + }), + }); + } + async export(sessionId: string, format: "html") { + const response = await this.call("/v1/documents/export", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + format, + options: { filename: `tax-memo.${format}` }, + }), + }); + return { + format, + bytes: new Uint8Array(await response.arrayBuffer()), + contentType: response.headers.get("content-type"), + }; + } + async exportHtml(html: string, filename: string) { + const response = await this.call("/v1/documents/export", { + method: "POST", + body: JSON.stringify({ html, format: "html", options: { filename } }), + }); + return { + bytes: new Uint8Array(await response.arrayBuffer()), + contentType: response.headers.get("content-type"), + }; + } +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/App.tsx b/use-cases/yemulambika/tax-technical-memorandum-builder/src/App.tsx new file mode 100644 index 00000000..0188ccee --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/App.tsx @@ -0,0 +1,462 @@ +import { useEffect, useRef, useState } from "react"; +import type { NumericEntry, Proposal } from "./domain"; +import { validateDocumentFiles } from "./file-selection"; +import "./style.css"; + +type UiCase = { + caseId: string; + mode: string; + documents: Array<{ name: string; status: string; documentId?: string }>; + jobStatus: string; + jobDurationMs: number; + proposals: Proposal[]; + authorities: Array<{ + title: string; + citation: string; + exactQuote: string; + status: string; + }>; + numeric: NumericEntry[]; + negativeCleared: boolean; + mandatoryPending: number; + approved: boolean; + duplicateSuppressed?: boolean; +}; +type Download = { + kind: "client" | "file"; + name: string; + size: number; + hash: string; + url: string; +}; + +async function api(path: string, init: RequestInit = {}) { + const response = await fetch(`/api/superdocs${path}`, { + ...init, + headers: { "Content-Type": "application/json", ...init.headers }, + }); + if (!response.ok) { + const body = (await response.json().catch(() => ({}))) as { + code?: string; + message?: string; + }; + throw new Error( + `${body.code ?? "REQUEST_FAILED"}: ${body.message ?? "The operation failed."}`, + ); + } + return response; +} + +export default function App() { + const [mode, setMode] = useState("Loading…"); + const [configured, setConfigured] = useState(true); + const [workflow, setWorkflow] = useState(); + const [status, setStatus] = useState("Ready"); + const [error, setError] = useState(""); + const [busy, setBusy] = useState(false); + const [downloads, setDownloads] = useState([]); + const [files, setFiles] = useState([]); + const actionRef = useRef(false); + const downloadsRef = useRef([]); + + useEffect(() => { + downloadsRef.current = downloads; + }, [downloads]); + + useEffect(() => { + api("/config") + .then((response) => response.json()) + .then((body) => { + setMode(body.mode); + setConfigured(body.configured); + if (!body.configured) + setError(`${body.error.code}: ${body.error.message}`); + }) + .catch((reason) => { + setMode("Configuration error"); + setError(String(reason)); + }); + return () => + downloadsRef.current.forEach((download) => + URL.revokeObjectURL(download.url), + ); + }, []); + + async function action(label: string, run: () => Promise) { + if (actionRef.current) return; + actionRef.current = true; + setBusy(true); + setError(""); + setStatus(label); + try { + await run(); + } catch (reason) { + setError( + reason instanceof Error ? reason.message : "The operation failed.", + ); + setStatus("Failed — retry is available"); + } finally { + actionRef.current = false; + setBusy(false); + } + } + const start = () => + action("Starting a new tax memorandum case…", async () => { + const response = await api("/cases", { method: "POST", body: "{}" }); + setWorkflow(await response.json()); + setStatus("Case ready — select four synthetic documents"); + }); + const upload = () => + action("Uploading four synthetic documents…", async () => { + if (!workflow) return; + const selection = validateDocumentFiles(files); + if (!selection.ok) throw new Error(selection.message); + const pendingNames = new Set( + workflow.documents + .filter((document) => document.status !== "UPLOADED") + .map((document) => document.name), + ); + let current = workflow; + for (const file of selection.files.filter((item) => + pendingNames.has(item.name), + )) { + const response = await api(`/cases/${workflow.caseId}/documents`, { + method: "POST", + body: JSON.stringify({ name: file.name, content: await file.text() }), + }); + current = await response.json(); + setWorkflow(current); + } + setStatus("Four documents uploaded"); + }); + const generate = () => + action("Starting live memorandum generation…", async () => { + if (!workflow) return; + let current = (await ( + await api(`/cases/${workflow.caseId}/generate`, { + method: "POST", + body: "{}", + }) + ).json()) as UiCase; + setWorkflow(current); + const delays = [ + 500, 1000, 2000, 4000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, + ]; + for (const delay of delays) { + if ( + ["awaiting_approval", "completed", "failed", "cancelled"].includes( + current.jobStatus, + ) + ) + break; + setStatus(`SuperDocs job: ${current.jobStatus}`); + await new Promise((resolve) => setTimeout(resolve, delay)); + current = (await ( + await api(`/cases/${workflow.caseId}/status`) + ).json()) as UiCase; + setWorkflow(current); + } + if (!["awaiting_approval", "completed"].includes(current.jobStatus)) + throw new Error( + current.jobStatus === "failed" + ? "JOB_FAILED: SuperDocs generation failed." + : "POLL_TIMEOUT: The bounded polling window ended. Retry status safely.", + ); + setStatus(`SuperDocs job: ${current.jobStatus} — human review required`); + }); + async function clearControls() { + if (!workflow) return; + await action( + "Rejecting fabricated quotation and unmatched figure…", + async () => { + const response = await api( + `/cases/${workflow.caseId}/clear-negative-controls`, + { method: "POST", body: "{}" }, + ); + const cleared = (await response.json()) as UiCase; + setWorkflow({ ...cleared, proposals: workflow.proposals }); + setStatus("Negative controls rejected and cleared"); + }, + ); + } + function updateProposal(id: string, patch: Partial) { + setWorkflow((current) => + current + ? { + ...current, + proposals: current.proposals.map((proposal) => + proposal.id === id ? { ...proposal, ...patch } : proposal, + ), + } + : current, + ); + } + const approve = () => + action("Submitting reviewed content…", async () => { + if (!workflow) return; + const response = await api(`/cases/${workflow.caseId}/approve`, { + method: "POST", + body: JSON.stringify({ proposals: workflow.proposals }), + }); + const next = (await response.json()) as UiCase; + setWorkflow(next); + setStatus( + next.duplicateSuppressed + ? "Identical approval suppressed locally" + : "Review submitted — memo content approved", + ); + }); + async function exportOutput(kind: "client" | "file") { + if (!workflow) return; + await action(`Exporting ${kind}-ready HTML…`, async () => { + const response = await api(`/cases/${workflow.caseId}/export`, { + method: "POST", + body: JSON.stringify({ kind }), + }); + const blob = await response.blob(); + const name = + response.headers + .get("content-disposition") + ?.match(/filename="([^"]+)"/)?.[1] ?? `tax-memo-${kind}.html`; + const hash = response.headers.get("x-content-sha256") ?? "unavailable"; + const url = URL.createObjectURL(blob); + setDownloads((items) => [ + ...items.filter((item) => item.kind !== kind), + { kind, name, size: blob.size, hash, url }, + ]); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = name; + anchor.click(); + setStatus(`${name} downloaded`); + }); + } + const mandatoryPending = + workflow?.proposals.filter((proposal) => proposal.decision === "PENDING") + .length ?? 0; + const blockers = + !workflow?.negativeCleared || + Boolean(mandatoryPending) || + workflow?.proposals.some((proposal) => !proposal.reviewerNote); + const fileSelection = validateDocumentFiles(files); + + return ( +
+
+

SuperDocs use case · synthetic demonstration

+

Tax Technical Memorandum Builder

+

SuperDocs mode: {mode}

+ + + {status} + + {error && ( +

+ {error} +

+ )} +
+ {workflow && ( + <> +
+

Uploaded documents

+ {workflow.documents.map((document) => ( +

+ {document.name} — {document.status}{" "} + {document.documentId} +

+ ))} + + + +

+ Job status: {workflow.jobStatus} ({workflow.jobDurationMs}{" "} + ms) +

+
+
+

Quotation verification

+ {workflow.authorities.map((authority) => ( +
+

{authority.title}

+

{authority.citation}

+
{authority.exactQuote}
+ + {authority.status === "VERIFIED" + ? "VERIFIED" + : "REJECTED / BLOCKING"} + +
+ ))} +
+
+

Monetary traceability

+ {workflow.numeric.map((entry, index) => ( +

+ {entry.narrative_figure} —{" "} + {entry.verified + ? `TRACED ${entry.source_row_id}` + : "UNMATCHED / BLOCKING — finalization is blocked until this figure is corrected"} +

+ ))} + {!workflow.negativeCleared && ( + + )} +
+
+

Human review

+ {workflow.proposals.length === 0 ? ( +

Proposals will begin PENDING after generation.

+ ) : ( + workflow.proposals.map((proposal) => { + const sameHeading = workflow.proposals.filter( + (item) => item.section === proposal.section, + ); + const position = + sameHeading.findIndex((item) => item.id === proposal.id) + 1; + const accessibleName = `${proposal.section} proposal ${position} of ${sameHeading.length}`; + return ( +
+

{proposal.section}

+

Proposal ID: {proposal.id}

+

{proposal.after}

+ + + + {proposal.decision} +
+ ); + }) + )} +

+ Mandatory pending: {mandatoryPending} +

+ + +
+
+

Final exports

+ + + {downloads.map((download) => ( + + ))} +
+ + )} +
+ ); +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.test.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.test.ts new file mode 100644 index 00000000..b812fa64 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.test.ts @@ -0,0 +1,168 @@ +import { describe, expect, it } from "vitest"; +import { + authoritiesTable, + blockers, + caseSchema, + deterministicTotal, + outputs, + parseProposedChanges, + decideProposal, + traceNarrative, + verifyAuthorities, + type Proposal, +} from "./domain"; +import { scenario } from "./scenario"; +import { FakeSuperDocsClient, pollBounded } from "./superdocs"; +describe("tax memo evidence pipeline", () => { + it("validates case facts", () => + expect(caseSchema.parse(scenario).facts).toHaveLength(2)); + it("verifies found quotes and rejects fabricated quotes", () => { + expect(verifyAuthorities(scenario.authorities)[0].verification_status).toBe( + "VERIFIED", + ); + expect( + verifyAuthorities([ + { ...scenario.authorities[0], exact_quote: "fabricated" }, + ])[0].verification_status, + ).toBe("UNVERIFIED"); + }); + it("flags missing authority and produces complete table", () => { + const table = authoritiesTable([ + { ...scenario.authorities[0], verification_status: "MISSING" }, + ]); + expect(table[0]).toMatchObject({ + authority: expect.any(String), + citation: expect.any(String), + exact_quote: expect.any(String), + location: expect.any(String), + verification_status: "MISSING", + }); + }); + it("preserves money strings and records deterministic inputs", () => { + expect(scenario.rows[0].amount).toBe("4800000.00"); + expect( + deterministicTotal( + scenario.rows, + ["EXP-001", "EXP-002"], + "CALC-1", + "Total", + ), + ).toEqual({ + source_row_id: "CALC-1", + label: "Total", + amount: "500000.00", + input_row_ids: ["EXP-001", "EXP-002"], + }); + }); + it("detects and verifies every matched narrative figure", () => { + const r = traceNarrative( + { Analysis: "INR 1200000 against INR 6500000.00 proceeds" }, + scenario.rows, + ); + expect(r).toHaveLength(2); + expect(r.every((x) => x.verified)).toBe(true); + }); + it("blocks an unmatched figure and prevents source override", () => { + const r = traceNarrative( + { Analysis: "The model invented INR 999999" }, + scenario.rows, + ); + expect(r[0].verified).toBe(false); + expect(blockers(verifyAuthorities(scenario.authorities), r, [])).toContain( + "Unmatched figure: INR 999999", + ); + }); + it("unverified quote and missing authority block approval", () => + expect( + blockers( + [{ ...scenario.authorities[0], verification_status: "UNVERIFIED" }], + [], + [], + ["Unsupported Ruling"], + ), + ).toHaveLength(2)); + it("supports independent decisions without automatic approval", () => { + let p: Proposal[] = [ + { id: "1", section: "A", before: "", after: "a", decision: "PENDING" }, + { id: "2", section: "B", before: "", after: "b", decision: "PENDING" }, + ]; + p = decideProposal(p, "1", "APPROVED", "Checked against source"); + p = decideProposal(p, "2", "REJECTED", "Unsupported"); + p = decideProposal(p, "2", "REJECTED", "Unsupported"); + expect(p.map((x) => x.decision)).toEqual(["APPROVED", "REJECTED"]); + expect(p.map((x) => x.reviewerNote)).toEqual([ + "Checked against source", + "Unsupported", + ]); + }); + it("shares approved source state and hides internal warnings from client", () => { + const a = verifyAuthorities(scenario.authorities), + r = traceNarrative( + { Analysis: "INR 1200000", Conclusion: "Supported" }, + scenario.rows, + ), + o = outputs( + scenario, + { Analysis: "INR 1200000", Conclusion: "Supported" }, + a, + r, + [], + ); + expect(o.client.source_state).toBe(o.internal.source_state); + expect(o.client).not.toHaveProperty("unsupported_authority_warnings"); + }); + it("second-parses JSON strings and rejects invalid content", () => { + expect(parseProposedChanges('[{"change_id":"x","after":"ok"}]')[0].id).toBe( + "x", + ); + expect(() => parseProposedChanges("not-json")).toThrow("not valid JSON"); + expect(() => parseProposedChanges({ nope: true })).toThrow( + "did not contain", + ); + }); + it("prefers upstream proposal identity and fingerprints missing identities", () => { + const duplicateHeadings = [ + { change_id: "upstream-valid", section: "Analysis", after: "Valid" }, + { id: "upstream-invalid", section: "Analysis", after: "Fabricated" }, + ]; + expect( + parseProposedChanges(duplicateHeadings, { sessionId: "session-1" }).map( + (proposal) => proposal.id, + ), + ).toEqual(["upstream-valid", "upstream-invalid"]); + const withoutId = [{ section: "Analysis", after: "Stable content" }]; + const first = parseProposedChanges(withoutId, { + sessionId: "session-1", + })[0].id; + const second = parseProposedChanges(withoutId, { + sessionId: "session-1", + })[0].id; + expect(first).toMatch(/^local-[0-9a-f]{16}$/); + expect(second).toBe(first); + expect( + parseProposedChanges(withoutId, { sessionId: "session-2" })[0].id, + ).not.toBe(first); + }); + it("rejects duplicate upstream proposal identities", () => { + expect(() => + parseProposedChanges([ + { change_id: "same", after: "First" }, + { change_id: "same", after: "Second" }, + ]), + ).toThrow("duplicate identities"); + }); + it("completes fake upload/chat/approve/export without pretending live", async () => { + const c = new FakeSuperDocsClient(), + u = await c.upload("x", "x"), + j = await c.instruction(u.sessionId, "draft"), + s = await pollBounded(c, j.jobId, 1, 0), + p = parseProposedChanges(s.proposedChanges).map((x) => ({ + ...x, + decision: "APPROVED" as const, + })); + await c.approve(u.sessionId, j.jobId, p); + const e = await c.export(u.sessionId, "html"); + expect(c.approved).toHaveLength(2); + expect(new TextDecoder().decode(e.bytes)).toContain("FAKE EXPORT"); + }); +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.ts new file mode 100644 index 00000000..fe5f5826 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/domain.ts @@ -0,0 +1,266 @@ +import { z } from "zod"; + +export const moneyRowSchema = z.object({ + source_row_id: z.string().min(1), + label: z.string().min(1), + amount: z.string().regex(/^\d+(\.\d{1,2})?$/), +}); +export const authoritySchema = z.object({ + authority_id: z.string(), + title: z.string().min(1), + authority_type: z.string(), + citation: z.string(), + source_document_id: z.string(), + exact_quote: z.string().min(1), + source_locator: z.string(), + verification_status: z + .enum(["VERIFIED", "UNVERIFIED", "MISSING"]) + .default("UNVERIFIED"), + proposition: z.string(), + attachment_text: z.string(), +}); +export const caseSchema = z.object({ + facts: z.array(z.string().min(1)).min(1), + question: z.string().min(10), + currency: z.string().min(1), + rows: z.array(moneyRowSchema).min(1), + authorities: z.array(authoritySchema).min(1), +}); +export type TaxCase = z.infer; +export type Authority = z.infer; +export type Proposal = { + /** Canonical identity: upstream id/change_id, or a deterministic local fingerprint. */ + id: string; + section: string; + before: string; + after: string; + decision: "PENDING" | "APPROVED" | "REJECTED"; + mandatory?: boolean; + reviewerNote?: string; +}; +export type NumericEntry = { + narrative_figure: string; + memo_section: string; + source_row_id: string | null; + source_value: string | null; + comparison_method: string; + verified: boolean; + warning: string | null; +}; + +const norm = (s: string) => + s.normalize("NFKC").replace(/\s+/g, " ").trim().toLocaleLowerCase(); +export function verifyAuthorities(items: Authority[]): Authority[] { + return items.map((a) => ({ + ...a, + verification_status: norm(a.attachment_text).includes(norm(a.exact_quote)) + ? "VERIFIED" + : "UNVERIFIED", + })); +} +export function authoritiesTable(items: Authority[]) { + return items.map( + ({ + title, + citation, + proposition, + exact_quote, + source_locator, + verification_status, + }) => ({ + authority: title, + citation, + proposition_supported: proposition, + exact_quote, + location: source_locator, + verification_status, + }), + ); +} +export function deterministicTotal( + rows: TaxCase["rows"], + ids: string[], + id: string, + label: string, +) { + let cents = 0n; + for (const key of ids) { + const row = rows.find((r) => r.source_row_id === key); + if (!row) throw new Error(`Missing calculation input: ${key}`); + const [w, f = ""] = row.amount.split("."); + cents += BigInt(w) * 100n + BigInt((f + "00").slice(0, 2)); + } + return { + source_row_id: id, + label, + amount: `${cents / 100n}.${String(cents % 100n).padStart(2, "0")}`, + input_row_ids: ids, + }; +} +const numeric = /\b(?:INR|₹)?\s*\d[\d,]*(?:\.\d{1,2})?\b/g; +const canonical = (x: string) => + x.replace(/INR|₹|\s|,/g, "").replace(/\.00$/, ""); +export function traceNarrative( + sections: Record, + rows: TaxCase["rows"], +): NumericEntry[] { + return Object.entries(sections).flatMap(([section, text]) => + (text.match(numeric) || []).map((value) => { + const row = rows.find((r) => canonical(r.amount) === canonical(value)); + return { + narrative_figure: value, + memo_section: section, + source_row_id: row?.source_row_id ?? null, + source_value: row?.amount ?? null, + comparison_method: row + ? "normalized exact decimal match" + : "no source/calculation match", + verified: !!row, + warning: row ? null : "Unmatched narrative figure", + }; + }), + ); +} +const stableFingerprint = (value: string) => { + let hash = 0xcbf29ce484222325n; + for (const byte of new TextEncoder().encode(value)) { + hash ^= BigInt(byte); + hash = BigInt.asUintN(64, hash * 0x100000001b3n); + } + return hash.toString(16).padStart(16, "0"); +}; +const normalizedIdentityContent = (value: unknown) => + String(value ?? "") + .normalize("NFKC") + .replace(/\s+/g, " ") + .trim(); + +export function parseProposedChanges( + value: unknown, + context: { sessionId?: string } = {}, +): Proposal[] { + let parsed = value; + if (typeof parsed === "string") { + try { + parsed = JSON.parse(parsed); + } catch { + throw new Error("SuperDocs proposed changes were not valid JSON"); + } + } + const source = Array.isArray(parsed) + ? parsed + : (parsed as { proposed_changes?: unknown })?.proposed_changes; + if (!Array.isArray(source)) + throw new Error("SuperDocs response did not contain proposed_changes"); + const proposals: Proposal[] = source.map((x, i) => { + const v = x as Record; + const after = v.after ?? v.new_html; + if (typeof after !== "string") + throw new Error(`Proposed change ${i + 1} has invalid content`); + const upstreamId = v.id ?? v.change_id; + const localIdentity = [ + context.sessionId ?? "unknown-session", + i, + v.operation_type ?? v.operation ?? v.type ?? "replace", + v.source_range_id ?? v.range_id ?? v.target ?? v.chunk_id ?? v.before, + normalizedIdentityContent(after), + ].join("\u001f"); + return { + id: + upstreamId === undefined || String(upstreamId).trim() === "" + ? `local-${stableFingerprint(localIdentity)}` + : String(upstreamId), + section: String(v.section ?? "Analysis"), + before: String(v.before ?? v.old_html ?? ""), + after, + decision: "PENDING", + }; + }); + if ( + new Set(proposals.map((proposal) => proposal.id)).size !== proposals.length + ) + throw new Error( + "SuperDocs proposed changes contained duplicate identities", + ); + return proposals; +} +export function blockers( + authorities: Authority[], + register: NumericEntry[], + proposals: Proposal[], + missing: string[] = [], +) { + return [ + ...register + .filter((x) => !x.verified) + .map((x) => `Unmatched figure: ${x.narrative_figure}`), + ...authorities + .filter((x) => x.verification_status !== "VERIFIED") + .map((x) => `Unverified quotation: ${x.title}`), + ...missing.map((x) => `Missing authority: ${x}`), + ...(proposals.some((x) => (x.mandatory ?? true) && x.decision === "PENDING") + ? ["Mandatory proposals remain undecided"] + : []), + ]; +} +export function decideProposal( + proposals: Proposal[], + id: string, + decision: Exclude, + reviewerNote: string, +) { + return proposals.map((proposal) => + proposal.id === id ? { ...proposal, decision, reviewerNote } : proposal, + ); +} +export function outputs( + data: TaxCase, + sections: Record, + authorities: Authority[], + register: NumericEntry[], + proposals: Proposal[], +) { + const sourceState = JSON.stringify({ + data, + sections, + authorities, + register, + proposals, + }); + const client = { + facts: data.facts, + question: data.question, + authorities: authorities + .filter((x) => x.verification_status === "VERIFIED") + .map((x) => x.citation), + analysis: [ + sections.Analysis, + ...proposals + .filter((x) => x.decision === "APPROVED" && x.section === "Analysis") + .map((x) => x.after), + ].join("\n"), + conclusion: [ + sections.Conclusion, + ...proposals + .filter((x) => x.decision === "APPROVED" && x.section === "Conclusion") + .map((x) => x.after), + ].join("\n"), + confidence_level: "Moderate", + source_state: sourceState, + }; + const internal = { + ...client, + authorities_table: authoritiesTable(authorities), + numeric_register: register, + unsupported_authority_warnings: authorities + .filter((x) => x.verification_status !== "VERIFIED") + .map((x) => x.title), + review_log: proposals.map((x) => ({ + id: x.id, + decision: x.decision, + reviewer_note: x.reviewerNote ?? "", + })), + limitations: ["Synthetic demonstration; not tax advice."], + }; + return { client, internal }; +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.test.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.test.ts new file mode 100644 index 00000000..b1663af4 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from "vitest"; +import type { Proposal } from "./domain"; +import { renderReviewedExports } from "./export-renderer"; + +const proposals: Proposal[] = [ + { + id: "approved-id", + section: "Analysis", + before: "", + after: "

Approved Analysis

Approved analysis content.

", + decision: "APPROVED", + reviewerNote: "Keep", + }, + { + id: "rejected-id", + section: "Analysis", + before: "", + after: "

Rejected Analysis

Rejected analysis content.

", + decision: "REJECTED", + reviewerNote: "Remove", + }, + { + id: "approved-heading-only", + section: "Analysis", + before: "", + after: "

Analysis

", + decision: "APPROVED", + reviewerNote: "Would be orphaned", + }, +]; + +describe("reviewed HTML renderer", () => { + const rendered = renderReviewedExports({ + draftHtml: + "

Memo

Analysis

Original approved analysis.

Conclusion

Conclusion.

Confidence level

Moderate

", + proposals, + authorities: [{ title: "Rule", citation: "Code 1" }], + rows: [{ amount: "1200000.00", source_row_id: "CALC-001" }], + rejectedAuthorityTitle: "Fabricated authority", + }); + + it("retains approved headings/content and removes rejected blocks", () => { + expect(rendered.reviewedBase).toContain("Approved Analysis"); + expect(rendered.reviewedBase).toContain("Approved analysis content."); + expect(rendered.reviewedBase).not.toContain("Rejected Analysis"); + expect(rendered.reviewedBase).not.toContain("Rejected analysis content."); + }); + + it("does not leave an orphan or empty heading", () => { + expect(rendered.reviewedBase).not.toMatch( + /]*>[^<]*<\/h[1-6]>\s*Analysis

Conclusion

", + ); + }); + + it("keeps client output free of internal warnings", () => { + expect(rendered.client).not.toContain("Unsupported-authority warnings"); + expect(rendered.client).not.toContain("Reviewer decision log"); + }); + + it("keeps file-ready audit sections", () => { + expect(rendered.file).toContain("Authorities table"); + expect(rendered.file).toContain("Numeric traceability register"); + expect(rendered.file).toContain("Unsupported-authority warnings"); + expect(rendered.file).toContain("Reviewer decision log"); + }); + + it("derives client and file outputs from the same reviewed state", () => { + expect(rendered.client).toBe(rendered.reviewedBase); + expect(rendered.file.startsWith(rendered.reviewedBase)).toBe(true); + }); +}); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.ts new file mode 100644 index 00000000..04e966e3 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/export-renderer.ts @@ -0,0 +1,54 @@ +import type { Proposal } from "./domain"; + +type AuditAuthority = { title: string; citation: string }; +type AuditRow = { amount: string; source_row_id: string }; + +const escapeHtml = (value: string) => + value.replace( + /[&<>"']/g, + (character) => + ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + })[character]!, + ); + +const headingOnly = /^\s*]*)?>[\s\S]*?<\/h\1>\s*$/i; + +/** Removes headings with no rendered content before the next heading. */ +export function removeOrphanHeadings(html: string) { + let result = html; + const consecutiveHeading = + /]*)>([\s\S]*?)<\/h\1>\s*(?=))/gi; + while (consecutiveHeading.test(result)) + result = result.replace(consecutiveHeading, ""); + return result; +} + +export function renderReviewedExports(input: { + draftHtml: string; + proposals: Proposal[]; + authorities: AuditAuthority[]; + rows: AuditRow[]; + rejectedAuthorityTitle: string; +}) { + const approvedFragments = input.proposals + .filter((proposal) => proposal.decision === "APPROVED") + .map((proposal) => proposal.after) + .filter((fragment) => !headingOnly.test(fragment)); + const reviewedBase = removeOrphanHeadings( + input.draftHtml.replace( + "

Conclusion

", + `${approvedFragments.join("\n")}

Conclusion

`, + ), + ); + const auditSections = `

Authorities table

${input.authorities.map((item) => `

${escapeHtml(item.title)} | ${escapeHtml(item.citation)} | VERIFIED

`).join("")}

Numeric traceability register

${input.rows.map((row) => `

INR ${escapeHtml(row.amount)} | ${escapeHtml(row.source_row_id)} | VERIFIED

`).join("")}

Unsupported-authority warnings

${escapeHtml(input.rejectedAuthorityTitle)} rejected.

Reviewer decision log

${input.proposals.map((item) => `${escapeHtml(item.id)}: ${item.decision}`).join(" | ")}

`; + return { + reviewedBase, + client: reviewedBase, + file: `${reviewedBase}${auditSections}`, + }; +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/file-selection.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/file-selection.ts new file mode 100644 index 00000000..a12907b6 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/file-selection.ts @@ -0,0 +1,44 @@ +export const SYNTHETIC_DOCUMENT_ORDER = [ + "synthetic-client-facts.txt", + "synthetic-authority-1.txt", + "synthetic-authority-2.txt", + "synthetic-tax-memo.html", +] as const; + +const expectedNames = new Set(SYNTHETIC_DOCUMENT_ORDER); + +export function validateDocumentFiles( + files: readonly T[], +) { + const counts = new Map(); + for (const file of files) + counts.set(file.name, (counts.get(file.name) ?? 0) + 1); + + const missing = SYNTHETIC_DOCUMENT_ORDER.filter((name) => !counts.has(name)); + const duplicate = [...counts.entries()] + .filter(([name, count]) => expectedNames.has(name) && count > 1) + .map(([name]) => name) + .sort(); + const unexpected = [...counts.keys()] + .filter((name) => !expectedNames.has(name)) + .sort(); + const problems = [ + missing.length ? `Missing: ${missing.join(", ")}` : "", + duplicate.length ? `Duplicate: ${duplicate.join(", ")}` : "", + unexpected.length ? `Unexpected: ${unexpected.join(", ")}` : "", + ].filter(Boolean); + + if (files.length !== SYNTHETIC_DOCUMENT_ORDER.length || problems.length) + return { + ok: false as const, + files: [] as T[], + message: problems.join("; ") || "Select exactly the four supplied files.", + }; + + const byName = new Map(files.map((file) => [file.name, file])); + return { + ok: true as const, + files: SYNTHETIC_DOCUMENT_ORDER.map((name) => byName.get(name)!), + message: "", + }; +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/main.tsx b/use-cases/yemulambika/tax-technical-memorandum-builder/src/main.tsx new file mode 100644 index 00000000..9ec3163a --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/main.tsx @@ -0,0 +1,8 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/scenario.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/scenario.ts new file mode 100644 index 00000000..941bdeb8 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/scenario.ts @@ -0,0 +1,82 @@ +import type { TaxCase } from "./domain"; + +export const scenario: TaxCase = { + facts: [ + "Fictional Deccan Byteworks Pvt Ltd acquired a demonstration asset on 15 April 2024 for INR 4800000.00 and sold it on 30 June 2026 for INR 6500000.00.", + "It incurred INR 200000.00 acquisition costs and INR 300000.00 selling expenses. The explicitly calculated taxable amount is INR 1200000.00 (INR 6500000.00 less INR 4800000.00, INR 200000.00 and INR 300000.00).", + ], + question: + "What is the taxable amount on the fictional asset disposal under the attached synthetic authorities?", + currency: "INR", + rows: [ + { + source_row_id: "COST-001", + label: "Purchase price / cost basis", + amount: "4800000.00", + }, + { + source_row_id: "PROCEEDS-001", + label: "Sale proceeds", + amount: "6500000.00", + }, + { + source_row_id: "EXP-001", + label: "Acquisition costs", + amount: "200000.00", + }, + { + source_row_id: "EXP-002", + label: "Selling expenses", + amount: "300000.00", + }, + { + source_row_id: "CALC-001", + label: "Taxable amount", + amount: "1200000.00", + }, + ], + authorities: [ + { + authority_id: "AUTH-001", + title: "Synthetic Basis Rule", + authority_type: "fictional legislation", + citation: "Synthetic Tax Code § 10(1)", + source_document_id: "synthetic-authority-1", + exact_quote: + "The verified acquisition price and direct acquisition costs form the taxpayer's basis in an asset.", + source_locator: "Section 10(1), paragraph 1", + verification_status: "UNVERIFIED", + proposition: "Verified acquisition price and direct costs form basis.", + attachment_text: + "SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY\nSection 10(1), paragraph 1. The verified acquisition price and direct acquisition costs form the taxpayer's basis in an asset.", + }, + { + authority_id: "AUTH-002", + title: "Synthetic Disposal Proceeds Rule", + authority_type: "fictional regulation", + citation: "Synthetic Tax Regulations 24(2)", + source_document_id: "synthetic-authority-2", + exact_quote: + "Taxable disposal proceeds equal consideration received less verified basis and direct disposal expenses.", + source_locator: "Regulation 24(2), paragraph 3", + verification_status: "UNVERIFIED", + proposition: + "Verified basis and direct disposal expenses reduce proceeds.", + attachment_text: + "SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY\nRegulation 24(2), paragraph 3. Taxable disposal proceeds equal consideration received less verified basis and direct disposal expenses.", + }, + { + authority_id: "AUTH-NEGATIVE", + title: "Fabricated Quotation Negative Control", + authority_type: "negative test fixture", + citation: "Synthetic Tax Bulletin 99", + source_document_id: "synthetic-authority-negative", + exact_quote: "Every fictional disposal is automatically exempt from tax.", + source_locator: "Intentionally absent", + verification_status: "UNVERIFIED", + proposition: "Must be rejected and excluded.", + attachment_text: + "SYNTHETIC AUTHORITY — FOR DEMONSTRATION ONLY\nThis attachment deliberately does not contain the claimed quotation.", + }, + ], +}; diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/style.css b/use-cases/yemulambika/tax-technical-memorandum-builder/src/style.css new file mode 100644 index 00000000..d66d2935 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/style.css @@ -0,0 +1,112 @@ +:root { + font-family: Inter, ui-sans-serif, system-ui; + color: #17201e; + background: #f3f1ea; +} +* { + box-sizing: border-box; +} +body { + margin: 0; +} +main { + max-width: 1180px; + margin: auto; + padding: 40px 24px 80px; +} +header { + background: #143d35; + color: #fff; + padding: 40px; + border-radius: 20px; +} +h1 { + font-family: Georgia, serif; + font-size: clamp(2.4rem, 6vw, 5rem); + margin: 0.15em 0; +} +.eyebrow { + text-transform: uppercase; + letter-spacing: 0.13em; + color: #b8d6ca; +} +section { + background: #fff; + margin-top: 22px; + padding: 28px; + border: 1px solid #d9ddd7; + border-radius: 14px; +} +h2 { + font-family: Georgia, serif; + font-size: 2rem; +} +table { + border-collapse: collapse; + width: 100%; + font-size: 0.9rem; +} +th, +td { + text-align: left; + vertical-align: top; + padding: 12px; + border-bottom: 1px solid #dfe4df; +} +small { + display: block; + color: #66706c; + margin-top: 5px; +} +button { + background: #e6a94f; + border: 0; + border-radius: 7px; + padding: 10px 15px; + margin: 5px 8px 5px 0; + font-weight: 700; + cursor: pointer; +} +button:disabled { + background: #d4d4d0; + color: #888; + cursor: not-allowed; +} +.reject { + background: #e7d2cc; +} +.status { + margin-left: 12px; +} +.ok { + color: #19704e; + font-weight: 700; +} +.bad { + color: #aa342b; + font-weight: 700; +} +.proposal { + border-left: 4px solid #e6a94f; + padding: 8px 18px; + margin: 18px 0; +} +.before { + color: #68716e; + text-decoration: line-through; +} +.warnings { + background: #fff6df; + padding: 12px 18px; + margin: 20px 0; + border-radius: 8px; +} +@media (max-width: 700px) { + table { + display: block; + overflow: auto; + } + header { + padding: 25px; + } +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/src/superdocs.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/src/superdocs.ts new file mode 100644 index 00000000..2942a1bb --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/src/superdocs.ts @@ -0,0 +1,136 @@ +import type { Proposal } from "./domain"; + +export type UploadResult = { sessionId: string; documentId: string }; +export type ChatResult = { jobId: string; status: string }; +export type ExportResult = { format: string; bytes: Uint8Array }; +export interface SuperDocsClient { + upload(filename: string, content: string): Promise; + instruction(sessionId: string, message: string): Promise; + poll(jobId: string): Promise<{ status: string; proposedChanges?: unknown }>; + approve(sessionId: string, jobId: string, changes: Proposal[]): Promise; + export(sessionId: string, format: "html"): Promise; +} +export class SuperDocsError extends Error { + constructor( + message: string, + public action: string, + public status?: number, + ) { + super(message); + } +} +export class FakeSuperDocsClient implements SuperDocsClient { + approved: Proposal[] = []; + async upload(_filename: string, _content: string) { + return { sessionId: "fake-session", documentId: "fake-document" }; + } + async instruction(_sessionId: string, _message: string) { + return { jobId: "fake-job", status: "awaiting_approval" }; + } + async poll(_jobId: string) { + return { + status: "awaiting_approval", + proposedChanges: JSON.stringify([ + { + change_id: "fixture-change-valid", + section: "Analysis", + before: "", + after: "INR 1200000 taxable amount is supported.", + }, + { + change_id: "fixture-change-fabricated", + section: "Analysis", + before: "", + after: "Every fictional disposal is automatically exempt from tax.", + }, + ]), + }; + } + async approve(_s: string, _j: string, changes: Proposal[]) { + this.approved = changes.filter((x) => x.decision === "APPROVED"); + } + async export(_s: string, format: "html") { + return { + format, + bytes: new TextEncoder().encode("FAKE EXPORT — not live evidence"), + }; + } +} + +/** Browser client: credentials are held by the same-origin Node BFF only. */ +export class BffSuperDocsClient implements SuperDocsClient { + private async call(path: string, init: RequestInit = {}) { + const response = await fetch(`/api/superdocs${path}`, { + ...init, + headers: { "Content-Type": "application/json", ...init.headers }, + }); + if (!response.ok) { + const body = (await response.json().catch(() => ({}))) as { + error?: string; + }; + throw new SuperDocsError( + body.error ?? `Request failed with HTTP ${response.status}`, + path, + response.status, + ); + } + return response; + } + async upload(filename: string, content: string) { + return (await ( + await this.call("/upload", { + method: "POST", + body: JSON.stringify({ filename, content }), + }) + ).json()) as UploadResult; + } + async instruction(sessionId: string, message: string) { + return (await ( + await this.call("/chat", { + method: "POST", + body: JSON.stringify({ sessionId, message }), + }) + ).json()) as ChatResult; + } + async poll(jobId: string) { + return (await ( + await this.call(`/jobs/${encodeURIComponent(jobId)}`) + ).json()) as { status: string; proposedChanges?: unknown }; + } + async approve(sessionId: string, jobId: string, changes: Proposal[]) { + await this.call(`/chat/${encodeURIComponent(sessionId)}/approve`, { + method: "POST", + body: JSON.stringify({ jobId, changes }), + }); + } + async export(sessionId: string, format: "html") { + const response = await this.call("/export", { + method: "POST", + body: JSON.stringify({ sessionId, format }), + }); + return { format, bytes: new Uint8Array(await response.arrayBuffer()) }; + } +} +export async function pollBounded( + client: SuperDocsClient, + jobId: string, + max = 30, + delay = 1000, +) { + for (let i = 0; i < max; i++) { + const state = await client.poll(jobId); + if (["awaiting_approval", "completed"].includes(state.status)) return state; + if (["failed", "cancelled"].includes(state.status)) + throw new SuperDocsError( + `SuperDocs job ended with status ${state.status}`, + "poll", + ); + await new Promise((resolve) => + setTimeout(resolve, Math.min(delay * 2 ** i, 8000)), + ); + } + throw new SuperDocsError( + `SuperDocs operation timed out after ${max} polls`, + "poll", + ); +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/tsconfig.json b/use-cases/yemulambika/tax-technical-memorandum-builder/tsconfig.json new file mode 100644 index 00000000..7de020a2 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "jsx": "react-jsx", + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/use-cases/yemulambika/tax-technical-memorandum-builder/vite.config.ts b/use-cases/yemulambika/tax-technical-memorandum-builder/vite.config.ts new file mode 100644 index 00000000..be603dc4 --- /dev/null +++ b/use-cases/yemulambika/tax-technical-memorandum-builder/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +export default defineConfig({ + plugins: [react()], + server: { proxy: { "/api/superdocs": "http://127.0.0.1:4174" } }, +});