From 2106eaeae21bc80b0a5fd1e0a35ab5b5043fbdff Mon Sep 17 00:00:00 2001 From: Aleksandr Pasevin Date: Sat, 19 Sep 2026 23:01:29 +0100 Subject: [PATCH 1/2] chore(config): keep local UI Kit mode sticky and fix dev:local install --- .pnpmfile.cjs | 47 +++++++++---- .pnpmfile.test.cjs | 141 ++++++++++++++++++++++++++++---------- docs/LOCAL_DEVELOPMENT.md | 14 +++- pnpm-workspace.yaml | 13 +++- 4 files changed, 163 insertions(+), 52 deletions(-) diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index b9ed8cc2..2cd3a856 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -70,8 +70,19 @@ function resolveCacheDir(workspaceRoot, cacheDir) { return resolvedCacheDir; } -function isAnyLocalFamilyEnabled() { - return Object.values(STANDARD_FAMILIES).some((family) => process.env[family.envFlag] === 'true'); +function packedManifestExists(cacheDir, familyKey) { + return fs.existsSync(path.join(cacheDir, `${familyKey}.json`)); +} + +function resolveFamilyActivation(family, familyKey, cacheDir) { + const flag = process.env[family.envFlag]; + if (flag === 'true') { + return { active: true, packedOnly: false }; + } + if (flag === 'false') { + return { active: false, packedOnly: false }; + } + return { active: packedManifestExists(cacheDir, familyKey), packedOnly: true }; } function readProjectConfig(workspaceRoot) { @@ -188,7 +199,7 @@ function readPackedManifest(cacheDir, familyKey) { } } -function rewriteDependencies(pkg, context, cacheDir, familyKey, family) { +function rewriteDependencies(pkg, context, cacheDir, familyKey, family, packedOnly) { const packedPackages = readPackedManifest(cacheDir, familyKey); const workspaceRoot = __dirname; @@ -205,6 +216,10 @@ function rewriteDependencies(pkg, context, cacheDir, familyKey, family) { continue; } + if (packedOnly) { + continue; + } + const absolutePath = resolvePackageDirectory(workspaceRoot, family, npmName, packagePath); pkg[depType][npmName] = `file:${absolutePath}`; context.log(`[local-dev] ${npmName} → ${absolutePath}`); @@ -232,12 +247,10 @@ function allowAdapterPrereleases(pkg) { continue; const m = range.match(/^\^(\d+)\.(\d+)\.(\d+)$/); if (!m) continue; - const maj = Number(m[1]), min = Number(m[2]), pat = Number(m[3]); - const upper = maj > 0 - ? `${maj + 1}.0.0` - : min > 0 - ? `0.${min + 1}.0` - : `0.0.${pat + 1}`; + const maj = Number(m[1]), + min = Number(m[2]), + pat = Number(m[3]); + const upper = maj > 0 ? `${maj + 1}.0.0` : min > 0 ? `0.${min + 1}.0` : `0.0.${pat + 1}`; pkg[depType][name] = `>=${maj}.${min}.${pat}-0 <${upper}`; } } @@ -366,16 +379,24 @@ function readPackage(pkg, context) { stripMetaMaskDependencies(pkg, context); - if (isAnyLocalFamilyEnabled()) { - const workspaceRoot = __dirname; + const workspaceRoot = __dirname; + if (fs.existsSync(path.join(workspaceRoot, CONFIG_FILE))) { const projectConfig = readProjectConfig(workspaceRoot); for (const [familyKey, family] of Object.entries(projectConfig.families)) { - if (process.env[family.envFlag] !== 'true') { + const activation = resolveFamilyActivation(family, familyKey, projectConfig.cacheDir); + if (!activation.active) { continue; } - rewriteDependencies(pkg, context, projectConfig.cacheDir, familyKey, family); + rewriteDependencies( + pkg, + context, + projectConfig.cacheDir, + familyKey, + family, + activation.packedOnly + ); } } diff --git a/.pnpmfile.test.cjs b/.pnpmfile.test.cjs index b3fee3bf..7e9d7a44 100644 --- a/.pnpmfile.test.cjs +++ b/.pnpmfile.test.cjs @@ -122,19 +122,41 @@ function withPackedManifest(familyKey, packages, fn) { } } +function withoutPackedManifest(familyKey, fn) { + const manifestPath = getPackedManifestPath(familyKey); + const previous = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath, 'utf8') : null; + + if (previous !== null) { + fs.unlinkSync(manifestPath); + } + + try { + return fn(); + } finally { + if (previous !== null) { + fs.mkdirSync(path.dirname(manifestPath), { recursive: true }); + fs.writeFileSync(manifestPath, previous); + } + } +} + test('rewrites both UI and adapter dependencies during dev:local flows', () => { const uiRepo = createUiRepo('role-manager-ui'); const adaptersRepo = createAdaptersRepo('role-manager-adapters'); const { hooks } = loadHook(); - const updated = withEnv( - { - LOCAL_UI: 'true', - LOCAL_ADAPTERS: 'true', - LOCAL_UI_PATH: uiRepo, - LOCAL_ADAPTERS_PATH: adaptersRepo, - }, - () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + const updated = withoutPackedManifest('ui', () => + withoutPackedManifest('adapters', () => + withEnv( + { + LOCAL_UI: 'true', + LOCAL_ADAPTERS: 'true', + LOCAL_UI_PATH: uiRepo, + LOCAL_ADAPTERS_PATH: adaptersRepo, + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) + ) ); assert.equal( @@ -155,13 +177,15 @@ test('supports adapter-only overrides with LOCAL_ADAPTERS_PATH', () => { const preferredRepo = createAdaptersRepo('role-manager-adapters-preferred'); const { hooks } = loadHook(); - const updated = withEnv( - { - LOCAL_UI: undefined, - LOCAL_ADAPTERS: 'true', - LOCAL_ADAPTERS_PATH: preferredRepo, - }, - () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + const updated = withoutPackedManifest('adapters', () => + withEnv( + { + LOCAL_UI: undefined, + LOCAL_ADAPTERS: 'true', + LOCAL_ADAPTERS_PATH: preferredRepo, + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) ); assert.equal( @@ -180,12 +204,14 @@ test('throws a clear error when the adapter checkout path is invalid', () => { assert.throws( () => - withEnv( - { - LOCAL_ADAPTERS: 'true', - LOCAL_ADAPTERS_PATH: missingRepo, - }, - () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + withoutPackedManifest('adapters', () => + withEnv( + { + LOCAL_ADAPTERS: 'true', + LOCAL_ADAPTERS_PATH: missingRepo, + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) ), (error) => { assert.match(error.message, /openzeppelin-adapters checkout not found/); @@ -220,6 +246,43 @@ test('prefers packed local tarballs when a manifest is present', () => { ); }); +test('keeps packed local packages active for incidental installs', () => { + const tarballDir = createTemporaryDirectory('role-manager-packed-ui-'); + const tarballPath = path.join(tarballDir, 'openzeppelin-ui-components-1.0.0.tgz'); + fs.writeFileSync(tarballPath, 'stub tarball'); + const { hooks } = loadHook(); + + const updated = withPackedManifest('ui', { '@openzeppelin/ui-components': tarballPath }, () => + withEnv( + { + LOCAL_UI: undefined, + LOCAL_UI_PATH: undefined, + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) + ); + + assert.equal(updated.dependencies['@openzeppelin/ui-components'], `file:${tarballPath}`); +}); + +test('explicit remote mode disables a packed local manifest', () => { + const tarballDir = createTemporaryDirectory('role-manager-packed-ui-remote-'); + const tarballPath = path.join(tarballDir, 'openzeppelin-ui-components-1.0.0.tgz'); + fs.writeFileSync(tarballPath, 'stub tarball'); + const { hooks } = loadHook(); + + const updated = withPackedManifest('ui', { '@openzeppelin/ui-components': tarballPath }, () => + withEnv( + { + LOCAL_UI: 'false', + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) + ); + + assert.equal(updated.dependencies['@openzeppelin/ui-components'], '^1.0.0'); +}); + test('throws a clear error when a configured package directory is missing package.json', () => { const adaptersRepo = createTemporaryDirectory('role-manager-adapters-missing-package-json-'); const vitePackageRoot = path.join(adaptersRepo, 'packages', 'adapters-vite'); @@ -233,12 +296,14 @@ test('throws a clear error when a configured package directory is missing packag assert.throws( () => - withEnv( - { - LOCAL_ADAPTERS: 'true', - LOCAL_ADAPTERS_PATH: adaptersRepo, - }, - () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + withoutPackedManifest('adapters', () => + withEnv( + { + LOCAL_ADAPTERS: 'true', + LOCAL_ADAPTERS_PATH: adaptersRepo, + }, + () => hooks.readPackage(createPackage(), { dir: process.cwd(), log: () => {} }) + ) ), (error) => { assert.match(error.message, /package\.json/); @@ -379,16 +444,18 @@ test('canonicalizes symlinked repository roots before rewriting file dependencie const logs = []; const { hooks } = loadHook(); - const pkg = withEnv( - { - LOCAL_ADAPTERS: 'true', - LOCAL_ADAPTERS_PATH: symlinkRepo, - }, - () => - hooks.readPackage(createPackage(), { - dir: process.cwd(), - log: (message) => logs.push(message), - }) + const pkg = withoutPackedManifest('adapters', () => + withEnv( + { + LOCAL_ADAPTERS: 'true', + LOCAL_ADAPTERS_PATH: symlinkRepo, + }, + () => + hooks.readPackage(createPackage(), { + dir: process.cwd(), + log: (message) => logs.push(message), + }) + ) ); const canonicalPackageRoot = fs.realpathSync(path.join(actualRepo, 'packages', 'adapter-evm')); diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md index 7b47f01a..aeaee96b 100644 --- a/docs/LOCAL_DEVELOPMENT.md +++ b/docs/LOCAL_DEVELOPMENT.md @@ -29,7 +29,7 @@ pnpm dev ## How It Works -The local development setup uses the published `oz-ui-dev` CLI plus a config-driven `.pnpmfile.cjs` hook in this repo. The CLI builds and packs the selected families from your local source checkouts, and the pnpm hook rewrites dependencies to those packed artifacts during install. +The local development setup uses the published `oz-ui-dev` CLI plus a config-driven `.pnpmfile.cjs` hook in this repo. The CLI builds and packs the selected families from your local source checkouts, and the pnpm hook rewrites dependencies to those packed artifacts during install. Packed manifests keep local mode active during later incidental installs; `pnpm dev:npm` removes those manifests and explicitly restores registry packages. ### Directory Structure @@ -65,6 +65,8 @@ pnpm dev:local This command delegates to the published `oz-ui-dev` CLI. It builds the selected package families from your local `openzeppelin-ui` and `openzeppelin-adapters` checkouts, packs them into tarballs under `.packed-packages/local-dev`, and reinstalls Role Manager against those packed artifacts. +The workspace's seven-day `minimumReleaseAge` policy remains active during this reinstall. `@testing-library/dom` and `@tanstack/virtual-core` are additional exclusions because their npm registry metadata omits the `time` field pnpm needs to enforce that policy. Keep these exceptions package-scoped; do not run local development with `PNPM_CONFIG_MINIMUM_RELEASE_AGE=0`, which disables the protection for the entire dependency graph. + ### Switch to Local UI Packages Only ```bash @@ -176,6 +178,16 @@ Using local packages for /path/to/role-manager adapters: 5 tarballs -> /path/to/role-manager/.packed-packages/local-dev/adapters.json ``` +Confirm the app link and package contents directly: + +```bash +readlink apps/role-manager/node_modules/@openzeppelin/ui-components +node -p "require('./apps/role-manager/node_modules/@openzeppelin/ui-components/package.json').version" +grep -c DataTable apps/role-manager/node_modules/@openzeppelin/ui-components/dist/index.mjs +``` + +The link should target `.packed-packages/local-dev`, and the version should match the local checkout. + ### Testing mainnet disable and dev seed helpers The committed `public/app.config.json` enables `mainnet_networks_disabled` for hosted Role Manager deployments. Staging and production Docker workflows pass `VITE_APP_CFG_FEATURE_FLAG_MAINNET_NETWORKS_DISABLED=true` as a build arg; local Docker builds default to `false` (see `Dockerfile`). diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8d95194b..7660fc38 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,6 +9,12 @@ packages: minimumReleaseAge: 10080 minimumReleaseAgeExclude: - '@openzeppelin/*' + # npm's packument for this transitive test dependency omits `time`, so pnpm + # cannot evaluate it against the release-age gate. + - '@testing-library/dom' + # npm's packument for this transitive virtualization dependency omits `time`, + # so pnpm cannot evaluate it against the release-age gate. + - '@tanstack/virtual-core' - 'vitest' - '@vitest/*' - 'viem' @@ -22,7 +28,6 @@ overrides: '@metamask/eth-json-rpc-provider': ^2.2.0 '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 - '@openzeppelin/ui-components': 3.8.3 react-hook-form: 7.79.0 # Security overrides — see PR adding these for full advisory list 'axios@<1.16.0': ^1.16.0 @@ -42,6 +47,12 @@ overrides: 'esbuild@>=0.27.3 <0.28.1': ^0.28.1 'uuid@<11.1.1': ^11.1.1 'js-yaml@>=4.0.0 <4.2.0': ^4.2.0 +packageExtensions: + # adapter-polkadot imports UI components at runtime but omits the dependency + # from its published manifest. + '@openzeppelin/adapter-polkadot@5.0.0': + dependencies: + '@openzeppelin/ui-components': 3.12.0 allowBuilds: '@openzeppelin/adapter-stellar': true blake-hash: true From 07b29296a2856c53f5fab50e9c96ae070d19ba14 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasevin Date: Sat, 19 Sep 2026 23:02:00 +0100 Subject: [PATCH 2/2] feat(role-manager): adopt the UI Kit DataTable for all tables --- apps/role-manager/package.json | 2 +- .../AuthorizedAccounts/AccountsPagination.tsx | 50 -- .../AuthorizedAccounts/AccountsTable.tsx | 263 ++++----- .../components/AuthorizedAccounts/index.ts | 9 - .../Dashboard/PendingTransfersTable.tsx | 220 +++++--- .../components/RoleChanges/ChangesTable.tsx | 200 ++++--- .../RoleChanges/CursorPagination.tsx | 99 ---- .../src/components/RoleChanges/index.ts | 3 - .../components/Roles/RoleIdentifiersTable.tsx | 87 +-- .../src/components/Shared/Pagination.tsx | 182 ------ .../src/components/Shared/index.ts | 2 - .../__tests__/useRoleChangesPageData.test.tsx | 58 +- .../src/hooks/useContractHistory.ts | 3 +- .../src/hooks/useRoleChangesPageData.ts | 57 +- .../src/pages/AuthorizedAccounts.tsx | 60 +- apps/role-manager/src/pages/RoleChanges.tsx | 53 +- apps/role-manager/src/types/index.ts | 2 +- apps/role-manager/src/types/role-changes.ts | 18 +- .../src/utils/__tests__/scroll.test.ts | 24 + apps/role-manager/src/utils/scroll.ts | 6 + pnpm-lock.yaml | 533 ++++++++++++++++-- 21 files changed, 1079 insertions(+), 852 deletions(-) delete mode 100644 apps/role-manager/src/components/AuthorizedAccounts/AccountsPagination.tsx delete mode 100644 apps/role-manager/src/components/RoleChanges/CursorPagination.tsx delete mode 100644 apps/role-manager/src/components/Shared/Pagination.tsx create mode 100644 apps/role-manager/src/utils/__tests__/scroll.test.ts create mode 100644 apps/role-manager/src/utils/scroll.ts diff --git a/apps/role-manager/package.json b/apps/role-manager/package.json index fa2c25a5..2ce12064 100644 --- a/apps/role-manager/package.json +++ b/apps/role-manager/package.json @@ -22,7 +22,7 @@ "@openzeppelin/adapter-evm": "^5.0.0", "@openzeppelin/adapter-polkadot": "^5.0.0", "@openzeppelin/adapter-stellar": "^4.0.1", - "@openzeppelin/ui-components": "^3.8.3", + "@openzeppelin/ui-components": "3.12.0", "@openzeppelin/ui-react": "^3.3.2", "@openzeppelin/ui-renderer": "^3.4.2", "@openzeppelin/ui-storage": "^1.2.5", diff --git a/apps/role-manager/src/components/AuthorizedAccounts/AccountsPagination.tsx b/apps/role-manager/src/components/AuthorizedAccounts/AccountsPagination.tsx deleted file mode 100644 index 0aa9da3c..00000000 --- a/apps/role-manager/src/components/AuthorizedAccounts/AccountsPagination.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * AccountsPagination Component - * Feature: 011-accounts-real-data - * - * Pagination controls for the Authorized Accounts table. - * User Story 4 (Phase 5): Paginated accounts view for contracts with many accounts. - * - * Tasks: T057 - * - * This is a thin wrapper around the shared Pagination component, - * providing "accounts" as the default item label. - */ - -import type { PaginationControls } from '../../hooks/useAuthorizedAccountsPageData'; -import { Pagination } from '../Shared/Pagination'; - -/** - * Props for AccountsPagination component - */ -export interface AccountsPaginationProps { - /** Pagination controls from useAuthorizedAccountsPageData hook */ - pagination: PaginationControls; - /** Additional CSS classes */ - className?: string; -} - -/** - * AccountsPagination - Pagination controls for Authorized Accounts table - * - * Thin wrapper around the shared Pagination component with "accounts" as the item label. - * - * @param pagination - Pagination state and controls from hook - * @param className - Additional CSS classes - */ -export function AccountsPagination({ pagination, className }: AccountsPaginationProps) { - return ( - - ); -} diff --git a/apps/role-manager/src/components/AuthorizedAccounts/AccountsTable.tsx b/apps/role-manager/src/components/AuthorizedAccounts/AccountsTable.tsx index 63a5a369..05749e68 100644 --- a/apps/role-manager/src/components/AuthorizedAccounts/AccountsTable.tsx +++ b/apps/role-manager/src/components/AuthorizedAccounts/AccountsTable.tsx @@ -1,31 +1,23 @@ -/** - * AccountsTable Component - * Feature: 010-authorized-accounts-page - * Updated by: 011-accounts-real-data - * - * Main data table for displaying authorized accounts. - * - * Structure: - * - Card container wrapper - * - HTML table with semantic structure - * - Header row with master checkbox (supports indeterminate state) - * - Column headers: Address, Status, Date Added, Roles, Actions - * - Body maps accounts to AccountRow components - * - * Selection behavior (FR-005): - * - Master checkbox: toggles all rows - * - Indeterminate state when partial selection - */ +/** Authorized accounts table with controlled, identity-keyed selection. */ + +import { Edit } from 'lucide-react'; +import { useMemo } from 'react'; -import { Checkbox } from '@openzeppelin/ui-components'; -import { cn } from '@openzeppelin/ui-utils'; +import { Button, DataTable, type DataTableColumn } from '@openzeppelin/ui-components'; +import type { PaginationControls } from '../../hooks/useAuthorizedAccountsPageData'; +import { useSelectedContract } from '../../hooks/useSelectedContract'; import { - getMasterCheckboxState, + ACCOUNT_STATUS_CONFIG, type AccountAction, type AuthorizedAccountView, } from '../../types/authorized-accounts'; -import { AccountRow } from './AccountRow'; +import { formatDateTime } from '../../utils/date'; +import { scrollMainToTop } from '../../utils/scroll'; +import { ResolvedAddressDisplay } from '../Shared/ResolvedAddressDisplay'; +import { RoleTypeBadge } from '../Shared/RoleTypeBadge'; +import { StatusBadge } from '../Shared/StatusBadge'; +import { YouBadge } from '../Shared/YouBadge'; /** * Props for AccountsTable component @@ -45,28 +37,12 @@ export interface AccountsTableProps { onRoleClick?: (roleId: string) => void; /** Optional content to render when accounts array is empty */ emptyState?: React.ReactNode; + /** Controlled pagination state for the current account page */ + pagination: PaginationControls; + /** App-owned filters rendered inside the kit table frame */ + toolbar: React.ReactNode; } -/** - * Column header definitions for the table - */ -const COLUMNS = [ - { id: 'checkbox', label: '', width: 'w-12' }, - { id: 'address', label: 'Address', width: '' }, - { id: 'status', label: 'Status', width: 'w-24' }, - { id: 'dateAdded', label: 'Date Added', width: 'w-44' }, - { id: 'roles', label: 'Roles', width: 'w-48' }, - { id: 'actions', label: '', width: 'w-32' }, -] as const; - -/** - * AccountsTable - Data table for authorized accounts - * - * Implements: - * - Master checkbox with indeterminate support - * - Row selection via checkbox toggle - * - Action callbacks for row actions - */ export function AccountsTable({ accounts, selectedIds, @@ -75,105 +51,114 @@ export function AccountsTable({ onAction, onRoleClick, emptyState, + pagination, + toolbar, }: AccountsTableProps) { - // Derive master checkbox state - const masterState = getMasterCheckboxState(selectedIds.size, accounts.length); - const isAllSelected = masterState === 'checked'; - const isIndeterminate = masterState === 'indeterminate'; - - /** - * Handles master checkbox toggle - * - If any selected: clear all - * - If none selected: select all - */ - const handleMasterToggle = () => { - if (selectedIds.size > 0) { - // Clear selection - onSelectionChange(new Set()); - } else { - // Select all - onSelectionChange(new Set(accounts.map((a) => a.id))); - } - }; - - /** - * Handles individual row selection toggle - */ - const handleRowToggle = (accountId: string) => { - const newSelectedIds = new Set(selectedIds); - if (newSelectedIds.has(accountId)) { - newSelectedIds.delete(accountId); - } else { - newSelectedIds.add(accountId); - } - onSelectionChange(newSelectedIds); - }; - - /** - * Handles action from AccountRow - */ - const handleAction = (accountId: string, action: AccountAction) => { - onAction(accountId, action); - }; - - return ( -
- - {/* Table Header */} - - - {/* Master Checkbox */} - - - {/* Other column headers */} - {COLUMNS.slice(1).map((column) => ( - - ))} - - + {connectedAddress && + account.address.toLowerCase() === connectedAddress.toLowerCase() && } + + ), + }, + { + id: 'status', + header: 'Status', + headerClassName: 'w-24', + cell: (account) => ( + + {ACCOUNT_STATUS_CONFIG[account.status].label} + + ), + }, + { + id: 'dateAdded', + header: 'Date Added', + headerClassName: 'w-44 whitespace-nowrap', + cellClassName: 'text-sm text-muted-foreground whitespace-nowrap', + cell: (account) => (account.dateAdded ? formatDateTime(account.dateAdded) : '-'), + }, + { + id: 'roles', + header: 'Roles', + headerClassName: 'w-48', + cell: (account) => ( +
+ {account.roles.map((role) => ( + onRoleClick(role.id) : undefined} + /> + ))} +
+ ), + }, + { + id: 'actions', + header: '', + headerLabel: 'Actions', + align: 'end', + headerClassName: 'w-32', + cell: (account) => ( + + ), + }, + ] satisfies readonly DataTableColumn[], + [connectedAddress, onAction, onRoleClick, selectedNetwork?.id] + ); - {/* Table Body */} -
- {accounts.length === 0 && emptyState ? ( - - - - ) : ( - accounts.map((account) => ( - handleRowToggle(account.id)} - onAction={(action) => handleAction(account.id, action)} - onRoleClick={onRoleClick} - /> - )) - )} - -
- + [ + { + id: 'address', + header: 'Address', + cell: (account) => ( +
+ -
- {column.label} -
- {emptyState} -
-
+ return ( + account.id} + toolbar={toolbar} + selection={{ + selectedKeys: selectedIds, + onSelectionChange: (next) => onSelectionChange(new Set(next)), + selectAllLabel: 'Select all accounts', + getCheckboxLabel: (account) => `Select account ${account.address}`, + columnClassName: 'w-12', + }} + emptyState={emptyState} + pagination={{ + kind: 'server', + pageIndex: pagination.currentPage - 1, + pageSize: pagination.pageSize, + totalCount: pagination.totalItems, + onPageChange: (pageIndex) => { + scrollMainToTop(); + pagination.goToPage(pageIndex + 1); + }, + paginationLabel: 'Authorized accounts pagination', + placement: 'inside', + }} + /> ); } diff --git a/apps/role-manager/src/components/AuthorizedAccounts/index.ts b/apps/role-manager/src/components/AuthorizedAccounts/index.ts index 82c53102..ea6a3c55 100644 --- a/apps/role-manager/src/components/AuthorizedAccounts/index.ts +++ b/apps/role-manager/src/components/AuthorizedAccounts/index.ts @@ -21,12 +21,6 @@ export { AccountsLoadingSkeleton } from './AccountsLoadingSkeleton'; export { AccountsEmptyState } from './AccountsEmptyState'; export { AccountsErrorState } from './AccountsErrorState'; -// ============================================================================= -// Components (Feature 011 - Phase 5 Pagination) -// ============================================================================= - -export { AccountsPagination } from './AccountsPagination'; - // ============================================================================= // Components (Phase 4 - User Story 2) // ============================================================================= @@ -43,7 +37,6 @@ export { ManageRolesDialog } from './ManageRolesDialog'; // Re-export shared components for convenience export { OutlineBadge } from '../Shared/OutlineBadge'; -export { Pagination } from '../Shared/Pagination'; export { StatusBadge } from '../Shared/StatusBadge'; // ============================================================================= @@ -61,7 +54,6 @@ export type { AccountActionsMenuProps } from './AccountActionsMenu'; export type { AccountRowProps } from './AccountRow'; export type { AccountsFilterBarProps } from './AccountsFilterBar'; export type { AccountsLoadingSkeletonProps } from './AccountsLoadingSkeleton'; -export type { AccountsPaginationProps } from './AccountsPagination'; export type { AccountsTableProps } from './AccountsTable'; export type { AccountsEmptyStateProps } from './AccountsEmptyState'; export type { AccountsErrorStateProps } from './AccountsErrorState'; @@ -80,7 +72,6 @@ export type { // Re-export shared types export type { OutlineBadgeProps } from '../Shared/OutlineBadge'; -export type { PaginationProps, PaginationState } from '../Shared/Pagination'; export type { StatusBadgeProps, StatusBadgeVariant } from '../Shared/StatusBadge'; export { diff --git a/apps/role-manager/src/components/Dashboard/PendingTransfersTable.tsx b/apps/role-manager/src/components/Dashboard/PendingTransfersTable.tsx index c7192f9a..c1924334 100644 --- a/apps/role-manager/src/components/Dashboard/PendingTransfersTable.tsx +++ b/apps/role-manager/src/components/Dashboard/PendingTransfersTable.tsx @@ -1,22 +1,22 @@ -/** - * PendingTransfersTable Component - * Feature: 015-ownership-transfer (Phase 6.5) - * - * Main data table for displaying pending transfers. - * - * Structure: - * - HTML table with semantic structure - * - Column headers: Type, From, To, Expires, Actions - * - Body maps transfers to PendingTransferRow components - * - Empty state slot for no-data scenarios - * - * Tasks: T048 - */ +/** Pending ownership and role-transfer table. */ + +import { ArrowRight, Clock } from 'lucide-react'; +import { useMemo } from 'react'; -import { cn } from '@openzeppelin/ui-utils'; +import { DataTable, type DataTableColumn } from '@openzeppelin/ui-components'; +import { useBlockTime } from '../../context/useBlockTime'; +import { useSelectedContract } from '../../hooks/useSelectedContract'; import type { PendingTransfer } from '../../types/pending-transfers'; -import { PendingTransferRow } from './PendingTransferRow'; +import { calculateBlockExpiration, formatTimeEstimateDisplay } from '../../utils/block-time'; +import { + formatExpirationTimestamp, + getTimestampTimeRemaining, + hasNoExpiration, + isTimestampBasedExpiration, +} from '../../utils/expiration'; +import { AcceptTransferButton, RoleTypeBadge, StatusBadge } from '../Shared'; +import { ResolvedAddressDisplay } from '../Shared/ResolvedAddressDisplay'; // ============================================================================= // Types @@ -36,76 +36,138 @@ export interface PendingTransfersTableProps { emptyState?: React.ReactNode; } -// ============================================================================= -// Constants -// ============================================================================= - -/** - * Column header definitions for the table - */ -const COLUMNS = [ - { id: 'type', label: 'Type', width: 'w-28' }, - { id: 'from', label: 'From', width: '' }, - { id: 'to', label: 'To', width: '' }, - { id: 'expires', label: 'Expires', width: 'w-32' }, - { id: 'actions', label: '', width: 'w-24' }, -] as const; - -// ============================================================================= -// Component -// ============================================================================= - -/** - * PendingTransfersTable - Data table for pending transfers - * - * Follows the same styling pattern as ChangesTable and AccountsTable. - */ export function PendingTransfersTable({ transfers, currentBlock, onAccept, emptyState, }: PendingTransfersTableProps) { - return ( -
- - {/* Table Header */} - - - {COLUMNS.map((column) => ( - - ))} - - + const { selectedNetwork } = useSelectedContract(); + const { formatBlocksToTime } = useBlockTime(); + const columns = useMemo( + () => + [ + { + id: 'type', + header: 'Type', + headerClassName: 'w-28', + cell: (transfer) => , + }, + { + id: 'from', + header: 'From', + cell: (transfer) => ( + + ), + }, + { + id: 'to', + header: 'To', + cell: (transfer) => ( +
+ + +
+ ), + }, + { + id: 'expires', + header: 'Expires', + headerClassName: 'w-32', + cellClassName: 'text-sm whitespace-nowrap', + cell: (transfer) => { + const noExpiration = hasNoExpiration(transfer.expirationMetadata); + const isTimestamp = isTimestampBasedExpiration(transfer.expirationMetadata); + const expirationEstimate = + !transfer.isExpired && !noExpiration && !isTimestamp + ? calculateBlockExpiration( + transfer.expirationBlock, + currentBlock, + formatBlocksToTime + ) + : null; - {/* Table Body */} -
- {transfers.length === 0 && emptyState ? ( - - - - ) : ( - transfers.map((transfer) => ( - —; + if (isTimestamp) { + const remaining = getTimestampTimeRemaining(transfer.expirationBlock); + return ( +
+ + {formatExpirationTimestamp(transfer.expirationBlock)} + + {transfer.isScheduleReached ? ( + Ready to accept + ) : remaining ? ( + ~{remaining} remaining + ) : null} +
+ ); + } + if (transfer.isExpired) return Expired; + return ( +
+ + {transfer.expirationBlock.toLocaleString()} + + {expirationEstimate?.timeEstimate && ( + + ≈ {formatTimeEstimateDisplay(expirationEstimate.timeEstimate)} + + )} +
+ ); + }, + }, + { + id: 'actions', + header: '', + headerLabel: 'Actions', + align: 'end', + headerClassName: 'w-24', + cell: (transfer) => + transfer.canAccept && !transfer.isExpired ? ( + onAccept?.(transfer)} /> - )) - )} -
-
- {column.label} -
- {emptyState} -
-
+ ) : !transfer.isExpired && transfer.isScheduleReached === false ? ( + + + Awaiting schedule + + ) : null, + }, + ] satisfies readonly DataTableColumn[], + [currentBlock, formatBlocksToTime, onAccept, selectedNetwork?.id] + ); + + return ( + transfer.id} + emptyState={emptyState} + /> ); } diff --git a/apps/role-manager/src/components/RoleChanges/ChangesTable.tsx b/apps/role-manager/src/components/RoleChanges/ChangesTable.tsx index 57af6e5f..c944268d 100644 --- a/apps/role-manager/src/components/RoleChanges/ChangesTable.tsx +++ b/apps/role-manager/src/components/RoleChanges/ChangesTable.tsx @@ -1,21 +1,20 @@ -/** - * ChangesTable Component - * Feature: 012-role-changes-data - * - * Main data table for displaying role change history. - * - * Structure: - * - HTML table with semantic structure - * - Column headers: Timestamp, Action, Role, Account, Transaction - * - Body maps events to ChangeRow components - * - * Tasks: T007 - */ +/** Role change history table with optional cursor-based server pagination. */ + +import { useMemo } from 'react'; -import { cn } from '@openzeppelin/ui-utils'; +import { AddressDisplay, DataTable, type DataTableColumn } from '@openzeppelin/ui-components'; -import type { RoleChangeEventView } from '../../types/role-changes'; -import { ChangeRow } from './ChangeRow'; +import { useSelectedContract } from '../../hooks/useSelectedContract'; +import { + ACTION_TYPE_CONFIG, + type RoleChangeEventView, + type RoleChangesPagination, +} from '../../types/role-changes'; +import { formatDateTime } from '../../utils/date'; +import { scrollMainToTop } from '../../utils/scroll'; +import { ResolvedAddressDisplay } from '../Shared/ResolvedAddressDisplay'; +import { RoleTypeBadge } from '../Shared/RoleTypeBadge'; +import { StatusBadge } from '../Shared/StatusBadge'; /** * Props for ChangesTable component @@ -27,63 +26,122 @@ export interface ChangesTableProps { onRoleClick?: (roleId: string) => void; /** Optional content to render when events array is empty */ emptyState?: React.ReactNode; + /** Cursor controls for server-side pagination */ + pagination: RoleChangesPagination; + /** App-owned filters rendered inside the kit table frame */ + toolbar: React.ReactNode; } -/** - * Column header definitions for the table - */ -const COLUMNS = [ - { id: 'timestamp', label: 'Date/Time', width: 'w-36' }, - { id: 'action', label: 'Action', width: 'w-32' }, - { id: 'role', label: 'Role', width: 'w-40' }, - { id: 'account', label: 'Account', width: '' }, - { id: 'transaction', label: 'Transaction', width: 'w-36' }, -] as const; - -/** - * ChangesTable - Data table for role change history - * - * Implements: - * - Column headers per FR-010 - * - Event rows via ChangeRow component - * - Empty state slot for no-data scenarios - */ -export function ChangesTable({ events, onRoleClick, emptyState }: ChangesTableProps) { +export function ChangesTable({ + events, + onRoleClick, + emptyState, + pagination, + toolbar, +}: ChangesTableProps) { + const { selectedNetwork } = useSelectedContract(); + const columns = useMemo( + () => + [ + { + id: 'timestamp', + header: 'Date/Time', + headerClassName: 'w-36', + cellClassName: 'text-sm text-muted-foreground whitespace-nowrap', + cell: (event) => formatDateTime(event.timestamp), + }, + { + id: 'action', + header: 'Action', + headerClassName: 'w-32', + cell: (event) => { + const actionConfig = ACTION_TYPE_CONFIG[event.action]; + return {actionConfig.label}; + }, + }, + { + id: 'role', + header: 'Role', + headerClassName: 'w-40', + cell: (event) => { + const roleType = + event.action === 'ownership-transfer' || event.action === 'ownership-renounced' + ? 'ownership' + : event.action === 'admin-transfer' || + event.action === 'admin-transfer-canceled' || + event.action === 'admin-renounced' || + event.action === 'admin-delay' + ? 'admin' + : undefined; + return ( + onRoleClick(event.roleId) : undefined} + /> + ); + }, + }, + { + id: 'account', + header: 'Account', + cell: (event) => + !event.account || event.account.trim() === '' ? ( + - + ) : ( + + ), + }, + { + id: 'transaction', + header: 'Transaction', + headerClassName: 'w-36', + cell: (event) => + event.transactionHash ? ( + + ) : ( + - + ), + }, + ] satisfies readonly DataTableColumn[], + [onRoleClick, selectedNetwork?.id] + ); return ( -
- - {/* Table Header */} - - - {COLUMNS.map((column) => ( - - ))} - - - - {/* Table Body */} - - {events.length === 0 && emptyState ? ( - - - - ) : ( - events.map((event) => ( - - )) - )} - -
- {column.label} -
- {emptyState} -
-
+ event.id} + toolbar={toolbar} + emptyState={emptyState} + pagination={{ + ...pagination, + onPageChange: (pageIndex) => { + scrollMainToTop(); + pagination.onPageChange(pageIndex); + }, + placement: 'inside', + hideStatus: true, + paginationLabel: 'Role changes pagination', + }} + /> ); } diff --git a/apps/role-manager/src/components/RoleChanges/CursorPagination.tsx b/apps/role-manager/src/components/RoleChanges/CursorPagination.tsx deleted file mode 100644 index f915cd0b..00000000 --- a/apps/role-manager/src/components/RoleChanges/CursorPagination.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/** - * CursorPagination Component - * Feature: 012-role-changes-data - * - * A cursor-based pagination component for navigating through paginated data - * without page numbers. Designed for APIs that use cursor-based pagination - * (hasNextPage, endCursor) instead of offset-based pagination. - * - * Unlike the standard Pagination component, this component: - * - Does NOT show "Showing X-Y of Z" (no total count available) - * - Does NOT show "Page X of Y" (no page numbers with cursors) - * - Only shows Previous/Next buttons with disabled states - * - * Task: T020 - */ - -import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; - -import { Button } from '@openzeppelin/ui-components'; -import { cn } from '@openzeppelin/ui-utils'; - -import type { CursorPaginationControls } from '../../types/role-changes'; - -/** - * Props for CursorPagination component - */ -export interface CursorPaginationProps { - /** Pagination controls from useRoleChangesPageData hook */ - pagination: CursorPaginationControls; - /** Additional CSS classes */ - className?: string; -} - -/** - * CursorPagination - Cursor-based pagination controls - * - * A simple pagination component for cursor-based APIs that don't expose - * total counts or page numbers. Shows only Previous/Next buttons. - * - * @example - * ```tsx - * const { pagination } = useRoleChangesPageData(); - * - * // Only render when there are pages to navigate - * {(pagination.hasNextPage || pagination.hasPrevPage) && ( - * - * )} - * ``` - */ -export function CursorPagination({ pagination, className }: CursorPaginationProps) { - const { hasNextPage, hasPrevPage, nextPage, prevPage, isLoading } = pagination; - - // Don't render if there's only one page (no navigation needed) - if (!hasNextPage && !hasPrevPage) { - return null; - } - - return ( -
- {/* Loading indicator */} - {isLoading && ( - - - Loading... - - )} - - {/* Previous button */} - - - {/* Next button */} - -
- ); -} diff --git a/apps/role-manager/src/components/RoleChanges/index.ts b/apps/role-manager/src/components/RoleChanges/index.ts index 620c9034..53b568af 100644 --- a/apps/role-manager/src/components/RoleChanges/index.ts +++ b/apps/role-manager/src/components/RoleChanges/index.ts @@ -14,8 +14,5 @@ export { ChangesLoadingSkeleton, type ChangesLoadingSkeletonProps } from './Chan export { ChangesEmptyState, type ChangesEmptyStateProps } from './ChangesEmptyState'; export { ChangesErrorState, type ChangesErrorStateProps } from './ChangesErrorState'; -// Phase 6 (US3): Pagination -export { CursorPagination, type CursorPaginationProps } from './CursorPagination'; - // Phase 7 (US4): Filtering export { ChangesFilterBar, type ChangesFilterBarProps } from './ChangesFilterBar'; diff --git a/apps/role-manager/src/components/Roles/RoleIdentifiersTable.tsx b/apps/role-manager/src/components/Roles/RoleIdentifiersTable.tsx index a9004d94..e3c7bb5a 100644 --- a/apps/role-manager/src/components/Roles/RoleIdentifiersTable.tsx +++ b/apps/role-manager/src/components/Roles/RoleIdentifiersTable.tsx @@ -10,6 +10,9 @@ * - T046: Handles hash identifiers with proper truncation display */ +import { useId } from 'react'; + +import { DataTable, type DataTableColumn } from '@openzeppelin/ui-components'; import { cn, truncateMiddle } from '@openzeppelin/ui-utils'; import type { RoleIdentifier } from '../../types/roles'; @@ -45,6 +48,41 @@ export interface RoleIdentifiersTableProps { className?: string; } +const columns = [ + { + id: 'identifier', + header: 'Role Identifier', + headerClassName: 'px-4 py-3 font-semibold text-foreground', + cellClassName: 'px-4 py-3', + cell: (role) => { + const formatted = formatIdentifier(role.identifier); + return ( + + {formatted.display} + + ); + }, + }, + { + id: 'name', + header: 'Name', + headerClassName: 'px-4 py-3 font-semibold text-foreground', + cellClassName: 'px-4 py-3 font-medium', + cell: (role) => role.name, + }, + { + id: 'description', + header: 'Description', + headerClassName: 'px-4 py-3 font-semibold text-foreground', + cellClassName: 'px-4 py-3 text-muted-foreground', + cell: (role) => + role.description || No description, + }, +] satisfies readonly DataTableColumn[]; + /** * Read-only reference table displaying all available role identifiers. * Data is sourced from useRolesPageData hook's roleIdentifiers array. @@ -54,51 +92,28 @@ export interface RoleIdentifiersTableProps { * */ export function RoleIdentifiersTable({ identifiers, className }: RoleIdentifiersTableProps) { + const headingId = useId(); + return (
{/* Section Header */}
-

Available Role Identifiers

+

+ Available Role Identifiers +

Reference table of all role identifiers available in this contract

- {/* Table */} -
- - - - - - - - - - {identifiers.map((identifier) => { - const formatted = formatIdentifier(identifier.identifier); - return ( - - - - - - ); - })} - -
Role IdentifierNameDescription
- - {formatted.display} - - {identifier.name} - {identifier.description || ( - No description - )} -
-
+ role.identifier} + getRowClassName={() => 'hover:bg-muted/30'} + />
); } diff --git a/apps/role-manager/src/components/Shared/Pagination.tsx b/apps/role-manager/src/components/Shared/Pagination.tsx deleted file mode 100644 index b76a66da..00000000 --- a/apps/role-manager/src/components/Shared/Pagination.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Pagination Component - * - * A reusable pagination control component for navigating through paginated data. - * Designed to be portable to @openzeppelin/ui-components package in the future. - * - * Features: - * - Displays "Showing X-Y of Z {itemLabel}" summary - * - Shows "Page X of Y" indicator - * - Previous/Next navigation buttons with disabled states - * - Responsive layout (stacks on mobile) - * - Accessible with aria-labels - */ - -import { ChevronLeft, ChevronRight } from 'lucide-react'; - -import { Button } from '@openzeppelin/ui-components'; -import { cn } from '@openzeppelin/ui-utils'; - -/** - * Pagination state interface - can be satisfied by any pagination hook - */ -export interface PaginationState { - /** Current page number (1-indexed) */ - currentPage: number; - /** Total number of pages */ - totalPages: number; - /** Total number of items across all pages */ - totalItems: number; - /** Number of items per page */ - pageSize: number; - /** Whether there is a next page available */ - hasNextPage: boolean; - /** Whether there is a previous page available */ - hasPreviousPage: boolean; -} - -/** - * Props for Pagination component - */ -export interface PaginationProps { - /** Current page number (1-indexed) */ - currentPage: number; - /** Total number of pages */ - totalPages: number; - /** Total number of items across all pages */ - totalItems: number; - /** Number of items per page */ - pageSize: number; - /** Whether there is a next page available */ - hasNextPage: boolean; - /** Whether there is a previous page available */ - hasPreviousPage: boolean; - /** Callback when next page is requested */ - onNextPage: () => void; - /** Callback when previous page is requested */ - onPreviousPage: () => void; - /** Label for items being paginated (e.g., "accounts", "roles", "items") */ - itemLabel?: string; - /** Whether to show the "Showing X-Y of Z" summary */ - showSummary?: boolean; - /** Whether to show the "Page X of Y" indicator */ - showPageIndicator?: boolean; - /** Additional CSS classes */ - className?: string; -} - -/** - * Pagination - Reusable pagination controls - * - * A flexible pagination component that can be used with any paginated data source. - * - * @example - * ```tsx - * // With hook pagination state - * const { pagination } = usePaginatedData(); - * - * - * ``` - * - * @example - * ```tsx - * // Minimal usage - * setPage(p => p + 1)} - * onPreviousPage={() => setPage(p => p - 1)} - * /> - * ``` - */ -export function Pagination({ - currentPage, - totalPages, - totalItems, - pageSize, - hasNextPage, - hasPreviousPage, - onNextPage, - onPreviousPage, - itemLabel = 'items', - showSummary = true, - showPageIndicator = true, - className, -}: PaginationProps) { - // Calculate display range - const startItem = totalItems > 0 ? (currentPage - 1) * pageSize + 1 : 0; - const endItem = Math.min(currentPage * pageSize, totalItems); - - return ( -
- {/* Summary text: "Showing X-Y of Z items" */} - {showSummary && ( -
- Showing{' '} - - {startItem}-{endItem} - {' '} - of {totalItems} {itemLabel} -
- )} - - {/* Navigation controls */} -
- {/* Page indicator: "Page X of Y" */} - {showPageIndicator && ( - - Page {currentPage} of{' '} - {totalPages} - - )} - - {/* Previous button */} - - - {/* Next button */} - -
-
- ); -} diff --git a/apps/role-manager/src/components/Shared/index.ts b/apps/role-manager/src/components/Shared/index.ts index 3dd8c6c9..2cf31a91 100644 --- a/apps/role-manager/src/components/Shared/index.ts +++ b/apps/role-manager/src/components/Shared/index.ts @@ -10,7 +10,6 @@ export { OutlineBadge } from './OutlineBadge'; export { PageEmptyState } from './PageEmptyState'; export { RoleTypeBadge } from './RoleTypeBadge'; export { PageHeader } from './PageHeader'; -export { Pagination } from './Pagination'; export { SelectLoadingPlaceholder } from './SelectLoadingPlaceholder'; export { Skeleton } from './Skeleton'; export { StatusBadge } from './StatusBadge'; @@ -67,7 +66,6 @@ export { RoleFilterItem } from './RoleFilterItem'; // Types export type { OutlineBadgeProps } from './OutlineBadge'; -export type { PaginationProps, PaginationState } from './Pagination'; export type { RoleTypeBadgeProps } from './RoleTypeBadge'; export type { SelectLoadingPlaceholderProps } from './SelectLoadingPlaceholder'; export type { SkeletonProps } from './Skeleton'; diff --git a/apps/role-manager/src/hooks/__tests__/useRoleChangesPageData.test.tsx b/apps/role-manager/src/hooks/__tests__/useRoleChangesPageData.test.tsx index b64f3569..5d44e803 100644 --- a/apps/role-manager/src/hooks/__tests__/useRoleChangesPageData.test.tsx +++ b/apps/role-manager/src/hooks/__tests__/useRoleChangesPageData.test.tsx @@ -540,7 +540,7 @@ describe('useRoleChangesPageData', () => { // =========================================================================== describe('pagination', () => { - it('should expose pagination controls', async () => { + it('should expose DataTable server pagination', async () => { const { result } = renderHook(() => useRoleChangesPageData(), { wrapper: createWrapper(), }); @@ -549,15 +549,17 @@ describe('useRoleChangesPageData', () => { expect(result.current.isLoading).toBe(false); }); - expect(result.current.pagination).toHaveProperty('hasNextPage'); - expect(result.current.pagination).toHaveProperty('hasPrevPage'); - expect(result.current.pagination).toHaveProperty('nextPage'); - expect(result.current.pagination).toHaveProperty('prevPage'); - expect(result.current.pagination).toHaveProperty('resetToFirst'); - expect(result.current.pagination).toHaveProperty('isLoading'); + expect(result.current.pagination).toMatchObject({ + kind: 'server', + pageIndex: 0, + pageSize: 20, + hasNextPage: true, + busy: false, + }); + expect(result.current.pagination.onPageChange).toBeTypeOf('function'); }); - it('should have hasPrevPage=false on first page', async () => { + it('should start at page index zero', async () => { const { result } = renderHook(() => useRoleChangesPageData(), { wrapper: createWrapper(), }); @@ -566,10 +568,10 @@ describe('useRoleChangesPageData', () => { expect(result.current.isLoading).toBe(false); }); - expect(result.current.pagination.hasPrevPage).toBe(false); + expect(result.current.pagination.pageIndex).toBe(0); }); - it('should provide nextPage function that can be called', async () => { + it('should advance when DataTable requests the next page', async () => { const { result } = renderHook(() => useRoleChangesPageData(), { wrapper: createWrapper(), }); @@ -578,15 +580,14 @@ describe('useRoleChangesPageData', () => { expect(result.current.isLoading).toBe(false); }); - // nextPage should be callable without errors - expect(() => { - act(() => { - result.current.pagination.nextPage(); - }); - }).not.toThrow(); + act(() => { + result.current.pagination.onPageChange(1); + }); + + expect(result.current.pagination.pageIndex).toBe(1); }); - it('should provide prevPage function that can be called', async () => { + it('should ignore an invalid previous-page request from the first page', async () => { const { result } = renderHook(() => useRoleChangesPageData(), { wrapper: createWrapper(), }); @@ -595,15 +596,14 @@ describe('useRoleChangesPageData', () => { expect(result.current.isLoading).toBe(false); }); - // prevPage should be callable without errors - expect(() => { - act(() => { - result.current.pagination.prevPage(); - }); - }).not.toThrow(); + act(() => { + result.current.pagination.onPageChange(-1); + }); + + expect(result.current.pagination.pageIndex).toBe(0); }); - it('should provide resetToFirst function that resets to initial state', async () => { + it('should return to the previous cursor page', async () => { const { result } = renderHook(() => useRoleChangesPageData(), { wrapper: createWrapper(), }); @@ -612,18 +612,16 @@ describe('useRoleChangesPageData', () => { expect(result.current.isLoading).toBe(false); }); - // Navigate (simulates going forward) act(() => { - result.current.pagination.nextPage(); + result.current.pagination.onPageChange(1); }); + expect(result.current.pagination.pageIndex).toBe(1); - // Reset to first act(() => { - result.current.pagination.resetToFirst(); + result.current.pagination.onPageChange(0); }); - // After reset, should have no previous page - expect(result.current.pagination.hasPrevPage).toBe(false); + expect(result.current.pagination.pageIndex).toBe(0); }); }); diff --git a/apps/role-manager/src/hooks/useContractHistory.ts b/apps/role-manager/src/hooks/useContractHistory.ts index b932b99f..6ab7548b 100644 --- a/apps/role-manager/src/hooks/useContractHistory.ts +++ b/apps/role-manager/src/hooks/useContractHistory.ts @@ -7,7 +7,7 @@ * * Tasks: T004 */ -import { useQuery } from '@tanstack/react-query'; +import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import type { RoleManagerRuntime } from '@/core/runtimeAdapter'; @@ -140,6 +140,7 @@ export function useContractHistory( } }, enabled: isReady && !!contractAddress && isContractRegistered, + placeholderData: keepPreviousData, staleTime: 30_000, // 30 seconds gcTime: 5 * 60 * 1000, // 5 minutes retry: false, diff --git a/apps/role-manager/src/hooks/useRoleChangesPageData.ts b/apps/role-manager/src/hooks/useRoleChangesPageData.ts index 5c9f942c..ccce6b01 100644 --- a/apps/role-manager/src/hooks/useRoleChangesPageData.ts +++ b/apps/role-manager/src/hooks/useRoleChangesPageData.ts @@ -13,11 +13,11 @@ import type { RoleBadgeInfo } from '../types/authorized-accounts'; import { DEFAULT_CURSOR_PAGINATION_STATE, DEFAULT_HISTORY_FILTER_STATE, - type CursorPaginationControls, type CursorPaginationState, type HistoryChangeType, type HistoryFilterState, type HistoryQueryOptions, + type RoleChangesPagination, type UseRoleChangesPageDataReturn, } from '../types/role-changes'; import { createGetAccountUrl, createGetTransactionUrl } from '../utils/explorer-urls'; @@ -299,37 +299,36 @@ export function useRoleChangesPageData(): UseRoleChangesPageDataReturn { // Pagination Controls // ============================================================================= - const pagination: CursorPaginationControls = useMemo( - () => ({ + const pagination: RoleChangesPagination = useMemo(() => { + const pageIndex = paginationState.cursorHistory.length; + + return { + kind: 'server', + pageIndex, + pageSize: DEFAULT_PAGE_SIZE, hasNextPage: pageInfo?.hasNextPage ?? false, - hasPrevPage: paginationState.cursorHistory.length > 0, - nextPage: () => { - if (pageInfo.endCursor) { + busy: isHistoryLoading || isHistoryFetching, + onPageChange: (nextPageIndex) => { + if (nextPageIndex === pageIndex + 1 && pageInfo.endCursor) { setPaginationState((prev) => ({ currentCursor: pageInfo.endCursor, // Always push current cursor to history (even undefined for page 1) - // This ensures hasPrevPage is true after navigating forward + // The history length becomes DataTable's next pageIndex. cursorHistory: [...prev.cursorHistory, prev.currentCursor], })); + return; + } + + if (nextPageIndex === pageIndex - 1 && pageIndex > 0) { + setPaginationState((prev) => { + const cursorHistory = [...prev.cursorHistory]; + const currentCursor = cursorHistory.pop(); + return { currentCursor, cursorHistory }; + }); } }, - prevPage: () => { - setPaginationState((prev) => { - const newHistory = [...prev.cursorHistory]; - const prevCursor = newHistory.pop(); - return { - currentCursor: prevCursor, - cursorHistory: newHistory, - }; - }); - }, - resetToFirst: () => { - setPaginationState(DEFAULT_CURSOR_PAGINATION_STATE); - }, - isLoading: isHistoryLoading || isHistoryFetching, - }), - [paginationState, pageInfo, isHistoryLoading, isHistoryFetching] - ); + }; + }, [paginationState, pageInfo, isHistoryLoading, isHistoryFetching]); // ============================================================================= // Return @@ -345,12 +344,12 @@ export function useRoleChangesPageData(): UseRoleChangesPageDataReturn { setFilters: () => {}, resetFilters: () => {}, pagination: { + kind: 'server', + pageIndex: 0, + pageSize: DEFAULT_PAGE_SIZE, hasNextPage: false, - hasPrevPage: false, - nextPage: () => {}, - prevPage: () => {}, - resetToFirst: () => {}, - isLoading: false, + busy: false, + onPageChange: () => {}, }, hasContractSelected: false, supportsHistory: false, diff --git a/apps/role-manager/src/pages/AuthorizedAccounts.tsx b/apps/role-manager/src/pages/AuthorizedAccounts.tsx index 2c806b59..1eaaeb9c 100644 --- a/apps/role-manager/src/pages/AuthorizedAccounts.tsx +++ b/apps/role-manager/src/pages/AuthorizedAccounts.tsx @@ -25,7 +25,6 @@ import { AccountsErrorState, AccountsFilterBar, AccountsLoadingSkeleton, - AccountsPagination, AccountsTable, ManageRolesDialog, type AccountAction, @@ -70,9 +69,6 @@ export function AuthorizedAccounts() { const trackFilterChanges = useFilterAnalytics('Authorized Accounts', filters); - // T060: Determine if pagination controls should be visible - const showPagination = pagination.totalItems > pagination.pageSize; - // Get contract info for display const { selectedContract } = useSelectedContract(); const contractLabel = useContractDisplayName(selectedContract); @@ -228,37 +224,31 @@ export function AuthorizedAccounts() { } /> - {/* Main content card with filter bar + table */} - - {/* Filter Bar - wired to real availableRoles */} - - - {/* Accounts Table with real data */} - - - - } - /> - - {/* T059/T060: Pagination controls (only shown when totalItems > pageSize) */} - {showPagination && } - + + } + emptyState={ +
+ +
+ } + /> {/* T027: ManageRolesDialog (Feature 014) */} {/* Note: onSuccess callback removed - query invalidation in mutations handles data refresh */} diff --git a/apps/role-manager/src/pages/RoleChanges.tsx b/apps/role-manager/src/pages/RoleChanges.tsx index 117007f8..214babab 100644 --- a/apps/role-manager/src/pages/RoleChanges.tsx +++ b/apps/role-manager/src/pages/RoleChanges.tsx @@ -25,7 +25,6 @@ import { ChangesFilterBar, ChangesLoadingSkeleton, ChangesTable, - CursorPagination, } from '../components/RoleChanges'; import { PageEmptyState } from '../components/Shared/PageEmptyState'; import { PageHeader } from '../components/Shared/PageHeader'; @@ -144,6 +143,14 @@ export function RoleChanges() { // Determine if we're in a loading/fetching state (for showing spinner on refresh button) const isBusy = isLoading || isRefreshing; + const filterBar = ( + + ); // Main content with real data (filter bar always visible when contract supports history) return ( @@ -171,39 +178,29 @@ export function RoleChanges() { } /> - {/* Main content card with filters and table */} - - {/* Filter bar - always visible to maintain search input focus */} - - - {/* Error state */} - {hasError ? ( + {hasError ? ( + + {filterBar} - ) : isLoading ? ( - /* Loading skeleton - only for table area, filter bar stays visible */ + + ) : isLoading ? ( + + {filterBar} - ) : ( - /* Table with data */ - <> - } - /> - {/* Cursor-based pagination (T022 - US3) */} - - - )} - + + ) : ( + } + pagination={pagination} + toolbar={filterBar} + /> + )} ); } diff --git a/apps/role-manager/src/types/index.ts b/apps/role-manager/src/types/index.ts index 000a8dbd..09d85b23 100644 --- a/apps/role-manager/src/types/index.ts +++ b/apps/role-manager/src/types/index.ts @@ -60,7 +60,7 @@ export { // Pagination types type CursorPaginationState, DEFAULT_CURSOR_PAGINATION_STATE, - type CursorPaginationControls, + type RoleChangesPagination, // Hook return types type UseContractHistoryReturn, type UseRoleChangesPageDataReturn, diff --git a/apps/role-manager/src/types/role-changes.ts b/apps/role-manager/src/types/role-changes.ts index d935ba70..84572e4f 100644 --- a/apps/role-manager/src/types/role-changes.ts +++ b/apps/role-manager/src/types/role-changes.ts @@ -9,6 +9,7 @@ * Note: API types (HistoryEntry, PageInfo, etc.) are imported from @openzeppelin/ui-types */ +import type { DataTableServerPagination } from '@openzeppelin/ui-components'; import type { HistoryChangeType, HistoryEntry, @@ -182,20 +183,7 @@ export const DEFAULT_CURSOR_PAGINATION_STATE: CursorPaginationState = { /** * Pagination controls for cursor-based navigation. */ -export interface CursorPaginationControls { - /** Whether there is a next page */ - hasNextPage: boolean; - /** Whether there is a previous page */ - hasPrevPage: boolean; - /** Navigate to next page */ - nextPage: () => void; - /** Navigate to previous page */ - prevPage: () => void; - /** Reset to first page */ - resetToFirst: () => void; - /** Whether pagination is in loading state */ - isLoading: boolean; -} +export type RoleChangesPagination = DataTableServerPagination; // ============================================================================= // Hook Return Types @@ -245,7 +233,7 @@ export interface UseRoleChangesPageDataReturn { // === Pagination === /** Cursor-based pagination controls */ - pagination: CursorPaginationControls; + pagination: RoleChangesPagination; // === Contract State === /** Whether a contract is currently selected */ diff --git a/apps/role-manager/src/utils/__tests__/scroll.test.ts b/apps/role-manager/src/utils/__tests__/scroll.test.ts new file mode 100644 index 00000000..12574a16 --- /dev/null +++ b/apps/role-manager/src/utils/__tests__/scroll.test.ts @@ -0,0 +1,24 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { scrollMainToTop } from '../scroll'; + +describe('scrollMainToTop', () => { + afterEach(() => { + document.body.replaceChildren(); + }); + + it('scrolls the app page viewport to the top without animation', () => { + const main = document.createElement('main'); + const scrollTo = vi.fn(); + main.scrollTo = scrollTo; + document.body.append(main); + + scrollMainToTop(); + + expect(scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'auto' }); + }); + + it('does nothing when the app page viewport is absent', () => { + expect(() => scrollMainToTop()).not.toThrow(); + }); +}); diff --git a/apps/role-manager/src/utils/scroll.ts b/apps/role-manager/src/utils/scroll.ts new file mode 100644 index 00000000..bcb89598 --- /dev/null +++ b/apps/role-manager/src/utils/scroll.ts @@ -0,0 +1,6 @@ +/** + * Scrolls the app-owned page viewport to its top without animation. + */ +export function scrollMainToTop(): void { + document.querySelector('main')?.scrollTo({ top: 0, behavior: 'auto' }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ef6f093..8cddca59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,6 @@ overrides: '@metamask/eth-json-rpc-provider': ^2.2.0 '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 - '@openzeppelin/ui-components': 3.8.3 react-hook-form: 7.79.0 axios@<1.16.0: ^1.16.0 hono@<4.12.25: ^4.12.25 @@ -28,7 +27,9 @@ overrides: uuid@<11.1.1: ^11.1.1 js-yaml@>=4.0.0 <4.2.0: ^4.2.0 -pnpmfileChecksum: sha256-SyYT1Cdnu97fa2SkzVzNBUBNLNxj9O7LgknTEiCzMR4= +packageExtensionsChecksum: sha256-wOahq0aA/8qCc/QsU4oLp9evC14zwKnfahPCcfP/+4A= + +pnpmfileChecksum: sha256-jQ+JTH5409TVggekFXYK8m9T6QbGyJOY4Lb/z2x0UsY= importers: @@ -159,16 +160,16 @@ importers: version: 1.9.5(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(typescript@5.9.3)(utf-8-validate@6.0.6) '@openzeppelin/adapter-evm': specifier: '>=5.0.0-0 <6.0.0' - version: 5.0.0(6a8e06c29018998bc877c78a69cf4541) + version: 5.0.0(d22739b92996c0402db66ec4d04aea0b) '@openzeppelin/adapter-polkadot': specifier: '>=5.0.0-0 <6.0.0' - version: 5.0.0(a55748a6987e7716ac721b4aa750d45d) + version: 5.0.0(602c1e5ca1a5ce40beb1fbe3d43374ed) '@openzeppelin/adapter-stellar': specifier: '>=4.0.1-0 <5.0.0' - version: 4.0.1(@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6) + version: 4.0.1(@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6) '@openzeppelin/ui-components': - specifier: 3.8.3 - version: 3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) + specifier: 3.12.0 + version: 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-react': specifier: ^3.3.2 version: 3.3.2(@babel/runtime@7.29.7)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) @@ -229,7 +230,7 @@ importers: devDependencies: '@openzeppelin/adapters-vite': specifier: '>=12.0.0-0 <13.0.0' - version: 12.0.0(@openzeppelin/adapter-evm@5.0.0(6a8e06c29018998bc877c78a69cf4541))(@openzeppelin/adapter-polkadot@5.0.0(a55748a6987e7716ac721b4aa750d45d))(@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3)) + version: 12.0.0(@openzeppelin/adapter-evm@5.0.0(d22739b92996c0402db66ec4d04aea0b))(@openzeppelin/adapter-polkadot@5.0.0(602c1e5ca1a5ce40beb1fbe3d43374ed))(@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3)) '@tailwindcss/vite': specifier: ^4.3.0 version: 4.3.0(vite@7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3)) @@ -985,16 +986,16 @@ packages: '@metamask/safe-event-emitter@2.0.0': resolution: {integrity: sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==} - '@metamask/safe-event-emitter@3.1.2': - resolution: {integrity: sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==} + '@metamask/safe-event-emitter@3.1.3': + resolution: {integrity: sha512-Pnl74ukgoOkSItBRmZ9nnhdBbRtBI1ZvpeYjKNgxjVzDWa/AtruKZc+7LjfZ0iO8iYqh7Ts3bPs2Gu7M2hSf7g==} engines: {node: '>=12.0.0'} '@metamask/superstruct@3.2.1': resolution: {integrity: sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==} engines: {node: '>=16.0.0'} - '@metamask/superstruct@3.3.0': - resolution: {integrity: sha512-P/MwKoAIHGgMz7gnfIV9jP0wG8f/bCHooy5LPfYWzoyV9FrcHB/9Ai/spIOa6jRmoSr+R6etWvulTakDfwCbrA==} + '@metamask/superstruct@3.4.1': + resolution: {integrity: sha512-caTaaBUcwBGbUNf3r0uT48upX4nECRbKhQ9pPOfW4sIkfcIUUDV4S9DZxq/5fuNPVt5KWpyd5xIIz0sP+iWLlg==} engines: {node: '>=16.0.0'} '@metamask/utils@11.11.0': @@ -1127,7 +1128,7 @@ packages: '@openzeppelin/adapter-evm@5.0.0': resolution: {integrity: sha512-/78k0Ev4MoEYgZHGAelxtaKOgEAwCOoLNXu8L02k+zGowPC2yDIm6dHJWRgeJHJGTdcCxbmg+jGZPaEtIWM21g==} peerDependencies: - '@openzeppelin/ui-components': 3.8.3 + '@openzeppelin/ui-components': ^2.0.0 '@openzeppelin/ui-react': ^2.0.1 '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 @@ -1154,7 +1155,7 @@ packages: '@openzeppelin/adapter-stellar@4.0.1': resolution: {integrity: sha512-fBg96i5rk//35BwBGJFYvf/2OCee+OM3rwkquwXo9bCMRHq3OPaWizH+91R/zBoS934xJ7utXXaHLUhyPVgYSw==} peerDependencies: - '@openzeppelin/ui-components': 3.8.3 + '@openzeppelin/ui-components': ^2.0.0 '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 react: ^19.0.0 @@ -1186,11 +1187,15 @@ packages: resolution: {integrity: sha512-G3Zhg0imaT9Ej1cE9Cq5d4uQvW+DinD2GvRuiPWo3+O9KI8ivBnGJkjEGgK9Ja3/QsUNIfx8Gk5Mdw2sPXjVWA==} engines: {node: '>=22.14.0', npm: use pnpm, pnpm: '>=9', yarn: use pnpm} - '@openzeppelin/ui-components@3.8.3': - resolution: {integrity: sha512-LkRF8b0FLOW48weEj/ILktf+EErGoha/TX6VKhUoeiMmeJcqQ9AP39cd6/rahaJ0ZkwJzHz/0zbR+dMJTj1QBg==} + '@openzeppelin/ui-components@3.12.0': + resolution: {integrity: sha512-pmgd+030zUftHa+6i4rnaAjzMETfpeGyOLHWondhXSe+TP/3U13hheDP2B9qMxAk4/H+3M9vQiymNonWrNJGKA==} peerDependencies: + '@pierre/trees': 1.0.0-beta.6 react: ^19.0.0 react-dom: ^19.0.0 + peerDependenciesMeta: + '@pierre/trees': + optional: true '@openzeppelin/ui-dev-cli@1.3.0': resolution: {integrity: sha512-Q1BldxQ7wE6g+oDZ19USnnPfQlx0G7pLlPBHLNCE05Co7qZRbiC3gPQqHfgC1wCPBtAgd6nB3G4/IjFylVN66g==} @@ -1239,6 +1244,9 @@ packages: '@radix-ui/primitive@1.1.5': resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} + '@radix-ui/react-accordion@1.2.16': resolution: {integrity: sha512-BpZJNmetujnGgUI6OX0jEhEmlA46WPqgub8Rv09Kyquwd0cc1ndMKpiPYCjmBU6KSSRPAMtgLpEoZSG/tdNIWQ==} peerDependencies: @@ -1265,6 +1273,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.11': + resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-checkbox@1.3.7': resolution: {integrity: sha512-JroKHfQBfh+fDuzpPsBC+pESkhuq8ql4hljTguz8MWnS35cISr3d/Jhl9kYrB44FlDtxCArYdDvTx+BSsJ64rQ==} peerDependencies: @@ -1304,6 +1325,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.15': + resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.3': resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} peerDependencies: @@ -1313,6 +1347,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.2.0': resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} peerDependencies: @@ -1322,6 +1365,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.19': resolution: {integrity: sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==} peerDependencies: @@ -1344,6 +1396,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.4': + resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.15': resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} peerDependencies: @@ -1401,6 +1462,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.11': resolution: {integrity: sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==} peerDependencies: @@ -1414,6 +1484,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-label@2.1.15': + resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.20': resolution: {integrity: sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==} peerDependencies: @@ -1466,6 +1549,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.7': resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} peerDependencies: @@ -1479,6 +1575,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.7': resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} peerDependencies: @@ -1505,6 +1614,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.1.16': + resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-radio-group@1.4.3': resolution: {integrity: sha512-WwZFjWV4s3aC1QtR3k04R+oANHtX2q6fgKlc7MCEiDNlnTxCZ3H8k3mHtEgVlOejystwk1WQgarQhNOQZ2bK1g==} peerDependencies: @@ -1518,6 +1640,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-radio-group@1.4.7': + resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.15': resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} peerDependencies: @@ -1531,6 +1666,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.19': + resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.3.3': resolution: {integrity: sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==} peerDependencies: @@ -1553,6 +1701,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-tabs@1.1.17': resolution: {integrity: sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==} peerDependencies: @@ -1601,6 +1758,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.2.3': resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} peerDependencies: @@ -1610,6 +1776,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-effect-event@0.0.3': resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} peerDependencies: @@ -1619,6 +1794,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-is-hydrated@0.1.1': resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} peerDependencies: @@ -1628,6 +1812,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.2': resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} peerDependencies: @@ -1637,6 +1830,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-previous@1.1.2': resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==} peerDependencies: @@ -1664,6 +1866,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.2.7': resolution: {integrity: sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==} peerDependencies: @@ -2632,6 +2843,15 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-virtual@3.14.13': + resolution: {integrity: sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.17.11': + resolution: {integrity: sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -4368,6 +4588,14 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + highlight.js@11.11.2: + resolution: {integrity: sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ==} + engines: {node: '>=12.0.0'} + + highlight.js@11.12.0: + resolution: {integrity: sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==} + engines: {node: '>=12.0.0'} + hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} @@ -4985,6 +5213,9 @@ packages: lossless-json@4.3.0: resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -5558,6 +5789,14 @@ packages: preact-render-to-string: optional: true + preact@10.29.8: + resolution: {integrity: sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==} + peerDependencies: + preact-render-to-string: '>=5' + peerDependenciesMeta: + preact-render-to-string: + optional: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6162,6 +6401,9 @@ packages: tailwind-merge@3.6.0: resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -7143,7 +7385,7 @@ snapshots: eth-json-rpc-filters: 6.0.1 eventemitter3: 5.0.4 keccak: 3.0.4 - preact: 10.29.7 + preact: 10.29.8 sha.js: 2.4.12 transitivePeerDependencies: - preact-render-to-string @@ -7703,7 +7945,7 @@ snapshots: '@metamask/eth-json-rpc-provider@2.3.2': dependencies: '@metamask/json-rpc-engine': 7.3.3 - '@metamask/safe-event-emitter': 3.1.2 + '@metamask/safe-event-emitter': 3.1.3 '@metamask/utils': 8.5.0 transitivePeerDependencies: - supports-color @@ -7711,7 +7953,7 @@ snapshots: '@metamask/json-rpc-engine@7.3.3': dependencies: '@metamask/rpc-errors': 6.4.0 - '@metamask/safe-event-emitter': 3.1.2 + '@metamask/safe-event-emitter': 3.1.3 '@metamask/utils': 8.5.0 transitivePeerDependencies: - supports-color @@ -7732,11 +7974,11 @@ snapshots: '@metamask/safe-event-emitter@2.0.0': {} - '@metamask/safe-event-emitter@3.1.2': {} + '@metamask/safe-event-emitter@3.1.3': {} '@metamask/superstruct@3.2.1': {} - '@metamask/superstruct@3.3.0': {} + '@metamask/superstruct@3.4.1': {} '@metamask/utils@11.11.0': dependencies: @@ -7767,7 +8009,7 @@ snapshots: '@metamask/utils@8.5.0': dependencies: '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.3.0 + '@metamask/superstruct': 3.4.1 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.13 @@ -7781,7 +8023,7 @@ snapshots: '@metamask/utils@9.3.0': dependencies: '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.3.0 + '@metamask/superstruct': 3.4.1 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.13 @@ -7953,10 +8195,10 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@openzeppelin/adapter-evm@5.0.0(6a8e06c29018998bc877c78a69cf4541)': + '@openzeppelin/adapter-evm@5.0.0(d22739b92996c0402db66ec4d04aea0b)': dependencies: '@openzeppelin/relayer-sdk': 1.9.0 - '@openzeppelin/ui-components': 3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) + '@openzeppelin/ui-components': 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-react': 3.3.2(@babel/runtime@7.29.7)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 @@ -7985,9 +8227,10 @@ snapshots: - utf-8-validate - zod - '@openzeppelin/adapter-polkadot@5.0.0(a55748a6987e7716ac721b4aa750d45d)': + '@openzeppelin/adapter-polkadot@5.0.0(602c1e5ca1a5ce40beb1fbe3d43374ed)': dependencies: '@openzeppelin/relayer-sdk': 1.9.0 + '@openzeppelin/ui-components': 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 '@rainbow-me/rainbowkit': 2.2.10(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(viem@2.47.10(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(wagmi@2.19.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.47.10(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6)) @@ -8002,23 +8245,27 @@ snapshots: viem: 2.47.10(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) wagmi: 2.19.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.47.10(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6) transitivePeerDependencies: + - '@babel/runtime' + - '@pierre/trees' - '@tanstack/query-core' - '@types/react' + - '@types/react-dom' - bufferutil - debug - immer - preact-render-to-string - supports-color + - tailwindcss - typescript - use-sync-external-store - utf-8-validate - zod - '@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6)': + '@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@creit.tech/stellar-wallets-kit': 1.9.5(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(typescript@5.9.3)(utf-8-validate@6.0.6) '@openzeppelin/relayer-sdk': 1.9.0 - '@openzeppelin/ui-components': 3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) + '@openzeppelin/ui-components': 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 '@stellar/stellar-sdk': 14.6.1 @@ -8040,13 +8287,13 @@ snapshots: - typescript - utf-8-validate - '@openzeppelin/adapters-vite@12.0.0(@openzeppelin/adapter-evm@5.0.0(6a8e06c29018998bc877c78a69cf4541))(@openzeppelin/adapter-polkadot@5.0.0(a55748a6987e7716ac721b4aa750d45d))(@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3))': + '@openzeppelin/adapters-vite@12.0.0(@openzeppelin/adapter-evm@5.0.0(d22739b92996c0402db66ec4d04aea0b))(@openzeppelin/adapter-polkadot@5.0.0(602c1e5ca1a5ce40beb1fbe3d43374ed))(@openzeppelin/adapter-stellar@4.0.1(@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: vite: 7.3.5(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.3) optionalDependencies: - '@openzeppelin/adapter-evm': 5.0.0(6a8e06c29018998bc877c78a69cf4541) - '@openzeppelin/adapter-polkadot': 5.0.0(a55748a6987e7716ac721b4aa750d45d) - '@openzeppelin/adapter-stellar': 4.0.1(@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6) + '@openzeppelin/adapter-evm': 5.0.0(d22739b92996c0402db66ec4d04aea0b) + '@openzeppelin/adapter-polkadot': 5.0.0(602c1e5ca1a5ce40beb1fbe3d43374ed) + '@openzeppelin/adapter-stellar': 4.0.1(@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3))(@openzeppelin/ui-types@3.5.2)(@openzeppelin/ui-utils@4.0.1)(@stellar/stellar-base@14.1.0)(bufferutil@4.1.0)(near-api-js@5.1.1)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@6.0.6) '@openzeppelin/relayer-sdk@1.9.0': dependencies: @@ -8055,7 +8302,7 @@ snapshots: - debug - supports-color - '@openzeppelin/ui-components@3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3)': + '@openzeppelin/ui-components@3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3)': dependencies: '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 @@ -8073,12 +8320,15 @@ snapshots: '@radix-ui/react-tabs': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-toast': 1.2.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-tooltip': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-virtual': 3.14.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@uiw/react-textarea-code-editor': 3.1.1(@babel/runtime@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@web3icons/react': 4.1.17(react@19.2.4)(typescript@5.9.3) class-variance-authority: 0.7.1 clsx: 2.1.1 date-fns: 4.4.0 + highlight.js: 11.12.0 lodash: 4.18.1 + lowlight: 3.3.0 lucide-react: 0.510.0(react@19.2.4) next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 @@ -8104,7 +8354,7 @@ snapshots: '@openzeppelin/ui-react@3.3.2(@babel/runtime@7.29.7)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3)': dependencies: - '@openzeppelin/ui-components': 3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) + '@openzeppelin/ui-components': 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 '@tanstack/react-query': 5.90.21(react@19.2.4) @@ -8113,6 +8363,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: - '@babel/runtime' + - '@pierre/trees' - '@types/react' - '@types/react-dom' - tailwindcss @@ -8121,23 +8372,24 @@ snapshots: '@openzeppelin/ui-renderer@3.4.2(@babel/runtime@7.29.7)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-hook-form@7.79.0(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3)': dependencies: '@hookform/resolvers': 4.1.3(react-hook-form@7.79.0(react@19.2.4)) - '@openzeppelin/ui-components': 3.8.3(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) + '@openzeppelin/ui-components': 3.12.0(@babel/runtime@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-react': 3.3.2(@babel/runtime@7.29.7)(@tanstack/react-query@5.90.21(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.2)(typescript@5.9.3) '@openzeppelin/ui-types': 3.5.2 '@openzeppelin/ui-utils': 4.0.1 - '@radix-ui/react-checkbox': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-label': 2.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-progress': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-radio-group': 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: 0.7.1 clsx: 2.1.1 lucide-react: 0.503.0(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) react-hook-form: 7.79.0(react@19.2.4) - tailwind-merge: 3.6.0 + tailwind-merge: 3.7.0 transitivePeerDependencies: - '@babel/runtime' + - '@pierre/trees' - '@tanstack/react-query' - '@types/react' - '@types/react-dom' @@ -8174,6 +8426,8 @@ snapshots: '@radix-ui/primitive@1.1.5': {} + '@radix-ui/primitive@1.1.7': {} + '@radix-ui/react-accordion@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8200,6 +8454,21 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-checkbox@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8244,18 +8513,42 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-context@1.2.0(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-context@1.2.2(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-dialog@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8284,6 +8577,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-direction@1.1.4(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8336,6 +8635,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-id@1.1.4(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-label@2.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -8345,6 +8651,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-menu@2.1.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8422,6 +8737,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-presence@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.4) @@ -8431,6 +8755,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-slot': 1.3.0(@types/react@19.2.14)(react@19.2.4) @@ -8450,6 +8783,16 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-radio-group@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8468,6 +8811,23 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-roving-focus@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8487,6 +8847,25 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-select@2.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.2 @@ -8524,6 +8903,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-slot@1.3.3(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-tabs@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -8586,6 +8972,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.14)(react@19.2.4)': dependencies: '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.14)(react@19.2.4) @@ -8594,6 +8986,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.14)(react@19.2.4)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.4) @@ -8601,18 +9002,37 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-previous@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 @@ -8633,6 +9053,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-visually-hidden@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -9544,6 +9971,14 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.4 + '@tanstack/react-virtual@3.14.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/virtual-core': 3.17.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@tanstack/virtual-core@3.17.11': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 @@ -11121,7 +11556,7 @@ snapshots: eth-block-tracker@7.1.0: dependencies: '@metamask/eth-json-rpc-provider': 2.3.2 - '@metamask/safe-event-emitter': 3.1.2 + '@metamask/safe-event-emitter': 3.1.3 '@metamask/utils': 5.0.2 json-rpc-random-id: 1.0.1 pify: 3.0.0 @@ -11130,7 +11565,7 @@ snapshots: eth-json-rpc-filters@6.0.1: dependencies: - '@metamask/safe-event-emitter': 3.1.2 + '@metamask/safe-event-emitter': 3.1.3 async-mutex: 0.2.6 eth-query: 2.1.2 json-rpc-engine: 6.1.0 @@ -11554,6 +11989,10 @@ snapshots: property-information: 7.2.0 space-separated-tokens: 2.0.2 + highlight.js@11.11.2: {} + + highlight.js@11.12.0: {} + hmac-drbg@1.0.1: dependencies: hash.js: 1.1.7 @@ -12151,6 +12590,12 @@ snapshots: lossless-json@4.3.0: {} + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + highlight.js: 11.11.2 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -12706,6 +13151,8 @@ snapshots: preact@10.29.7: {} + preact@10.29.8: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: @@ -13346,6 +13793,8 @@ snapshots: tailwind-merge@3.6.0: {} + tailwind-merge@3.7.0: {} + tailwindcss-animate@1.0.7(tailwindcss@4.2.2): dependencies: tailwindcss: 4.2.2