+ Something went wrong +
+Unable to load listing
++ {error?.message || 'An unexpected error occurred while loading this page.'} +
+diff --git a/.eslintrc.json b/.eslintrc.json
index cf5d8d47..a40a77a5 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,8 @@
{
"extends": [
"next/core-web-vitals",
- "next/typescript"
+ "next/typescript",
+ "plugin:jsx-a11y/recommended"
],
"rules": {
"@typescript-eslint/no-unused-vars": [
diff --git a/docs/MARKETPLACE_LISTING_PAGE.md b/docs/MARKETPLACE_LISTING_PAGE.md
new file mode 100644
index 00000000..f5adc2d0
--- /dev/null
+++ b/docs/MARKETPLACE_LISTING_PAGE.md
@@ -0,0 +1,42 @@
+# Marketplace Listing Detail Page
+
+This document describes the new deep-linkable marketplace listing detail page at `src/app/marketplace/[id]/page.tsx`.
+
+## Purpose
+
+The listing detail page provides a durable, shareable route for marketplace listings that is independent of the existing `CommitmentDetailsModal` quick-view UI.
+
+## Routes
+
+- `GET /marketplace/:id` — renders the new marketplace listing detail page.
+- `GET /api/marketplace/listings/:id` — returns the listing payload used by the page and other marketplace clients.
+
+## Features
+
+- Server-rendered listing details for marketplace listings
+- Trust and risk signals using `TrustBadge` and `ReputationDisplay`
+- Purchase flow wiring via `/api/marketplace/listings/:id/preflight` and `/api/marketplace/listings/:id/purchase`
+- Loading fallback via `src/app/marketplace/[id]/loading.tsx`
+- Error boundary via `src/app/marketplace/[id]/error.tsx`
+- Deep-linkable metadata for sharing and social previews
+
+## Implementation
+
+- `src/app/marketplace/[id]/page.tsx`
+ - loads the listing server-side from `marketplaceService.getPublicListing`
+ - returns `notFound()` when the listing does not exist
+ - renders the marketplace detail component and purchase panel
+
+- `src/components/MarketplaceListingDetail.tsx`
+ - renders marketplace listing fields, risk indicators, and trust signals
+
+- `src/components/MarketplaceListingPurchase.tsx`
+ - client-side purchase CTA with preflight and purchase request handling
+
+- `src/app/api/marketplace/listings/[id]/route.ts`
+ - adds a public `GET` route for listing retrieval
+ - retains `DELETE` for listing cancellation
+
+## Notes
+
+This page is intentionally separate from the quick-view modal so listings can be shared directly, bookmarked, and indexed by client-side navigation.
diff --git a/docs/PR_BACKEND_CSRF.md b/docs/PR_BACKEND_CSRF.md
new file mode 100644
index 00000000..d53c7ac0
--- /dev/null
+++ b/docs/PR_BACKEND_CSRF.md
@@ -0,0 +1,39 @@
+# PR Documentation: Backend CSRF Coverage
+
+## Summary
+
+This PR strengthens the backend CSRF test suite for browser-session mutation endpoints.
+It adds focused coverage around the double-submit cookie flow implemented in `src/lib/backend/csrf.ts`, with emphasis on token freshness, session binding, rotation, and origin validation.
+
+## What changed
+
+- Expanded `src/lib/backend/csrf.test.ts` to cover:
+ - fresh CSRF token verification for the same browser session
+ - rejection of empty or missing CSRF headers
+ - rejection when the header token belongs to a different session
+ - token rotation behavior where the old token is rejected and the new token is accepted
+ - same-origin enforcement for `assertSameOriginForCookieSession`
+- Verified the behavior with:
+ - `./node_modules/.bin/vitest run src/lib/backend/csrf.test.ts`
+
+## Why this matters
+
+- Protects cookie-authenticated write routes from forged cross-site requests.
+- Ensures stale or tampered CSRF tokens cannot be replayed with a valid session cookie.
+- Confirms the helper continues to reject mismatched, empty, and cross-session token values.
+- Makes the intended security behavior explicit and regression-resistant.
+
+## Testing
+
+1. Run the focused CSRF unit tests:
+ - `./node_modules/.bin/vitest run src/lib/backend/csrf.test.ts`
+2. Confirm the result:
+ - `17 tests passed`
+3. Optionally run the wider suite for regression coverage:
+ - `pnpm test`
+
+## Notes
+
+- No production behavior was changed; this PR is test coverage focused.
+- The existing CSRF helper already enforces same-origin checks and avoids enforcement for bearer-token API clients.
+- These additions make the security contract easier to audit and maintain over time.
diff --git a/docs/PR_JSX_A11Y.md b/docs/PR_JSX_A11Y.md
new file mode 100644
index 00000000..79011060
--- /dev/null
+++ b/docs/PR_JSX_A11Y.md
@@ -0,0 +1,36 @@
+# PR Documentation: JSX Accessibility Linting
+
+## Summary
+
+This PR enables the recommended `jsx-a11y` ESLint rules across the frontend and fixes the accessibility issues they surface.
+The goal is to make accessibility checks part of the normal development workflow and reduce regressions in interactive UI components.
+
+## What changed
+
+- Enabled the recommended `jsx-a11y` rules in `.eslintrc.json`.
+- Fixed accessibility violations in affected components and route views, including:
+ - keyboard-accessible interactive controls
+ - proper label associations for form-like controls
+ - valid link semantics and navigable anchors
+ - safer interactive patterns in dialogs and menus
+- Added documentation for the linting setup and local validation steps in `docs/accessibility/LINTING.md`.
+
+## Why this matters
+
+- Helps prevent common accessibility regressions in buttons, links, forms, and dialogs.
+- Improves support for keyboard and assistive-technology users.
+- Makes accessibility enforcement part of the regular frontend workflow rather than a one-off review task.
+
+## Testing
+
+1. Run the targeted lint check for updated UI files:
+ - `./node_modules/.bin/eslint "src/components/**/*.{ts,tsx}" "src/app/**/*.{ts,tsx}" --ext .ts,.tsx`
+2. Confirm the updated `jsx-a11y` issues are resolved.
+3. Optionally run the broader repo lint command:
+ - `corepack pnpm lint`
+
+## Notes
+
+- This PR focuses on enabling and remediating the accessibility issues surfaced by `jsx-a11y`.
+- Some unrelated repository-wide lint issues remain outside the scope of this change.
+- The new documentation provides a repeatable local validation path for future contributors.
diff --git a/docs/PR_MARKETPLACE_LISTING_DETAIL.md b/docs/PR_MARKETPLACE_LISTING_DETAIL.md
new file mode 100644
index 00000000..abfe9e04
--- /dev/null
+++ b/docs/PR_MARKETPLACE_LISTING_DETAIL.md
@@ -0,0 +1,40 @@
+# PR Documentation: Marketplace Listing Detail Page
+
+## Summary
+
+This PR adds a deep-linkable marketplace listing detail page at `/marketplace/[id]`.
+The page renders server-side listing data, supports rich metadata, and wires the purchase flow through the public marketplace API.
+
+## What changed
+
+- Added server route: `src/app/marketplace/[id]/page.tsx`
+- Added route-level loading fallback: `src/app/marketplace/[id]/loading.tsx`
+- Added route-level error boundary: `src/app/marketplace/[id]/error.tsx`
+- Added marketplace listing detail component: `src/components/MarketplaceListingDetail.tsx`
+- Added client-side purchase widget: `src/components/MarketplaceListingPurchase.tsx`
+- Added public GET API route for listings: `src/app/api/marketplace/listings/[id]/route.ts`
+- Expanded marketplace service: `src/lib/backend/services/marketplace.ts`
+- Added listing detail page documentation: `docs/MARKETPLACE_LISTING_PAGE.md`
+- Added API docs update: `docs/backend-api-reference.md`
+- Added CORS policy update: `docs/backend-cors-policy.md`
+
+## Why this matters
+
+- Users can now share and bookmark individual marketplace listings.
+- The page is server-rendered for better SEO and metadata previews.
+- The purchase flow is wired consistently with existing marketplace endpoints.
+- The new public GET endpoint enables marketplace clients to fetch a single listing by ID.
+
+## How to verify
+
+1. Run the new page test file:
+ - `./node_modules/.bin/vitest run src/app/marketplace/[id]/page.test.tsx`
+2. Confirm the test output includes:
+ - `4 tests passed`
+3. Review the new docs and API reference changes.
+
+## Notes
+
+- No backend breaking contract changes were introduced beyond the addition of a new public GET endpoint.
+- The listing detail page uses existing marketplace models and service lookup logic.
+- The purchase panel depends on wallet connection and `/api/marketplace/listings/[id]/preflight`.
diff --git a/docs/accessibility/LINTING.md b/docs/accessibility/LINTING.md
new file mode 100644
index 00000000..29d6ca8f
--- /dev/null
+++ b/docs/accessibility/LINTING.md
@@ -0,0 +1,29 @@
+# Accessibility Linting
+
+## Overview
+
+The frontend now enables the recommended `jsx-a11y` ESLint rules to catch common accessibility issues during development and CI.
+
+## Enabled rule set
+
+The repository extends the recommended `jsx-a11y` configuration in `.eslintrc.json`.
+
+This helps enforce:
+
+- meaningful alt text and accessible images
+- keyboard-accessible links and buttons
+- proper form labels and associations
+- safe interactive element patterns for modal and menu UI
+
+## Local validation
+
+Run the accessibility lint check locally with:
+
+```bash
+./node_modules/.bin/eslint "src/components/**/*.{ts,tsx}" "src/app/**/*.{ts,tsx}" --ext .ts,.tsx
+```
+
+## Notes
+
+- Inline eslint suppressions are only used when a UI pattern genuinely requires a targeted exception and the reason is documented in the code.
+- The goal is to keep the app accessible for keyboard, screen-reader, and assistive-technology users while preventing regressions in reusable UI components.
diff --git a/docs/backend-api-reference.md b/docs/backend-api-reference.md
index bdc7293e..4fa2994b 100644
--- a/docs/backend-api-reference.md
+++ b/docs/backend-api-reference.md
@@ -64,6 +64,44 @@ Clients should wait the indicated seconds before retrying. See [error-handling.m
---
+## `GET /api/marketplace/listings/[id]`
+
+Fetches a single marketplace listing by its listing ID. This endpoint is used by
+`/marketplace/[id]` to render deep-linkable listing detail pages.
+
+- **Authentication**: none
+- **Path parameter**: `id` — the marketplace listing ID
+- **Response**:
+ - `200 OK`: Listing returned.
+ - `404 Not Found`: Listing does not exist.
+
+### Example
+
+```bash
+curl -X GET http://localhost:3000/api/marketplace/listings/LST-001
+```
+
+```json
+{
+ "success": true,
+ "data": {
+ "listing": {
+ "listingId": "LST-001",
+ "commitmentId": "CMT-001",
+ "type": "Safe",
+ "amount": 50000,
+ "remainingDays": 25,
+ "maxLoss": 2,
+ "currentYield": 5.2,
+ "complianceScore": 95,
+ "price": 52000
+ }
+ }
+}
+```
+
+---
+
## `POST /api/marketplace/listings/[id]/purchase`
Purchases a marketplace listing. Requires an active session cookie. Runs
diff --git a/docs/backend-cors-policy.md b/docs/backend-cors-policy.md
index a445420a..70472c4b 100644
--- a/docs/backend-cors-policy.md
+++ b/docs/backend-cors-policy.md
@@ -52,6 +52,7 @@ Public browser routes:
- `GET /api/ready`
- `GET /api/marketplace`
- `GET /api/marketplace/listings`
+- `GET /api/marketplace/listings/[id]`
- `GET /api/attestations`
First-party browser routes:
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 56eedfb5..0ec5ffe6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -90,6 +90,9 @@ importers:
ioredis:
specifier: ^5.11.0
version: 5.11.0
+ jsdom:
+ specifier: ^29.1.1
+ version: 29.1.1
node-fetch:
specifier: ^3.3.2
version: 3.3.2
@@ -101,7 +104,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^4.0.18
- version: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
+ version: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
packages:
@@ -112,9 +115,20 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
- '@babel/code-frame@7.29.0':
- resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
- engines: {node: '>=6.9.0'}
+ '@asamuzakjp/css-color@5.1.11':
+ resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/dom-selector@7.1.1':
+ resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/generational-cache@1.0.1':
+ resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/nwsapi@2.3.9':
+ resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
'@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
@@ -224,6 +238,46 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
+ '@bramus/specificity@2.4.2':
+ resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
+ hasBin: true
+
+ '@csstools/color-helpers@6.1.0':
+ resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==}
+ engines: {node: '>=20.19.0'}
+
+ '@csstools/css-calc@3.2.1':
+ resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-color-parser@4.1.9':
+ resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-parser-algorithms@4.0.0':
+ resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^4.0.0
+
+ '@csstools/css-syntax-patches-for-csstree@1.1.5':
+ resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==}
+ peerDependencies:
+ css-tree: ^3.2.1
+ peerDependenciesMeta:
+ css-tree:
+ optional: true
+
+ '@csstools/css-tokenizer@4.0.0':
+ resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
+ engines: {node: '>=20.19.0'}
+
'@emnapi/core@1.10.0':
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
@@ -407,6 +461,15 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@exodus/bytes@1.15.1':
+ resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ '@noble/hashes': ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ '@noble/hashes':
+ optional: true
+
'@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -674,6 +737,7 @@ packages:
'@stellar/stellar-base@11.0.1':
resolution: {integrity: sha512-VQh+1KEtFjegD6spx08+lENt8tQOkQQQZoLtqExjpRXyWlqDhEe+bXMlBTYKDc5MIynHyD42RPEib27UG17trA==}
+ deprecated: This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support.
'@stellar/stellar-sdk@11.3.0':
resolution: {integrity: sha512-i+heopibJNRA7iM8rEPz0AXphBPYvy2HDo8rxbDwWpozwCfw8kglP9cLkkhgJe8YicgLrdExz/iQZaLpqLC+6w==}
@@ -1242,6 +1306,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ bidi-js@1.0.3:
+ resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+
bignumber.js@9.3.1:
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
@@ -1329,6 +1396,10 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ css-tree@3.2.1:
+ resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
@@ -1386,6 +1457,10 @@ packages:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
+ data-urls@7.0.0:
+ resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@@ -1418,6 +1493,9 @@ packages:
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1483,6 +1561,10 @@ packages:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'}
+ entities@8.0.0:
+ resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
+ engines: {node: '>=20.19.0'}
+
es-abstract@1.24.2:
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
engines: {node: '>= 0.4'}
@@ -1846,6 +1928,10 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
+ html-encoding-sniffer@6.0.0:
+ resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -1972,6 +2058,9 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -2048,6 +2137,15 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
+ jsdom@29.1.1:
+ resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -2177,6 +2275,10 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ lru-cache@11.5.1:
+ resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
+ engines: {node: 20 || >=22}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -2203,6 +2305,9 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
+ mdn-data@2.27.1:
+ resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -2352,6 +2457,9 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse5@8.0.1:
+ resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -2494,6 +2602,10 @@ packages:
resolution: {integrity: sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==}
engines: {bare: '>=1.10.0'}
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
@@ -2541,6 +2653,10 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
@@ -2704,6 +2820,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
tailwind-merge@3.6.0:
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
@@ -2735,6 +2854,13 @@ packages:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
+ tldts-core@7.4.4:
+ resolution: {integrity: sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==}
+
+ tldts@7.4.4:
+ resolution: {integrity: sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==}
+ hasBin: true
+
to-buffer@1.2.2:
resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==}
engines: {node: '>= 0.4'}
@@ -2746,6 +2872,14 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
+ tough-cookie@6.0.1:
+ resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
+ engines: {node: '>=16'}
+
+ tr46@6.0.0:
+ resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
+ engines: {node: '>=20'}
+
ts-api-utils@2.5.0:
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
@@ -2802,6 +2936,10 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici@7.28.0:
+ resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
+ engines: {node: '>=20.18.1'}
+
unrs-resolver@1.11.1:
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
@@ -2909,14 +3047,30 @@ packages:
jsdom:
optional: true
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
+ webidl-conversions@8.0.1:
+ resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
+ engines: {node: '>=20'}
+
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
+ whatwg-mimetype@5.0.0:
+ resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
+ engines: {node: '>=20'}
+
+ whatwg-url@16.0.1:
+ resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@@ -2970,6 +3124,13 @@ packages:
utf-8-validate:
optional: true
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -2986,11 +3147,25 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
- '@babel/code-frame@7.29.0':
+ '@asamuzakjp/css-color@5.1.11':
dependencies:
- '@babel/helper-validator-identifier': 7.28.5
- js-tokens: 4.0.0
- picocolors: 1.1.1
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@asamuzakjp/dom-selector@7.1.1':
+ dependencies:
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@asamuzakjp/nwsapi': 2.3.9
+ bidi-js: 1.0.3
+ css-tree: 3.2.1
+ is-potential-custom-element-name: 1.0.1
+
+ '@asamuzakjp/generational-cache@1.0.1': {}
+
+ '@asamuzakjp/nwsapi@2.3.9': {}
'@babel/code-frame@7.29.7':
dependencies:
@@ -3121,6 +3296,34 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
+ '@bramus/specificity@2.4.2':
+ dependencies:
+ css-tree: 3.2.1
+
+ '@csstools/color-helpers@6.1.0': {}
+
+ '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/color-helpers': 6.1.0
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
+ dependencies:
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)':
+ optionalDependencies:
+ css-tree: 3.2.1
+
+ '@csstools/css-tokenizer@4.0.0': {}
+
'@emnapi/core@1.10.0':
dependencies:
'@emnapi/wasi-threads': 1.2.1
@@ -3238,6 +3441,8 @@ snapshots:
'@eslint/js@8.57.1': {}
+ '@exodus/bytes@1.15.1': {}
+
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -3530,7 +3735,7 @@ snapshots:
'@testing-library/dom@10.4.1':
dependencies:
- '@babel/code-frame': 7.29.0
+ '@babel/code-frame': 7.29.7
'@babel/runtime': 7.29.2
'@types/aria-query': 5.0.4
aria-query: 5.3.0
@@ -3820,7 +4025,7 @@ snapshots:
obug: 2.1.1
std-env: 4.1.0
tinyrainbow: 3.1.0
- vitest: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
+ vitest: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
'@vitest/expect@4.1.6':
dependencies:
@@ -3866,7 +4071,7 @@ snapshots:
sirv: 3.0.2
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
- vitest: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
+ vitest: 4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
'@vitest/utils@4.1.6':
dependencies:
@@ -4036,6 +4241,10 @@ snapshots:
baseline-browser-mapping@2.10.40: {}
+ bidi-js@1.0.3:
+ dependencies:
+ require-from-string: 2.0.2
+
bignumber.js@9.3.1: {}
brace-expansion@1.1.14:
@@ -4124,6 +4333,11 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ css-tree@3.2.1:
+ dependencies:
+ mdn-data: 2.27.1
+ source-map-js: 1.2.1
+
css.escape@1.5.1: {}
csstype@3.2.3: {}
@@ -4170,6 +4384,13 @@ snapshots:
data-uri-to-buffer@4.0.1: {}
+ data-urls@7.0.0:
+ dependencies:
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.4
@@ -4198,6 +4419,8 @@ snapshots:
decimal.js-light@2.5.1: {}
+ decimal.js@10.6.0: {}
+
deep-is@0.1.4: {}
define-data-property@1.1.4:
@@ -4253,6 +4476,8 @@ snapshots:
entities@7.0.1: {}
+ entities@8.0.0: {}
+
es-abstract@1.24.2:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -4399,7 +4624,7 @@ snapshots:
'@typescript-eslint/parser': 8.59.3(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1)
@@ -4419,7 +4644,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -4434,14 +4659,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.59.3(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -4456,7 +4681,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.3
is-core-module: 2.16.2
is-glob: 4.0.3
@@ -4789,6 +5014,12 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ html-encoding-sniffer@6.0.0:
+ dependencies:
+ '@exodus/bytes': 1.15.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
html-escaper@2.0.2: {}
https-proxy-agent@5.0.1:
@@ -4919,6 +5150,8 @@ snapshots:
is-path-inside@3.0.3: {}
+ is-potential-custom-element-name@1.0.1: {}
+
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@@ -5000,6 +5233,32 @@ snapshots:
dependencies:
argparse: 2.0.1
+ jsdom@29.1.1:
+ dependencies:
+ '@asamuzakjp/css-color': 5.1.11
+ '@asamuzakjp/dom-selector': 7.1.1
+ '@bramus/specificity': 2.4.2
+ '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1)
+ '@exodus/bytes': 1.15.1
+ css-tree: 3.2.1
+ data-urls: 7.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 6.0.0
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.5.1
+ parse5: 8.0.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 6.0.1
+ undici: 7.28.0
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 8.0.1
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
@@ -5097,6 +5356,8 @@ snapshots:
lru-cache@10.4.3: {}
+ lru-cache@11.5.1: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -5123,6 +5384,8 @@ snapshots:
math-intrinsics@1.1.0: {}
+ mdn-data@2.27.1: {}
+
mime-db@1.52.0: {}
mime-types@2.1.35:
@@ -5280,6 +5543,10 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse5@8.0.1:
+ dependencies:
+ entities: 8.0.0
+
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
@@ -5430,6 +5697,8 @@ snapshots:
- bare-url
optional: true
+ require-from-string@2.0.2: {}
+
reselect@5.1.1: {}
resolve-from@4.0.0: {}
@@ -5497,6 +5766,10 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
@@ -5692,6 +5965,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ symbol-tree@3.2.4: {}
+
tailwind-merge@3.6.0: {}
tailwindcss@4.3.0: {}
@@ -5713,6 +5988,12 @@ snapshots:
tinyrainbow@3.1.0: {}
+ tldts-core@7.4.4: {}
+
+ tldts@7.4.4:
+ dependencies:
+ tldts-core: 7.4.4
+
to-buffer@1.2.2:
dependencies:
isarray: 2.0.5
@@ -5723,6 +6004,14 @@ snapshots:
totalist@3.0.1: {}
+ tough-cookie@6.0.1:
+ dependencies:
+ tldts: 7.4.4
+
+ tr46@6.0.0:
+ dependencies:
+ punycode: 2.3.1
+
ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -5795,6 +6084,8 @@ snapshots:
undici-types@6.21.0: {}
+ undici@7.28.0: {}
+
unrs-resolver@1.11.1:
dependencies:
napi-postinstall: 0.3.4
@@ -5866,7 +6157,7 @@ snapshots:
jiti: 2.7.0
tsx: 4.21.0
- vitest@4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)):
+ vitest@4.1.6(@types/node@20.19.41)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.6
'@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@20.19.41)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0))
@@ -5893,13 +6184,30 @@ snapshots:
'@vitest/coverage-v8': 4.1.6(vitest@4.1.6)
'@vitest/ui': 4.1.6(vitest@4.1.6)
happy-dom: 20.9.0
+ jsdom: 29.1.1
transitivePeerDependencies:
- msw
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
web-streams-polyfill@3.3.3: {}
+ webidl-conversions@8.0.1: {}
+
whatwg-mimetype@3.0.0: {}
+ whatwg-mimetype@5.0.0: {}
+
+ whatwg-url@16.0.1:
+ dependencies:
+ '@exodus/bytes': 1.15.1
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
+
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@@ -5968,6 +6276,10 @@ snapshots:
ws@8.20.1: {}
+ xml-name-validator@5.0.0: {}
+
+ xmlchars@2.2.0: {}
+
yallist@3.1.1: {}
yocto-queue@0.1.0: {}
diff --git a/src/app/api/marketplace/listings/[id]/route.ts b/src/app/api/marketplace/listings/[id]/route.ts
index 57bee70b..689a1797 100644
--- a/src/app/api/marketplace/listings/[id]/route.ts
+++ b/src/app/api/marketplace/listings/[id]/route.ts
@@ -10,11 +10,26 @@ import { withApiHandler } from '@/lib/backend/withApiHandler';
import type { CancelListingResponse } from '@/types/marketplace';
const MARKETPLACE_LISTING_DETAIL_CORS_POLICY = {
+ GET: { access: 'public' },
DELETE: { access: 'first-party' },
} satisfies CorsRoutePolicy;
export const OPTIONS = createCorsOptionsHandler(MARKETPLACE_LISTING_DETAIL_CORS_POLICY);
+export const GET = withApiHandler(async (req: NextRequest, { params }, correlationId) => {
+ const listingId = params.id;
+ if (!listingId) {
+ throw new ValidationError('Listing ID is required');
+ }
+
+ const listing = await marketplaceService.getPublicListing(listingId);
+ if (!listing) {
+ throw new NotFoundError('Listing', { listingId });
+ }
+
+ return ok({ listing }, undefined, 200, correlationId);
+}, { cors: MARKETPLACE_LISTING_DETAIL_CORS_POLICY, enableETag: true });
+
export const DELETE = withApiHandler(async (req: NextRequest, { params }, correlationId) => {
assertMutationCsrf(req);
@@ -59,5 +74,5 @@ export const DELETE = withApiHandler(async (req: NextRequest, { params }, correl
return ok(response, undefined, 200, correlationId);
}, { cors: MARKETPLACE_LISTING_DETAIL_CORS_POLICY });
-const _405 = methodNotAllowed(['DELETE']);
-export { _405 as GET, _405 as POST, _405 as PUT, _405 as PATCH };
+const _405 = methodNotAllowed(['POST', 'PUT', 'PATCH']);
+export { _405 as POST, _405 as PUT, _405 as PATCH };
diff --git a/src/app/marketplace/[id]/error.tsx b/src/app/marketplace/[id]/error.tsx
new file mode 100644
index 00000000..cddf44c7
--- /dev/null
+++ b/src/app/marketplace/[id]/error.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import Link from 'next/link';
+
+export default function MarketplaceListingError({
+ error,
+ reset,
+}: {
+ error: Error;
+ reset: () => void;
+}) {
+ return (
+
+ Something went wrong
+
+ {error?.message || 'An unexpected error occurred while loading this page.'}
+ Unable to load listing
+
Marketplace
++ A deep-linkable view for marketplace commitments, with seller trust + signals, risk summaries, and purchase flow wiring. +
+