Skip to content

Conversation

@jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Jan 14, 2026

PR-Codex overview

This PR focuses on refactoring components to use Tailwind CSS for styling, replacing styled-components, and improving the overall code structure by removing unused files and optimizing imports. It also updates the handling of button events and enhances utility functions.

Detailed summary

  • Deleted multiple unused files in web/src/styles and web/src/components.
  • Replaced styled-components with Tailwind CSS classes in various components.
  • Updated button props from onClick to onPress for better consistency.
  • Refactored layout components to use Tailwind CSS for styling.
  • Introduced utility functions for URL validation.
  • Updated the subgraph/mappings/escrow.ts to improve event handling.
  • Removed unnecessary imports and streamlined component structures.

The following files were skipped due to too many changes: web/src/pages/MyTransactions/TransactionsFetcher.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx, web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/TimeOutButton.tsx, web/src/components/FileViewer/index.tsx, web/package.json, web/src/layout/Header/navbar/Menu/Settings/Notifications/index.tsx, web/src/pages/NewTransaction/Terms/Payment/TokenTransaction/TokenAndAmount/index.tsx, web/src/pages/NewTransaction/EscrowDetails/TypeOfEscrow/EscrowOptions/GeneralEscrow.tsx, web/src/layout/Header/MobileHeader.tsx, web/src/pages/NewTransaction/Terms/Deadline/index.tsx, web/src/pages/NewTransaction/EscrowDetails/TypeOfEscrow/EscrowOptions/CryptoSwap.tsx, web/src/pages/MyTransactions/Modal/RaiseDisputeModal/FeeRequired.tsx, web/src/components/ExternalLinkWarning.tsx, web/src/pages/MyTransactions/TransactionDetails/index.tsx, web/src/layout/index.tsx, web/src/pages/MyTransactions/index.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/index.tsx, web/src/pages/NewTransaction/EscrowDetails/TypeOfEscrow/Info.tsx, web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/FormContact.tsx, web/src/pages/NewTransaction/Terms/Payment/DestinationAddress.tsx, web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/index.tsx, web/src/layout/Header/index.tsx, web/src/components/LightButton.tsx, web/src/layout/Header/navbar/Product.tsx, web/src/pages/NewTransaction/Terms/Notifications/EmailField.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/MaxBalance.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx, web/src/components/SimpleToggleButton.tsx, web/src/pages/AttachmentDisplay/Header.tsx, web/src/layout/Footer/index.tsx, web/src/pages/AttachmentDisplay/index.tsx, web/src/components/TransactionsDisplay/Filters.tsx, web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/DropdownButton.tsx, web/src/layout/Header/navbar/Menu/index.tsx, web/src/layout/Header/navbar/Explore.tsx, web/src/components/PreviewCard/index.tsx, web/src/layout/Header/navbar/DappList.tsx, web/src/layout/Header/navbar/Menu/Help.tsx, web/src/components/MarkdownEditor.tsx, web/src/components/PreviewCard/Header.tsx, web/src/layout/Header/DesktopHeader.tsx, web/src/components/TransactionsDisplay/TransactionsGrid.tsx, web/src/pages/NewTransaction/Terms/Deliverable/index.tsx, web/src/layout/Header/navbar/index.tsx, web/src/pages/NewTransaction/index.tsx, web/src/components/TransactionInfo/Field.tsx, contracts/src/interfaces/IEscrow.sol, web/src/layout/Header/navbar/Menu/Settings/index.tsx, web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx, web/src/components/TransactionCard/index.tsx, web/src/layout/Header/navbar/Menu/Settings/General.tsx, web/src/components/TransactionsDisplay/Search.tsx, web/src/components/MarkdownRenderer.tsx, web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/EmailVerificationInfo.tsx, web/src/pages/NewTransaction/NavigationButtons/DepositPaymentButton.tsx, web/src/styles/markdownRendererStyles.css, web/src/components/ConnectWallet/AccountDisplay.tsx, web/src/pages/NewTransaction/Terms/Payment/BuyerAddress.tsx, web/src/pages/Settings/EmailConfirmation/index.tsx, web/src/hooks/useEscrowTimelineItems.tsx, web/src/components/TransactionInfo/index.tsx, web/src/components/PreviewCard/Terms/Description.tsx, web/src/components/TransactionCard/StatusBanner.tsx, web/src/global.css, web/src/styles/mdxEditorStyles.css, yarn.lock

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Markdown editor for rich text input in contract terms.
    • Added external link confirmation warnings for safer navigation.
    • Improved theme system with CSS variable-based styling.
  • Dependencies

    • Updated UI component library to latest version with new theme support.
    • Migrated styling infrastructure for improved performance and maintainability.
  • Bug Fixes

    • Enhanced URL validation and duplicate token detection.

✏️ Tip: You can customize this high-level summary in your review settings.

gap-editor and others added 30 commits August 12, 2025 00:52
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
also run linter
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Walkthrough

This pull request performs a major styling system migration from styled-components to Tailwind CSS with utility classes across the web frontend. It removes styled-components dependency, adds contract interface updates for dispute-to-transaction mapping, introduces new Markdown editing and rendering components, and updates the subgraph to use the new contract getter.

Changes

Cohort / File(s) Summary
Contract Interface Updates
contracts/src/EscrowUniversal.sol, contracts/src/EscrowView.sol, contracts/src/interfaces/IEscrow.sol
Updated imports to use named types from Types.sol; added two new public view functions transactions() and disputeIDtoTransactionID() to IEscrow interface for querying transaction details and mapping disputes to transactions.
Global App Configuration
web/global.d.ts, web/package.json, web/app.tsx, web/global.css
Removed styled-components module augmentation; replaced StyledComponentsProvider with ThemeProvider; added global CSS with design tokens, responsive spacing, and base element styles; updated dependencies (removed @types/styled-components and styled-components, added tailwindcss, @mdxeditor/editor, clsx, tailwind-merge, @kleros/ui-components-library upgraded to v3.6.0).
Theme & Context Management
web/src/context/ThemeProvider.tsx, web/src/context/StyledComponentsProvider.tsx (deleted), web/src/context/Web3Provider.tsx
Created new ThemeProvider using localStorage and DOM class toggling for dark mode; removed styled-components-based theme provider; replaced theme color references with hardcoded hex values.
Markdown Components (New)
web/src/components/MarkdownEditor.tsx, web/src/components/MarkdownRenderer.tsx, web/src/components/ExternalLinkWarning.tsx, web/src/styles/markdownRendererStyles.css, web/src/styles/mdxEditorStyles.css
Added comprehensive Markdown editor with MDX support and toolbar; added Markdown renderer with external link interception and sanitization; added external link confirmation modal; added corresponding CSS stylesheets.
Core Component Styling Migration
web/src/components/Overlay.tsx, web/src/components/InfoCard.tsx, web/src/components/Loader.tsx, web/src/components/SimpleToggleButton.tsx, web/src/components/LightButton.tsx, web/src/components/EnsureAuth.tsx
Replaced styled-components with plain React components using CSS utility classes; updated Button prop names (disabled→isDisabled, onClick→onPress) consistently across all Button usages.
Removed Components
web/src/components/ExternalLink.tsx, web/src/components/StyledSkeleton.tsx, web/src/components/StyledIcons/*.tsx, web/src/components/WithHelpTooltip.tsx (deleted)
Deleted styled-based icon components and utility components no longer needed after migration; replaced ExternalLink with native anchor elements.
Header & Navigation
web/src/layout/Header/index.tsx, web/src/layout/Header/Logo.tsx, web/src/layout/Header/DesktopHeader.tsx, web/src/layout/Header/MobileHeader.tsx, web/src/layout/Header/navbar/* (multiple files)
Migrated header layout from styled-components to utility classes; updated button event props throughout navbar; replaced themed styling with CSS variables and classNames; updated breakpoint constant from BREAKPOINT_LANDSCAPE to LG_BREAKPOINT.
Transaction Components
web/src/components/TransactionCard/index.tsx, web/src/components/TransactionInfo/Field.tsx, web/src/components/TransactionCard/StatusBanner.tsx, web/src/components/TransactionsDisplay/*
Replaced styled wrappers with Card/div components using classNames; updated Field/value prop types; refactored status banner styling to CSS variable-driven approach; updated pagination and filter components.
PreviewCard & Terms
web/src/components/PreviewCard/index.tsx, web/src/components/PreviewCard/Header.tsx, web/src/components/PreviewCard/Terms/Description.tsx, web/src/components/PreviewCard/Terms/index.tsx (and others)
Removed StyledCard and Divider styled components; replaced with Card component and utility classes; updated Header interface to include optional escrowTitle; replaced Description rendering with MarkdownRenderer.
Modals & Dialogs
web/src/pages/MyTransactions/Modal/StyledModal.tsx, web/src/pages/MyTransactions/Modal/PaymentReleased/*, web/src/pages/MyTransactions/Modal/RaiseDisputeModal/*, web/src/pages/MyTransactions/Modal/ProposeSettlementModal/*
Converted StyledModal from styled-component to forwardRef functional component; removed Header/Description wrapper components in favor of inline markup; removed CloseButton/VerifiedLogo components; updated modal structure throughout.
Payment & Token Components
web/src/pages/NewTransaction/Terms/Payment/*/, web/src/pages/NewTransaction/EscrowDetails/TypeOfEscrow/*
Replaced styled Field/Container components with TextField/BigNumberField from UI library; updated form handlers to accept values directly instead of events; removed multiple AmountField/Header/Token wrapper components; migrated EscrowOptions styling to Card+classNames.
Button & Handler Updates
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/*, web/src/pages/MyTransactions/TransactionDetails/WasItFulfilled/Buttons/*
Updated all Button prop usages: disabled→isDisabled, onClick→onPress; refactored button wrappers to use plain divs with Tailwind classes; replaced SkeletonButton with react-loading-skeleton.
Style Utilities & Hooks
web/src/styles/themes.ts (deleted), web/src/styles/global-style.ts (deleted), web/src/styles/landscapeStyle.ts (deleted), web/src/styles/commonStyles.ts (deleted), web/src/styles/customScrollbar.ts (deleted), web/src/styles/breakpoints.ts
Removed all styled-components theme definitions and CSS-in-JS helpers; added LG_BREAKPOINT constant; moved all styling to global.css and component-level utility classes.
Context & Hook Updates
web/src/hooks/useToggleThemeContext.tsx, web/src/hooks/useEscrowTimelineItems.tsx, web/src/hooks/useIsDesktop.tsx, web/src/hooks/useFilteredTokens.ts
Renamed useToggleTheme→useTheme; updated timeline variant handling from theme object to CSS variables; updated breakpoint constant usage; improved token deduplication logic.
Utility Functions (New)
web/src/utils/index.ts, web/src/utils/linkUtils.ts, web/src/utils/urlValidation.ts
Added cn() utility for Tailwind class merging; added isExternalLink() for external URL detection; added isValidUrl() for URL validation with protocol/MIME allowlisting.
Subgraph Updates
subgraph/package.json, subgraph/src/escrow.ts, subgraph/subgraph.yaml
Updated escrow mapping to call contract.disputeIDtoTransactionID() for dispute→transaction resolution; changed mapping file path from ./mappings to ./src; added Goldsky deployment scripts and @goldskycom/cli dependency.
Page & Layout Components
web/src/pages/MyTransactions/*, web/src/pages/NewTransaction/*, web/src/pages/Settings/*, web/src/layout/Footer/index.tsx, web/src/layout/index.tsx
Migrated all page-level containers and layouts from styled-components to utility classes; removed responsive style helpers; updated all file upload, form, and modal-related components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

Possibly related PRs

Suggested labels

Type: Maintenance :construction:, Package: Web, Package: Contracts, Type: UX

Suggested reviewers

  • kemuru
  • jaybuidl
  • tractorss

Poem

🐰 From styled components to Tailwind's embrace,
The web frontend springs forth with a faster pace,
Markdown editors flourish, disputes map true,
With utility classes painting a CSS anew!
A major refactor, consistent and bright,
Making the escrow shine with responsive delight ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Deploy to Production' is vague and generic, providing no meaningful information about the specific changes in this large refactoring PR. Consider using a more descriptive title that reflects the main change, such as 'Refactor UI: Replace styled-components with Tailwind CSS' or 'Replace styled-components with Tailwind CSS and update component structure.'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
subgraph/src/escrow.ts (1)

243-260: Consider using try_disputeIDtoTransactionID for resilience.

Direct contract calls in subgraph mappings can cause indexing to halt if the call reverts or fails. Using the auto-generated try_ method provides safer error handling.

♻️ Suggested refactor using try_ pattern
 export function handleDisputeRequest(event: DisputeRequestEvent): void {
   let disputeID = event.params._arbitratorDisputeID;

   // Get the transactionID by calling the contract
   let contract = EscrowUniversal.bind(event.address);
-  let transactionID = contract.disputeIDtoTransactionID(disputeID).toString();
+  let transactionIDResult = contract.try_disputeIDtoTransactionID(disputeID);
+  if (transactionIDResult.reverted) {
+    return;
+  }
+  let transactionID = transactionIDResult.value.toString();

   let disputeRequest = new DisputeRequest(disputeID.toString());

   let escrow = Escrow.load(transactionID);
   if (!escrow) {
     return;
   }

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdecf3e and de89ae4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • subgraph/package.json
  • subgraph/src/escrow.ts
  • subgraph/src/utils.ts
  • subgraph/subgraph.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Dependabot
  • GitHub Check: Redirect rules - kleros-escrow-v2
  • GitHub Check: Redirect rules - kleros-escrow-v2
  • GitHub Check: Header rules - kleros-escrow-v2
  • GitHub Check: Header rules - kleros-escrow-v2
  • GitHub Check: Pages changed - kleros-escrow-v2
  • GitHub Check: Pages changed - kleros-escrow-v2
🔇 Additional comments (5)
subgraph/src/escrow.ts (1)

22-23: LGTM!

The import of EscrowUniversal contract binding is correctly added to enable the disputeIDtoTransactionID lookup in the handler.

subgraph/subgraph.yaml (1)

16-16: LGTM!

The mapping file path correctly points to the new location ./src/escrow.ts, which aligns with the project structure.

subgraph/package.json (3)

3-3: LGTM!

Patch version bump is appropriate for adding deployment tooling support.


16-18: Verify naming consistency across deployment platforms.

The Goldsky scripts follow a consistent pattern with existing graph deploy scripts. However, I noticed a naming difference for mainnet:

  • Line 15: graph deploy escrow-v2-neo
  • Line 18: goldsky subgraph deploy escrow-v2-mainnet

If this is intentional (different naming conventions per platform), this is fine. Otherwise, consider aligning the names to avoid confusion.


33-33: @goldskycom/cli version 13.1.1 is current and secure.

The version specified is the latest available on npm (released ~1 month ago). The caret constraint is appropriate for this CLI tool, allowing safe minor/patch updates within the 13.x range. The package is actively maintained with multiple maintainers and no security vulnerabilities identified.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (27)
web/src/hooks/useFilteredTokens.ts (1)

8-8: Type mismatch: setTokens should accept an array.

The parameter type is IToken but line 43 passes an array (uniqueTokens). This should be IToken[].

Proposed fix
-  setTokens: (tokens: IToken) => void,
+  setTokens: (tokens: IToken[]) => void,
web/src/layout/Header/navbar/Menu/Help.tsx (1)

43-46: Fix containerRef typing (likely TS incompatibility with ref + useClickAway).
useRef(null) produces a ref whose current is typed as only null, which typically won’t type-check as a div ref and may not satisfy react-use’s useClickAway typing.

Proposed fix
-const Help: React.FC<IHelp> = ({ toggleIsHelpOpen }) => {
-  const containerRef = useRef(null);
+const Help: React.FC<IHelp> = ({ toggleIsHelpOpen }) => {
+  const containerRef = useRef<HTMLDivElement | null>(null);
   useClickAway(containerRef, () => toggleIsHelpOpen());

Also applies to: 49-51

web/src/pages/NewTransaction/NavigationButtons/DepositPaymentButton.tsx (1)

59-80: insufficientBalance compares different units (ETH string vs wei bigint)—this can allow deposits with insufficient funds.
Right now sendingQuantity (parsed later via parseEther/parseUnits) is compared against nativeBalance.value / tokenBalance raw values via parseFloat(...), which is both unit-mismatched and precision-unsafe.

Proposed fix (compare bigint-in-base-units)
   const insufficientBalance = useMemo(() => {
     if (isUndefined(sendingQuantity)) return true;

     if (isNativeTransaction) {
-      return nativeBalance ? parseFloat(sendingQuantity) > parseFloat(nativeBalance.value.toString()) : true;
+      return nativeBalance?.value ? transactionValue > nativeBalance.value : true;
     }

-    return isUndefined(tokenBalance) ? true : parseFloat(sendingQuantity) > parseFloat(tokenBalance.toString());
-  }, [sendingQuantity, tokenBalance, nativeBalance, isNativeTransaction]);
+    return isUndefined(tokenBalance) ? true : transactionValue > tokenBalance;
+  }, [sendingQuantity, tokenBalance, nativeBalance, isNativeTransaction, transactionValue]);

(Separately: parseUnits(sendingQuantity, 18) assumes 18 decimals for all ERC20s—verify sendingToken always uses 18 or use the token’s actual decimals.)

web/src/components/PreviewCard/EscrowTimeline/index.tsx (1)

6-18: Add feeTimeout and settlementTimeout to the IEscrowTimeline interface.

The EscrowTimeline component destructures feeTimeout and settlementTimeout (lines 32-33) and passes them to useEscrowTimelineItems (lines 47-48), but the IEscrowTimeline interface (lines 6-18) does not declare these props. This causes a TypeScript type mismatch. Both are required parameters expected by the hook.

Proposed fix
 interface IEscrowTimeline {
   isPreview: boolean;
   transactionCreationTimestamp: number;
   status: boolean;
   assetSymbol: string;
   buyerAddress: string;
   sellerAddress: string;
   payments: Payment[];
   settlementProposals: SettlementProposal[];
   hasToPayFees: HasToPayFee[];
   disputeRequest: DisputeRequest;
   resolvedEvents: TransactionResolved[];
+  feeTimeout: number;
+  settlementTimeout: number;
 }
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/RaiseDisputeButton.tsx (2)

68-100: Guard *.request and always reset isSending (success currently leaves it true). This can throw if config is unset and can also leave the button permanently disabled if the component doesn’t unmount after a successful submit.

Proposed diff
-  const handleRaiseDispute = () => {
-    if (isBuyer && !isUndefined(payArbitrationFeeByBuyer)) {
-      setIsSending(true);
-      wrapWithToast(async () => await payArbitrationFeeByBuyer(payArbitrationFeeByBuyerConfig.request), publicClient)
-        .then((wrapResult) => {
-          if (wrapResult.status) {
-            toggleModal && toggleModal();
-            refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
-          } else {
-            setIsSending(false);
-          }
-        })
-        .catch((error) => {
-          console.error("Error raising dispute as buyer:", error);
-          setIsSending(false);
-        });
-    } else if (!isBuyer && !isUndefined(payArbitrationFeeBySeller)) {
-      setIsSending(true);
-      wrapWithToast(async () => await payArbitrationFeeBySeller(payArbitrationFeeBySellerConfig.request), publicClient)
-        .then((wrapResult) => {
-          if (wrapResult.status) {
-            toggleModal && toggleModal();
-            refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
-          } else {
-            setIsSending(false);
-          }
-        })
-        .catch((error) => {
-          console.error("Error raising dispute as seller:", error);
-          setIsSending(false);
-        });
-    }
-  };
+  const handleRaiseDispute = async () => {
+    const writeFn = isBuyer ? payArbitrationFeeByBuyer : payArbitrationFeeBySeller;
+    const request = isBuyer ? payArbitrationFeeByBuyerConfig?.request : payArbitrationFeeBySellerConfig?.request;
+    if (isUndefined(writeFn) || !request) return;
+
+    setIsSending(true);
+    try {
+      const wrapResult = await wrapWithToast(async () => await writeFn(request), publicClient);
+      if (wrapResult.status) {
+        toggleModal?.();
+        refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
+      }
+    } catch (error) {
+      console.error("Error raising dispute:", error);
+    } finally {
+      setIsSending(false);
+    }
+  };

104-113: Don’t OR buyer+seller simulate states; it can disable the button due to stale state from the “other” hook. Prefer role-scoped isLoading/isError derived values.

Proposed diff
+  const isLoadingConfig = isBuyer ? isLoadingBuyerConfig : isLoadingSellerConfig;
+  const isErrorConfig = isBuyer ? isErrorBuyerConfig : isErrorSellerConfig;

   return (
     <div>
       <Button
-        isLoading={!insufficientBalance && (isSending || isLoadingBuyerConfig || isLoadingSellerConfig)}
+        isLoading={!insufficientBalance && (isSending || isLoadingConfig)}
         isDisabled={
           isSending ||
           insufficientBalance ||
-          isLoadingBuyerConfig ||
-          isLoadingSellerConfig ||
-          isErrorBuyerConfig ||
-          isErrorSellerConfig
+          isLoadingConfig ||
+          isErrorConfig
         }
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ProposeSettlementButton.tsx (1)

40-59: Reset isSending on successful settlement proposal.

The success path (lines 47-49) doesn't call setIsSending(false), leaving the button in loading state indefinitely. Add the reset either in the success block or preferably use .finally() like ReleasePaymentButton does to ensure cleanup on all paths.

Proposed patch
   const handleProposeSettlement = () => {
     if (!isUndefined(proposeSettlement)) {
       setIsSending(true);
       wrapWithToast(async () => await proposeSettlement(proposeSettlementConfig.request), publicClient)
         .then((wrapResult) => {
           if (wrapResult.status) {
             toggleModal && toggleModal();
             refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
+            setIsSending(false);
           } else {
             setIsSending(false);
           }
         })
         .catch((error) => {
           console.error("Error proposing settlement:", error);
           setIsSending(false);
         });
     }
   };
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ExecuteTransactionButton.tsx (1)

30-45: isSending never resets on success → button can stay stuck loading/disabled.
On the success path you refetch but don’t clear isSending (unlike ClaimFullPaymentButton which uses .finally).

Proposed fix
       wrapWithToast(() => executeTransaction(executeTransactionConfig.request), publicClient)
         .then((wrapResult) => {
           if (wrapResult.status) {
             refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
-          } else {
-            setIsSending(false);
           }
-        })
+        })
         .catch((error) => {
           console.error("Error executing transaction:", error);
-          setIsSending(false);
         });
+        .finally(() => setIsSending(false));

Also applies to: 50-54

web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/Balance.tsx (1)

17-28: Add address presence check to wagmi query.enabled conditions to prevent queries with undefined address.

The useBalance and useReadContract hooks are currently enabled based only on transaction type, ignoring that address from useAccount() is undefined until a wallet is connected. This causes force-casting of undefined to 0x${string}. Explicitly guard both hooks with && !!address in their enabled conditions, following wagmi's recommended pattern for dependent queries.

Proposed fix
   const { data: nativeBalance } = useBalance({
-    query: { enabled: isNativeTransaction },
+    query: { enabled: isNativeTransaction && !!address },
     address: address as `0x${string}`,
   });

   const { data: tokenBalance } = useReadContract({
-    query: { enabled: !isNativeTransaction },
+    query: { enabled: !isNativeTransaction && !!address },
     address: token?.address as `0x${string}`,
     abi: erc20Abi,
     functionName: "balanceOf",
     args: [address as `0x${string}`],
   });
web/global.d.ts (1)

1-12: Fix likely TS error: React types referenced without import after adding export {}.
Bring React types into scope (or use import("react").FC / SVGProps) so global.d.ts still compiles as a module.

Proposed fix
+import type { FC, SVGProps } from "react";
+
-declare global {
-  module "*.svg" {
-    const content: React.FC<React.SVGAttributes<SVGElement>>;
-    export default content;
-  }
-  module "*.png" {
-    const path: string;
-    export default path;
-  }
-}
+declare module "*.svg" {
+  const content: FC<SVGProps<SVGSVGElement>>;
+  export default content;
+}
+
+declare module "*.png" {
+  const path: string;
+  export default path;
+}

 export {};
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/TimeOutButton.tsx (1)

15-43: Fix isBuyer computation; undefined === undefined makes it true (wrong) and flips query enabling.
Make it explicitly dependent on both values being present.

Proposed fix
-  const isBuyer = useMemo(() => address?.toLowerCase() === buyer?.toLowerCase(), [address, buyer]);
+  const isBuyer = useMemo(() => {
+    if (!address || !buyer) return false;
+    return address.toLowerCase() === buyer.toLowerCase();
+  }, [address, buyer]);
web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx (1)

27-30: Fix possible runtime crash from user?.emailUpdateableAt! when it’s undefined.
isEmailUpdateable becomes false if user?.email exists but user?.emailUpdateableAt is undefined, yet the render path force-unwraps it.

Proposed fix
-  const isEmailUpdateable = user?.email
-    ? !isUndefined(user?.emailUpdateableAt) && new Date(user.emailUpdateableAt!).getTime() < new Date().getTime()
-    : true;
+  const emailUpdateableAt = user?.emailUpdateableAt;
+  const emailUpdateableAtMs = !isUndefined(emailUpdateableAt) ? new Date(emailUpdateableAt).getTime() : undefined;
+  const isEmailUpdateable = user?.email ? (emailUpdateableAtMs ? emailUpdateableAtMs < Date.now() : true) : true;

...
-      {!isEmailUpdateable ? (
+      {!isEmailUpdateable && emailUpdateableAtMs ? (
         <InfoCard
           className="w-fit text-sm mb-2 wrap-break-word"
-          msg={`You can update email again ${timeLeftUntil(user?.emailUpdateableAt!)}`}
+          msg={`You can update email again ${timeLeftUntil(emailUpdateableAtMs)}`}
         />
       ) : null}

Also applies to: 96-101

web/src/layout/Header/navbar/Menu/Settings/General.tsx (1)

16-18: Guard against missing block explorer URL (broken href).

Proposed fix
-  const addressExplorerLink = useMemo(() => {
-    return `${chain?.blockExplorers?.default.url}/address/${address}`;
-  }, [address, chain]);
+  const addressExplorerLink = useMemo(() => {
+    const baseUrl = chain?.blockExplorers?.default.url;
+    if (!baseUrl || !address) return undefined;
+    return `${baseUrl}/address/${address}`;
+  }, [address, chain]);

...
-                <a
-                  href={addressExplorerLink}
+                <a
+                  href={addressExplorerLink ?? "#"}
                   rel="noopener noreferrer"
                   target="_blank"
                   className="hover:underline [&_label]:cursor-pointer [&_label]:text-klerosUIComponentsPrimaryBlue"
                 >

Also applies to: 35-43

web/src/pages/MyTransactions/Modal/ProposeSettlementModal/AmountField.tsx (1)

16-27: Critical: Type mismatch causes broken validation logic.

parseFloat(amount) returns a number (e.g., 1.5), while parseEther(amountProposed) returns a bigint in wei (e.g., 1500000000000000000n). Comparing these directly (transactionAmount < proposedAmount) will produce incorrect results because you're comparing ether as a float against wei as a bigint.

Both values should be in the same unit and type before comparison.

🐛 Proposed fix
   useEffect(() => {
-    const transactionAmount = parseFloat(amount);
-    const proposedAmount = parseEther(amountProposed);
+    const transactionAmount = parseEther(amount);
+    const proposedAmount = parseEther(amountProposed || "0");

-    if (amountProposed && transactionAmount < proposedAmount) {
+    if (amountProposed && proposedAmount > transactionAmount) {
       setError("Proposed amount exceeds transaction amount");
       setIsAmountValid(false);
     } else {
       setError("");
       setIsAmountValid(true);
     }
   }, [amountProposed, amount, setIsAmountValid]);
web/src/components/TransactionsDisplay/TransactionsGrid.tsx (1)

61-67: Remove unnecessary Math.ceil() and nullish coalescing on totalPages.

totalPages is already computed as a page count in TransactionsFetcher.tsx (line 76: Math.ceil(totalTransactions / transactionsPerPage)), and typed as a required number. Passing it to numPages={Math.ceil(totalPages ?? 0)} applies ceiling redundantly and the ?? 0 is unnecessary. Change to: numPages={totalPages}.

web/src/layout/Header/navbar/DappList.tsx (1)

83-111: Tailwind token z-1 is invalid—use z-[1] or define in config. The z-1 class is not part of Tailwind's default z-index scale (which includes z-0, z-10, z-20, etc.) and is not defined in your custom theme. This appears in multiple files and should be corrected. The custom tokens w-fluid-300-480, px-fluid-8-24, and rounded-base are properly defined and working correctly.

Additionally, the code has minor improvements:

  • Type containerRef as useRef<HTMLDivElement | null>(null) for clarity
  • Simplify the callback: useClickAway(containerRef, toggleIsDappListOpen) instead of wrapping with an arrow function
  • Remove the redundant transform class (not needed with -translate-x-1/2)
Proposed changes
-  const containerRef = useRef(null);
-  useClickAway(containerRef, () => toggleIsDappListOpen());
+  const containerRef = useRef<HTMLDivElement | null>(null);
+  useClickAway(containerRef, toggleIsDappListOpen);

   return (
     <div
       ref={containerRef}
       className={clsx(
         "flex flex-col items-center absolute max-h-[340px] w-[86vw] max-w-[480px]",
-        "top-[5%] left-1/2 transform -translate-x-1/2 z-1",
+        "top-[5%] left-1/2 -translate-x-1/2 z-[1]",
         "border border-klerosUIComponentsStroke rounded-base",
         "bg-klerosUIComponentsWhiteBackground shadow-custom",
         "[&_svg]:visible",
web/src/pages/MyTransactions/Modal/PaymentReleased/index.tsx (1)

15-25: Tighten ref typing + simplify click-away handler.

This keeps ref usage type-safe and avoids an unnecessary lambda wrapper. The StyledModal component is typed as forwardRef<HTMLDivElement, ...>, so the ref should be properly typed, and toggleModal can be passed directly to useClickAway since it already has the correct () => void signature.

Proposed change
-  const containerRef = useRef(null);
-  useClickAway(containerRef, () => toggleModal());
+  const containerRef = useRef<HTMLDivElement | null>(null);
+  useClickAway(containerRef, toggleModal);
web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx (1)

31-50: Fix click-away ref wiring + guard JSON.parse against corrupted localStorage.

Right now containerRef is never attached to an element, so useClickAway likely won’t work as intended. Also, a bad selectedToken value in localStorage can throw and take down the page.

Proposed fix
-  const containerRef = useRef(null);
+  const containerRef = useRef<HTMLDivElement | null>(null);
@@
   useEffect(() => {
     if (tokens?.length > 0) {
       const nativeToken = tokens.find((token) => token.address === "native");
-      setSendingToken(JSON.parse(localStorage.getItem("selectedToken")) || nativeToken);
+      let selectedToken: unknown = null;
+      try {
+        const raw = localStorage.getItem("selectedToken");
+        selectedToken = raw ? JSON.parse(raw) : null;
+      } catch {
+        localStorage.removeItem("selectedToken");
+      }
+      setSendingToken((selectedToken as any) || nativeToken);
     }
@@
   return (
-    <div className="relative flex flex-col gap-2">
+    <div ref={containerRef} className="relative flex flex-col gap-2">
       <DropdownButton {...{ loading, sendingToken }} onClick={() => setIsOpen(!isOpen)} />
       {isOpen && <TokenListModal {...{ setIsOpen, tokens, setTokens, handleSelectToken }} />}
     </div>
   );
web/src/components/Loader.tsx (1)

9-19: Fix !width / !height checks (treats 0 as “unset”).

Use nullish checks so callers can intentionally pass 0 without getting w-full/h-full.

Proposed fix
-    <div className={cn(!width && "w-full", !height && "h-full", className)} style={{ width, height }}>
+    <div
+      className={cn(width == null && "w-full", height == null && "h-full", className)}
+      style={{ width, height }}
+    >
       <KlerosIcon className="fill-klerosUIComponentsStroke animate-breathing" />
     </div>
web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx (1)

45-66: Use BigInt-based comparison for balance validation and allow the input field to be clearable.

When quantity is empty, parseFloat("") returns NaN, which bypasses both the "insufficient balance" and "amount is zero" checks. The code then incorrectly sets setHasSufficientNativeBalance(true), allowing submission with no amount. Additionally, value={quantity || "0"} creates a UX mismatch where the field displays "0" but the actual state is empty, making it effectively non-clearable. Float comparisons also risk precision loss for large token amounts.

Replace the float-based validation with BigInt-safe logic using viem's parseUnits, handle empty input explicitly, and remove the || "0" fallback.

Proposed diff (BigInt-safe compare + allow empty)
-import { formatUnits } from "viem";
+import { formatUnits, parseUnits } from "viem";
 ...
   useEffect(() => {
-    const enteredAmount = parseFloat(quantity);
+    // Treat empty as invalid/insufficient to prevent proceeding without an amount.
+    if (!quantity) {
+      setError("");
+      setHasSufficientNativeBalance(false);
+      return;
+    }
+
+    const enteredAmount = Number(quantity);
 
-    if (quantity && balanceAmount < enteredAmount) {
+    // Prefer BigInt compare for correctness.
+    try {
+      const balanceRaw = isNativeTransaction ? nativeBalance?.value : tokenBalance;
+      const decimals = isNativeTransaction ? nativeBalance?.decimals : tokenDecimal;
+      if (balanceRaw != null && decimals != null) {
+        const enteredRaw = parseUnits(quantity, decimals);
+        if (balanceRaw < enteredRaw) {
+          setError("Insufficient balance");
+          setHasSufficientNativeBalance(false);
+          return;
+        }
+      }
+    } catch {
+      setError("Invalid amount");
+      setHasSufficientNativeBalance(false);
+      return;
+    }
+
+    if (balanceAmount < enteredAmount) {
       setError("Insufficient balance");
       setHasSufficientNativeBalance(false);
     } else if (enteredAmount === 0) {
       setError("Amount is zero");
       setHasSufficientNativeBalance(false);
     } else {
       setError("");
       setHasSufficientNativeBalance(true);
     }
   }, [quantity, balanceAmount, setHasSufficientNativeBalance]);
 ...
       <BigNumberField
         aria-label="Amount"
         className="w-48"
-        value={quantity || "0"}
+        value={quantity}
         onChange={(value) => setQuantity(value.toString())}
         placeholder="Amount"
         variant={error ? "error" : undefined}
         message={error}
         showFieldError
         minValue="0"
       />
web/src/components/PreviewCard/Terms/index.tsx (1)

20-46: Terms no longer passes required props to Description (likely typecheck + runtime issues).
Description expects receivingToken and sendingToken (and uses them in cryptoSwapSummary), but Terms doesn’t destructure/pass them anymore.

Proposed fix
 const Terms: React.FC<ITerms> = ({
   escrowType,
   deliverableText,
   receivingQuantity,
+  receivingToken,
   buyerAddress,
   sendingQuantity,
+  sendingToken,
   sellerAddress,
   deadline,
   assetSymbol,
   extraDescriptionUri,
 }) => {
   return (
     <div className="flex flex-col gap-4 lg:gap-6">
       <h3 className="m-0 text-klerosUIComponentsPrimaryText font-semibold">Terms</h3>
       <Description
         {...{
           escrowType,
           deliverableText,
           receivingQuantity,
+          receivingToken,
           buyerAddress,
           sendingQuantity,
+          sendingToken,
           sellerAddress,
           deadline,
           assetSymbol,
         }}
       />
       <AttachedFile {...{ extraDescriptionUri }} />
     </div>
   );
 };
web/src/pages/NewTransaction/Terms/Payment/BuyerAddress.tsx (2)

97-102: Edge case: marking buyer resolved when walletAddress is missing.
setIsBuyerAddressResolved(true) is set even if walletAddress is undefined (Line 99-101). Consider guarding and setting resolved=false when empty.


86-114: Collapsed section can still receive focus/keyboard interaction (accessibility issue).
With only opacity and max-height applied, the nested TextField remains tabbable and focusable when hidden, and screen readers will still announce its content. Add pointer-events-none when collapsed and use aria-hidden to properly hide from both keyboard navigation and assistive technology.

Suggested approach
       <div
         className={cn(
           "flex flex-col justify-center items-center",
           "overflow-hidden transition-[max-height,opacity] duration-500 ease-in-out",
-          isBuyerAddressCustom ? "max-h-40 opacity-100" : "max-h-0 opacity-0"
+          isBuyerAddressCustom
+            ? "max-h-40 opacity-100 pointer-events-auto"
+            : "max-h-0 opacity-0 pointer-events-none"
         )}
+        aria-hidden={!isBuyerAddressCustom}
       >
web/src/components/PreviewCard/Terms/Description.tsx (1)

13-25: Type mismatch: deliverableText is string but handled as optional.
Either make deliverableText optional in IDescription (and upstream), or remove the undefined branch; current typing can mask real null/undefined bugs.

Also applies to: 120-132

web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ViewCaseButton.tsx (2)

15-19: Move hardcoded Kleros Court URL to environment configuration.

The testnet URL https://dev--kleros-v2-testnet.netlify.app is hardcoded and will be deployed to production. The codebase already has environment variable infrastructure (.env.devnet.public, .env.testnet.public, .env.mainnet.public) and uses REACT_APP_DEPLOYMENT to differentiate builds. Add the court base URL as an environment variable and reference it here, following the pattern used for other external URLs like REACT_APP_IPFS_GATEWAY and subgraph URLs.


10-25: Fix potential crash in buttonLink and add security flags to window.open.

Line 18 in buttonLink's fallback branch unconditionally interpolates disputeRequest.id, which produces an invalid URL like .../#/cases/undefined when disputeRequest?.id is falsy. Although the component conditionally renders only when disputeRequest?.id exists, the useMemo executes before that check. Additionally, window.open on line 23 lacks security flags—use noopener,noreferrer to prevent reverse tabnabbing, matching the pattern in MarkdownRenderer.tsx.

Proposed fix
  const buttonLink = useMemo(() => {
+   if (!disputeRequest?.id) return null;
    if (disputeRequest?.id && resolvedEvents?.length > 0)
      return `https://dev--kleros-v2-testnet.netlify.app/#/cases/${disputeRequest.id}/voting`;
    return `https://dev--kleros-v2-testnet.netlify.app/#/cases/${disputeRequest.id}`;
  }, [disputeRequest?.id, resolvedEvents?.length]);

  const handleButtonClick = () => {
-   if (disputeRequest?.id) {
-     window.open(buttonLink, "_blank");
-   }
+   if (buttonLink) {
+     window.open(buttonLink, "_blank", "noopener,noreferrer");
+   }
  };
web/src/components/PreviewCard/index.tsx (1)

11-16: Align IPreviewCard.escrowTitle with Header (make it optional to avoid mismatched public API).

Suggested change
 interface IPreviewCard {
   escrowType: string;
-  escrowTitle: string;
+  escrowTitle?: string;
   deliverableText: string;
   receivingQuantity: string;
   transactionCreationTimestamp: string;
   status: string;
   transactionHash: string;
@@
 const PreviewCard: React.FC<IPreviewCard> = ({
   escrowType,
   escrowTitle,
@@
 }) => (
   <Card className={clsx("flex flex-col gap-4 lg:gap-6", "w-full h-auto p-4 pt-5 lg:p-8", isPreview && "pb-9")}>
     <Header {...{ escrowType, escrowTitle, status, transactionHash, isCard: false }} />

Also applies to: 64-66

🤖 Fix all issues with AI agents
In `@web/src/components/Loader.tsx`:
- Around line 17-19: The Loader.tsx uses a non-existent "animate-breathing"
class on the KlerosIcon; add a breathing animation definition and wire it up:
either add a `@keyframes` breathing and a .animate-breathing class to
web/src/global.css (so Loader.tsx's KlerosIcon will animate) or add an
"breathing" animation entry to the Tailwind config's theme.extend.animation and
theme.extend.keyframes and rebuild so the class animate-breathing becomes
available; update global.css or tailwind config rather than changing Loader.tsx.

In `@web/src/components/TransactionsDisplay/StatsAndFilters.tsx`:
- Around line 7-10: The custom Tailwind-like classes (mt-fluid-4-8,
mb-fluid-16-32, gap-fluid-4-8) used in the TransactionsDisplay/StatsAndFilters
component won't be generated by Tailwind; either create proper utility mappings
for those classes (e.g., add utilities in tailwind.config.ts or extend
global.css with `@layer` utilities that map .mt-fluid-4-8, .mb-fluid-16-32,
.gap-fluid-4-8 to the corresponding CSS variables --spacing-fluid-4-8 and
--spacing-fluid-16-32) or remove the custom class names and apply the variables
directly via style attributes on the div (style={{ marginTop:
'var(--spacing-fluid-4-8)', marginBottom: 'var(--spacing-fluid-16-32)', gap:
'var(--spacing-fluid-4-8)' }}), then keep the JSX using <Stats {...{
totalTransactions, resolvedTransactions }} /> and <Filters /> unchanged.

In `@web/src/global.css`:
- Around line 1-6: Move the Tailwind v4 entrypoint import to be the very first
`@import` and remove the legacy `@tailwind` utilities directive: replace the current
top lines so that `@import` "tailwindcss"; appears before any other at-rules or
rules, then have `@import`
"../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; (and
keep the `@source` if needed) after it, and remove the now-invalid `@tailwind`
utilities; line; ensure all `@import` statements come before the `@custom-variant`
dark (&:where(.dark, .dark *)); rule.

In `@web/src/layout/Header/navbar/Menu/Help.tsx`:
- Around line 51-58: The Tailwind utilities `z-1` and the CSS variable
`--leading-18px` are not valid in Tailwind v4; update the className usages in
the Help component (component Help) to use `z-[1]` and `leading-[18px]`
respectively, and apply the same replacements in the Navbar index component
(component Navbar/index), the Menu Settings component (component Settings), and
the DappList component (component DappList) wherever `z-1` or `--leading-18px`
appear so all arbitrary values use Tailwind v4 bracket syntax.

In `@web/src/pages/MyTransactions/Modal/ProposeSettlementModal/index.tsx`:
- Line 6: StyledModal is missing dialog semantics; update its component to use
forwardRef (e.g., forwardRef<HTMLDivElement,
React.ComponentPropsWithoutRef<"div">>) and spread incoming props so it applies
role="dialog" and aria-labelledby from callers, e.g., accept ({ className,
children, ...props }, ref) and render <div ref={ref} role="dialog" {...props}>.
In the ProposeSettlementModal ensure the heading <h1> has a stable id (e.g.,
settlement-modal-title) and pass that id as aria-labelledby on StyledModal so
the dialog is linked to the heading.

In
`@web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ProposeSettlementButton.tsx`:
- Around line 32-38: Guard parseEther(amountProposed) so it cannot throw during
render by computing the parsed value outside the hook call (e.g., useMemo or
try/catch) and only pass args to useSimulateEscrowUniversalProposeSettlement
when amountProposed is a valid number/parsable string (otherwise pass undefined
or an empty args array); additionally, ensure isSending is reset to false on
successful settlement by calling setIsSending(false) after the success path
(alongside toggleModal if present) and defensively check toggleModal before
calling it to avoid leaving the spinner stuck.

In `@web/src/pages/NewTransaction/NavigationButtons/index.tsx`:
- Line 18: The code passes nextRoute ?? "" into NextButton which triggers
navigate("") when nextRoute is undefined; update the call site to pass nextRoute
directly (not coalesced to empty string) and/or update the NextButton component
to validate its prop: in NextButton (the component handling navigate calls)
check if nextRoute is truthy before calling navigate (and disable the button or
no-op if missing), and at the parent where NextButton is used prefer passing
nextRoute as-is so undefined propagates and can be handled inside NextButton.
🟠 Major comments (19)
web/src/pages/NewTransaction/NavigationButtons/DepositPaymentButton.tsx-46-52 (1)

46-52: Prevent runtime throw when deadline is missing/invalid (BigInt(NaN)).
new Date(deadline) can be invalid; BigInt(Math.floor(NaN)) throws and bricks the flow.

Proposed fix
-  const deliveryDeadlineTimestamp = useMemo(() => BigInt(Math.floor(new Date(deadline).getTime() / 1000)), [deadline]);
+  const deliveryDeadlineTimestamp = useMemo(() => {
+    const ms = Date.parse(deadline);
+    if (!deadline || Number.isNaN(ms)) return undefined;
+    return BigInt(Math.floor(ms / 1000));
+  }, [deadline]);

   const bufferSec = useMemo(() => BigInt(pickBufferFor(Math.floor(Date.now() / 1000))), []);
-  const disputeDeadlineTimestamp = useMemo(
-    () => deliveryDeadlineTimestamp + bufferSec,
-    [deliveryDeadlineTimestamp, bufferSec]
-  );
+  const disputeDeadlineTimestamp = useMemo(() => {
+    if (isUndefined(deliveryDeadlineTimestamp)) return undefined;
+    return deliveryDeadlineTimestamp + bufferSec;
+  }, [deliveryDeadlineTimestamp, bufferSec]);

And then gate simulations using !isUndefined(disputeDeadlineTimestamp) and pass a safe fallback in args if needed.

web/src/components/PreviewCard/Terms/AttachedFile.tsx-11-18 (1)

11-18: Encode the url query param to avoid broken links / injection via &/# characters.
getIpfsUrl(...) can produce URLs containing characters that should be escaped in a query string.

Proposed fix
   const uri = extraDescriptionUri && getIpfsUrl(extraDescriptionUri);

   return extraDescriptionUri ? (
-    <Link className="flex gap-fluid-5-6" to={`/attachment/?url=${uri}`}>
+    <Link
+      className="flex gap-fluid-5-6"
+      to={`/attachment/?url=${encodeURIComponent(uri ?? "")}`}
+    >
       <AttachmentIcon className="w-4 fill-klerosUIComponentsPrimaryBlue" />
       View Attached File
     </Link>
   ) : null;
web/src/pages/MyTransactions/TransactionDetails/WasItFulfilled/Buttons/ClaimFullPaymentButton.tsx-20-26 (1)

20-26: Type mismatch in args: use BigInt(id) to match ExecuteTransactionButton pattern.
ClaimFullPaymentButton passes args: [id], but the identical component ExecuteTransactionButton passes args: [BigInt(id)]. Since both use useSimulateEscrowUniversalExecuteTransaction with the same context value, consistency is required—wagmi/viem expects bigint for uint256 parameters. This inconsistency will cause simulation or write failures.

Fix
   } = useSimulateEscrowUniversalExecuteTransaction({
-    args: [id],
+    args: [BigInt(id)],
   });
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ExecuteTransactionButton.tsx-19-25 (1)

19-25: Guard against missing executeTransactionConfig.request before calling writeContractAsync.

The current guard only checks !isUndefined(executeTransaction), but executeTransactionConfig.request can be undefined if the simulate hook hasn't completed. While the Button is disabled during loading, React doesn't prevent event handlers from firing—a race condition could trigger the handler before data is ready, causing a null reference error.

Also, the async/await wrapper in wrapWithToast() is unnecessary; pass the function directly.

Proposed fix
const handleExecuteTransaction = () => {
-  if (!isUndefined(executeTransaction)) {
+  if (!isUndefined(executeTransaction) && executeTransactionConfig?.request) {
     setIsSending(true);
-    wrapWithToast(async () => await executeTransaction(executeTransactionConfig.request), publicClient)
+    wrapWithToast(() => executeTransaction(executeTransactionConfig.request), publicClient)
       .then((wrapResult) => {
         if (wrapResult.status) {
           refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/TimeOutButton.tsx-23-43 (1)

23-43: Guard BigInt(id) and timeOutBy*Config.request to avoid runtime throws.
Right now a missing/invalid id or an undefined config can crash the component when hooks run or on click.

Proposed fix
-  } = useSimulateEscrowUniversalTimeOutByBuyer({
-    args: [BigInt(id)],
+  } = useSimulateEscrowUniversalTimeOutByBuyer({
+    args: id ? [BigInt(id)] : undefined,
     query: {
-      enabled: isBuyer,
+      enabled: Boolean(id) && isBuyer,
     },
   });

   const {
     data: timeOutBySellerConfig,
     isLoading: isLoadingSellerConfig,
     isError: isErrorSellerConfig,
   } = useSimulateEscrowUniversalTimeOutBySeller({
-    args: [BigInt(id)],
+    args: id ? [BigInt(id)] : undefined,
     query: {
-      enabled: !isBuyer,
+      enabled: Boolean(id) && !isBuyer,
     },
   });

   const handleTimeout = () => {
-    if (isBuyer && !isUndefined(timeOutByBuyer)) {
+    if (isBuyer && timeOutByBuyerConfig?.request && !isUndefined(timeOutByBuyer)) {
       setIsSending(true);
       wrapWithToast(async () => await timeOutByBuyer(timeOutByBuyerConfig.request), publicClient)
         .then((wrapResult) => {
           if (!wrapResult.status) {
             setIsSending(false);
             refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
           }
         })
         .catch((error) => {
           console.error("Error timing out as buyer:", error);
           setIsSending(false);
         });
-    } else if (!isBuyer && !isUndefined(timeOutBySeller)) {
+    } else if (!isBuyer && timeOutBySellerConfig?.request && !isUndefined(timeOutBySeller)) {
       setIsSending(true);
       wrapWithToast(async () => await timeOutBySeller(timeOutBySellerConfig.request), publicClient)
         .then((wrapResult) => {
           if (!wrapResult.status) {
             setIsSending(false);
             refetchQuery([["refetchOnBlock", "useTransactionDetailsQuery"]]);
           }
         })
         .catch((error) => {
           console.error("Error timing out as seller:", error);
           setIsSending(false);
         });
     }
   };

Also applies to: 48-76

web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/index.tsx-13-13 (1)

13-13: Fix isBuyer here too; undefined === undefined makes it true.
This can flip button visibility/logic before address/context is ready.

Proposed fix
-  const isBuyer = useMemo(() => address?.toLowerCase() === buyer?.toLowerCase(), [address, buyer]);
+  const isBuyer = useMemo(() => {
+    if (!address || !buyer) return false;
+    return address.toLowerCase() === buyer.toLowerCase();
+  }, [address, buyer]);

Also applies to: 35-36

web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/index.tsx-4-4 (1)

4-4: Make the token row keyboard-accessible (prefer <button> over clickable <div>)

Right now it’s mouse-clickable only; keyboard users won’t be able to select tokens without extra work.

Proposed fix
-    <div
+    <button
+      type="button"
       className={cn(
         "flex items-center justify-between",
-        "transition duration-100 cursor-pointer py-2.5 px-4",
+        "transition duration-100 cursor-pointer py-2.5 px-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-klerosUIComponentsPrimaryBlue",
         "hover:bg-klerosUIComponentsLightBlue",
         selected
           ? "bg-klerosUIComponentsMediumBlue border-l-[3px] border-klerosUIComponentsPrimaryBlue pl-3"
           : "bg-transparent border-none pl-4"
       )}
       onClick={() => onSelect(token)}
+      aria-pressed={selected}
     >
       <div className="flex items-center gap-2">
         <TokenIcon symbol={token.symbol} logo={token.logo} />
         <span className="text-klerosUIComponentsPrimaryText">{token.symbol}</span>
       </div>
       <Balance {...{ token }} />
-    </div>
+    </button>

Also applies to: 8-25

web/src/components/TransactionsDisplay/index.tsx-27-34 (1)

27-34: Address non-standard Tailwind syntax and incorrect semantic HTML element.

Line 28 uses text-(length:--spacing-fluid-20-24) with parentheses, which is not standard Tailwind v4 syntax. Tailwind v4 uses square brackets for arbitrary values: text-[length:--spacing-fluid-20-24]. While this pattern appears throughout the codebase, the parentheses notation is not documented and could be fragile across Tailwind updates.

Additionally, line 33 incorrectly uses <label> for display text. The <label> element is semantically meant for form controls. Use <p> instead.

Suggested fixes
-      <h1 className="mb-fluid-12-24 text-(length:--spacing-fluid-20-24)">{title}</h1>
+      <h1 className="mb-fluid-12-24 text-[length:--spacing-fluid-20-24]">{title}</h1>

-        <label className="text-(length:--spacing-fluid-14-16)">No transactions found</label>
+        <p className="text-[length:--spacing-fluid-14-16]">No transactions found</p>
web/src/layout/Header/MobileHeader.tsx-27-32 (1)

27-32: Hamburger button needs an accessible name for screen readers.

The LightButton with text="" is icon-only and requires an aria-label (or equivalent accessibility attribute) to identify it to assistive technologies. The component's interface doesn't currently support passing aria-label, so this either needs to be added to LightButton or passed through as a spread prop.

web/src/components/OverlayPortal.tsx-5-8 (1)

5-8: Change z-9999 to z-[9999] to apply the z-index value correctly.

The class z-9999 is not a standard Tailwind utility and is not configured in the project's theme, so it won't be generated and will silently fail to apply. Use the arbitrary value syntax z-[9999] which is fully supported in Tailwind v4.1.17.

Proposed change
-    <div className="fixed top-0 left-0 z-9999 w-full h-full">{children}</div>,
+    <div className="fixed top-0 left-0 z-[9999] w-full h-full">{children}</div>,
web/src/pages/AttachmentDisplay/index.tsx-30-37 (1)

30-37: Incorrect use of Link for external URLs.

React Router's Link component is designed for internal navigation and will not correctly handle external URLs. The url from search params could be an external attachment URL (e.g., IPFS gateway). This will cause broken navigation.

Use a native <a> tag instead, consistent with the approach in Footer/index.tsx.

Proposed fix
-          <Link
-            className="flex gap-2 items-center self-end mb-2 hover:underline"
-            to={url}
-            rel="noopener noreferrer"
-            target="_blank"
-          >
+          <a
+            className="flex gap-2 items-center self-end mb-2 hover:underline"
+            href={url}
+            rel="noopener noreferrer"
+            target="_blank"
+          >
             Open in new tab <NewTabIcon className="fill-klerosUIComponentsPrimaryBlue" />
-          </Link>
+          </a>

Also update the import:

-import { Link, useSearchParams } from "react-router-dom";
+import { useSearchParams } from "react-router-dom";
web/src/components/SimpleToggleButton.tsx-5-29 (1)

5-29: Forward remaining button props + set type="button" (current implementation is a subtle breaking change).

Right now you drop all props except onClick, despite extending React.ButtonHTMLAttributes<HTMLButtonElement> (e.g., disabled, aria-*, type). Also, default button type can submit forms unexpectedly.

Proposed diff
 interface ISimpleToggleButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   isOpen: boolean;
   label: string;
 }

-const SimpleToggleButton: React.FC<ISimpleToggleButton> = ({ isOpen, label, onClick }) => (
+const SimpleToggleButton: React.FC<ISimpleToggleButton> = ({ isOpen, label, type, ...rest }) => (
   <button
     className={cn(
       "flex items-center justify-center p-0",
       "cursor-pointer bg-transparent border-none outline-none",
       "focus-visible:shadow-[0_0_0_2px_var(--klerosUIComponentsPrimaryBlue)]",
-      "hover:[&_label]:text-klerosUIComponentsSecondaryBlue hover:[&_svg]:fill-klerosUIComponentsSecondaryBlue"
+      "hover:[&_span]:text-klerosUIComponentsSecondaryBlue hover:[&_svg]:fill-klerosUIComponentsSecondaryBlue"
     )}
-    {...{ onClick }}
+    type={type ?? "button"}
+    {...rest}
   >
-    <label className="text-xs text-klerosUIComponentsPrimaryBlue cursor-pointer transition-colors ease-in-out duration-200">
+    <span className="text-xs text-klerosUIComponentsPrimaryBlue cursor-pointer transition-colors ease-in-out duration-200">
       {label}
-    </label>
+    </span>
     <Arrow
       className={cn(
         "w-2.5 h-2.5 ml-1.5 transition-transform ease-in-out duration-200 fill-klerosUIComponentsPrimaryBlue",
         isOpen ? "rotate-180" : "rotate-0"
       )}
     />
   </button>
 );
web/src/pages/NewTransaction/Terms/Deadline/index.tsx-11-29 (1)

11-29: Guard parseZonedDateTime() against non-zoned DateValue types to prevent runtime errors.

While Datepicker with minValue set to a ZonedDateTime and time prop typically emits ZonedDateTime, the function signature accepts the union type DateValue (which includes CalendarDate and CalendarDateTime). Calling parseZonedDateTime(value.toString()) on a non-zoned value will throw because their string formats don't match the expected zoned pattern. Add error handling or type narrowing to avoid crashes.

Suggested fix
  const handleDateChange = (value: DateValue | null) => {
    if (!value) {
      setDeadline("");
      return;
    }

-   const formattedDeadline = parseZonedDateTime(value.toString()).toDate().toISOString();
-   setDeadline(formattedDeadline);
+   try {
+     const zoned = parseZonedDateTime(value.toString());
+     setDeadline(zoned.toDate().toISOString());
+   } catch {
+     setDeadline("");
+   }
  };
web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/DropdownButton.tsx-8-28 (1)

8-28: Change clickable <div> to <button> element for proper keyboard navigation and accessibility.

This component violates WCAG guidelines by implementing a clickable container without keyboard support or semantic button role. Additionally, sendingToken may be undefined during initial render, causing a crash on line 23 when accessing .symbol and .logo without optional chaining (inconsistent with line 25 which already uses optional chaining).

Proposed fix
-    <div
+    <button
+      type="button"
       className={clsx(
         "relative flex items-center justify-between",
         "h-[45px] w-48 py-2.5 px-3.5",
         "cursor-pointer transition duration-100",
         "border rounded-base border-klerosUIComponentsStroke",
         "text-klerosUIComponentsPrimaryText",
         "bg-klerosUIComponentsWhiteBackground hover:bg-klerosUIComponentsLightGrey"
       )}
       onClick={onClick}
     >
       <div className="flex items-center gap-2">
         {loading ? (
           <Skeleton className="rounded-full mb-0.5" width={24} height={24} />
         ) : (
-          <TokenIcon symbol={sendingToken.symbol} logo={sendingToken.logo} />
+          <TokenIcon symbol={sendingToken?.symbol} logo={sendingToken?.logo} />
         )}
-        {loading ? <Skeleton width={40} height={16} /> : sendingToken?.symbol}
+        {loading ? <Skeleton width={40} height={16} /> : sendingToken?.symbol ?? ""}
       </div>
-      <span className="inline-block ml-2 p-1 border-r border-b border-klerosUIComponentsStroke rotate-45" />
-    </div>
+      <span
+        aria-hidden="true"
+        className="inline-block ml-2 p-1 border-r border-b border-klerosUIComponentsStroke rotate-45"
+      />
+    </button>
web/src/components/PreviewCard/Terms/Description.tsx-39-46 (1)

39-46: Gate useEnsName hook calls with address validation.

The addresses are cast to 0x${string} without validation, which allows invalid strings through the type system. In wagmi v2, useEnsName will throw errors and make noisy requests if passed an invalid address. Pass undefined unless the address is validated via validateAddress() (already available in the codebase), or use the query: { enabled: Boolean(isValidAddress) } parameter to gate execution.

Reference pattern exists in BuyerAddress.tsx (lines 41–49) which validates addresses before use.

web/src/layout/Header/index.tsx-20-26 (1)

20-26: CSS variable naming mismatch: Header component uses unprefixed variables while newer stylesheets use prefixed.

The Header's StatusBanner theme references --klerosUIComponentsWhiteBackground, --klerosUIComponentsPrimaryText, and --klerosUIComponentsSecondaryText (unprefixed), but mdxEditorStyles.css and markdownRendererStyles.css consistently use the --color- prefixed versions (--color-klerosUIComponentsWhiteBackground, etc.). Since these variables are defined in the external @kleros/ui-components-library package, if the theme CSS only defines one naming scheme, the banner colors will silently fail.

Update the Header component to use the prefixed variable names:

Fix
         theme={{
           colors: {
-            main: "var(--klerosUIComponentsWhiteBackground)",
-            primary: "var(--klerosUIComponentsPrimaryText)",
-            secondary: "var(--klerosUIComponentsSecondaryText)",
+            main: "var(--color-klerosUIComponentsWhiteBackground)",
+            primary: "var(--color-klerosUIComponentsPrimaryText)",
+            secondary: "var(--color-klerosUIComponentsSecondaryText)",
           },
         }}
web/src/components/MarkdownRenderer.tsx-149-150 (1)

149-150: Security concern: Allowing style attribute enables CSS-based attacks.

The global allowlist for style attribute on all elements ("*": ["className", "id", "style"]) can be exploited for CSS-based attacks such as:

  • Data exfiltration via background-image: url(...)
  • UI redressing/clickjacking via positioning
  • Content spoofing

Consider removing style from the global allowlist, or if inline styles are required, use a CSS sanitizer like css.escape or a dedicated library.

🔒 Suggested fix
                 attributes: {
-                  "*": ["className", "id", "style"],
+                  "*": ["className", "id"],
                   a: ["href", "title", "target", "rel"],
web/src/layout/Header/navbar/index.tsx-47-80 (1)

47-80: Using hidden prevents the close animation from running.
On close, the wrapper becomes hidden immediately, so the inner scale-y transition won’t be visible. Prefer pointer-events-none + opacity/visibility transitions instead of toggling display.

Suggested change (animate-friendly open/close)
-      <div className={cn("absolute top-full left-0 w-screen h-screen z-1", isOpen ? "visible" : "hidden")}>
-        <Overlay className="top-[unset]">
+      <div
+        aria-hidden={!isOpen}
+        className={cn(
+          "absolute top-full left-0 w-screen h-screen z-50 transition-opacity duration-[klerosUIComponentsTransitionSpeed]",
+          isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
+        )}
+      >
+        <Overlay>
           <div
             className={cn(
               "absolute top-0 left-0 right-0 max-h-[calc(100vh-160px)] p-6 overflow-y-auto z-10",
               "bg-klerosUIComponentsWhiteBackground shadow-default origin-top",
               "transition-[transform,visibility] duration-[klerosUIComponentsTransitionSpeed] ease-in-out",
               "[&_hr]:my-6",
               isOpen ? "scale-y-100 visible" : "scale-y-0 invisible"
             )}
           >
web/src/components/FileViewer/Viewers/MarkdownViewer.tsx-7-18 (1)

7-18: Harden base64 decoding (data-URL assumption + UTF-8 + decode failure path).

Current logic can throw (missing comma / invalid base64) and can corrupt non-ASCII markdown (UTF-8).

Proposed fix
 const MarkdownDocRenderer: DocRenderer = ({ mainState: { currentDocument } }) => {
   if (!currentDocument) return null;
-  const base64String = (currentDocument.fileData as string).split(",")[1];
-
-  // Decode the base64 string
-  const decodedData = atob(base64String);
+  const fileData = currentDocument.fileData as string;
+  const base64String = fileData.includes(",") ? fileData.split(",")[1] : fileData;
+  if (!base64String) return null;
+
+  let decodedData = "";
+  try {
+    const bytes = Uint8Array.from(globalThis.atob(base64String), (c) => c.charCodeAt(0));
+    decodedData = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
+  } catch {
+    // Fail closed: don't break the viewer on malformed input
+    return null;
+  }
 
   return (
     <div className="p-4" id="md-renderer">
       <MarkdownRenderer content={decodedData} />
     </div>
   );
 };

Comment on lines +17 to +19
<div className={cn(!width && "w-full", !height && "h-full", className)} style={{ width, height }}>
<KlerosIcon className="fill-klerosUIComponentsStroke animate-breathing" />
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for animate-breathing definition
echo "=== Searching for animate-breathing definition ==="
rg -n --hidden -S 'animate-breathing' . || echo "No matches found"

echo ""
echo "=== Searching for keyframes breathing ==="
rg -n --hidden -S '@keyframes\s+breathing|keyframes\s*:\s*\{[^}]*breathing' . || echo "No matches found"

echo ""
echo "=== Checking Tailwind config ==="
find . -name 'tailwind.config.*' -type f | head -5

echo ""
echo "=== Checking package.json for tailwind ==="
rg -A 5 'tailwindcss' package.json || echo "Not found"

Repository: kleros/escrow-v2

Length of output: 374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for CSS files and animation definitions ==="
find . -type f \( -name '*.css' -o -name '*.scss' -o -name '*.less' \) | head -20

echo ""
echo "=== Search for 'breathing' anywhere ==="
rg -n --hidden -S 'breathing' . || echo "No matches found"

echo ""
echo "=== Search for global styles or style definitions ==="
rg -n --hidden -S '@keyframes|@import.*css|\.animate|animation:' . | head -30

echo ""
echo "=== Check if there's a styles directory ==="
ls -la web/src/styles 2>/dev/null || echo "No styles directory found"

echo ""
echo "=== Check main index or App files ==="
find web/src -maxdepth 2 -name 'index.*' -o -name 'App.*' -o -name 'main.*' | xargs ls -la 2>/dev/null

Repository: kleros/escrow-v2

Length of output: 1414


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Content of global.css ==="
cat -n web/src/global.css

echo ""
echo "=== Check Kleros UI theme for breathing animation ==="
find node_modules/@kleros/ui-components-library -name '*.css' 2>/dev/null | head -5

echo ""
echo "=== Search in Kleros UI theme for breathing animation ==="
rg -n 'breathing|@keyframes' node_modules/@kleros/ui-components-library/dist 2>/dev/null || echo "Not found or directory doesn't exist"

echo ""
echo "=== Check Tailwind config in the app ==="
find . -maxdepth 2 -name 'tailwind.config.*' -type f 2>/dev/null

echo ""
echo "=== Check for any vite/build config with tailwind setup ==="
find . -maxdepth 2 \( -name 'vite.config.*' -o -name 'next.config.*' -o -name 'webpack.config.*' \) -type f | head -5

Repository: kleros/escrow-v2

Length of output: 7765


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for Tailwind config ==="
find . -maxdepth 3 -type f \( -name 'tailwind.config.*' -o -name 'vite.config.*' -o -name 'postcss.config.*' \) 2>/dev/null

echo ""
echo "=== Check for CSS that might define animations ==="
rg -n '@keyframes|animation:' web/src --type css 2>/dev/null || echo "No keyframes found in CSS"

echo ""
echo "=== Check package.json structure ==="
cat package.json | head -40

echo ""
echo "=== Search for any 'breathing' animation definition in entire codebase ==="
rg -i 'breathing' . --type=ts --type=tsx --type=css --type=json 2>/dev/null || echo "Only found in Loader.tsx"

Repository: kleros/escrow-v2

Length of output: 1244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Content of vite.config.js ==="
cat -n web/vite.config.js

echo ""
echo "=== Check web package.json ==="
cat -n web/package.json | head -60

Repository: kleros/escrow-v2

Length of output: 3911


animate-breathing animation is not defined anywhere in the codebase.

The class is used in Loader.tsx:18 but no corresponding @keyframes breathing definition exists in any CSS file, Tailwind configuration, or the imported Kleros UI theme. This animation will not work. Add the animation definition to web/src/global.css or configure it via a Tailwind theme extension.

🤖 Prompt for AI Agents
In `@web/src/components/Loader.tsx` around lines 17 - 19, The Loader.tsx uses a
non-existent "animate-breathing" class on the KlerosIcon; add a breathing
animation definition and wire it up: either add a `@keyframes` breathing and a
.animate-breathing class to web/src/global.css (so Loader.tsx's KlerosIcon will
animate) or add an "breathing" animation entry to the Tailwind config's
theme.extend.animation and theme.extend.keyframes and rebuild so the class
animate-breathing becomes available; update global.css or tailwind config rather
than changing Loader.tsx.

Comment on lines +7 to +10
<div className="flex flex-wrap justify-between items-center gap-2 mt-fluid-4-8 mb-fluid-16-32">
<Stats {...{ totalTransactions, resolvedTransactions }} />
<Filters />
</Container>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for Tailwind config files and custom fluid class definitions
echo "=== Searching for Tailwind config files ==="
fd -e "config.ts" -e "config.js" -e "tailwind.config.ts" -e "tailwind.config.js" | head -20

echo ""
echo "=== Searching for fluid spacing class definitions ==="
rg "fluid-4-8|fluid-16-32" -t js -t ts -t json --max-count 20

Repository: kleros/escrow-v2

Length of output: 487


🏁 Script executed:

#!/bin/bash
echo "=== Searching for Tailwind config in web directory ==="
find web -name "*.config.*" -o -name "tailwind*" | grep -v node_modules

echo ""
echo "=== Searching for fluid class definitions in CSS/SCSS files ==="
rg "fluid-4-8|fluid-16-32|fluid-" -t css -t scss --max-count 30

echo ""
echo "=== Checking for global CSS or styles directory ==="
fd -e "css" -e "scss" web/src --max-depth 3 | head -20

echo ""
echo "=== Checking package.json for Tailwind ==="
cat web/package.json | grep -A 5 -B 5 "tailwind\|postcss" || echo "No tailwind/postcss found in package.json"

Repository: kleros/escrow-v2

Length of output: 1347


🏁 Script executed:

#!/bin/bash
echo "=== Reading vite.config.js ==="
cat web/vite.config.js

echo ""
echo "=== Searching for postcss config ==="
find web -name "postcss.config.*" 2>/dev/null

echo ""
echo "=== Searching for CSS input file (typically index.css or globals.css) ==="
fd . web/src -type f -name "*.css" | head -20

echo ""
echo "=== Checking for any Tailwind directives in source files ==="
rg "@tailwind|@layer|@apply" web/src --max-count 10

Repository: kleros/escrow-v2

Length of output: 1319


🏁 Script executed:

#!/bin/bash
echo "=== Reading global.css ==="
cat web/src/global.css

echo ""
echo "=== Searching for all CSS files ==="
fd -type f -name "*.css" web/src

echo ""
echo "=== Searching for tailwind.config in project root and web ==="
find . -maxdepth 3 -name "tailwind.config.*" 2>/dev/null

echo ""
echo "=== Searching for `@layer` definitions ==="
rg "@layer" web/src --max-count 20

Repository: kleros/escrow-v2

Length of output: 6861


Custom fluid spacing classes are not defined as Tailwind utilities and will not work.

The code uses mt-fluid-4-8, mb-fluid-16-32, and gap-fluid-4-8 as Tailwind class names, but these are not configured in the Tailwind utilities. While web/src/global.css defines CSS variables like --spacing-fluid-4-8 and --spacing-fluid-16-32 in the @theme block, Tailwind needs explicit utility class mappings to use these variables.

Either:

  1. Add Tailwind utility definitions in global.css or tailwind.config.ts that map these classes to the CSS variables, or
  2. Use the CSS variables directly with style attributes

Without this configuration, these classes will be ignored by Tailwind and produce no styling at runtime.

🤖 Prompt for AI Agents
In `@web/src/components/TransactionsDisplay/StatsAndFilters.tsx` around lines 7 -
10, The custom Tailwind-like classes (mt-fluid-4-8, mb-fluid-16-32,
gap-fluid-4-8) used in the TransactionsDisplay/StatsAndFilters component won't
be generated by Tailwind; either create proper utility mappings for those
classes (e.g., add utilities in tailwind.config.ts or extend global.css with
`@layer` utilities that map .mt-fluid-4-8, .mb-fluid-16-32, .gap-fluid-4-8 to the
corresponding CSS variables --spacing-fluid-4-8 and --spacing-fluid-16-32) or
remove the custom class names and apply the variables directly via style
attributes on the div (style={{ marginTop: 'var(--spacing-fluid-4-8)',
marginBottom: 'var(--spacing-fluid-16-32)', gap: 'var(--spacing-fluid-4-8)' }}),
then keep the JSX using <Stats {...{ totalTransactions, resolvedTransactions }}
/> and <Filters /> unchanged.

Comment on lines +1 to +6
@import "../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
@tailwind utilities;
@source "../../node_modules/@kleros/ui-components-library";
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check for Tailwind version in package.json
fd package.json -t f | head -5 | xargs grep -l "tailwind" 2>/dev/null || echo "Searching for package.json..."
find . -maxdepth 2 -name "package.json" -type f | head -1 | xargs cat

Repository: kleros/escrow-v2

Length of output: 3744


🏁 Script executed:

# Look for tailwind config files
fd "tailwind.config" -t f

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Check the current global.css file
cat -n web/src/global.css

Repository: kleros/escrow-v2

Length of output: 7680


🏁 Script executed:

# Check if there are other CSS files that might show the pattern
fd "\.css$" web/src -t f | head -10

Repository: kleros/escrow-v2

Length of output: 155


🏁 Script executed:

# Find web package.json
find . -path "*/web/package.json" -type f | head -1 | xargs cat

Repository: kleros/escrow-v2

Length of output: 3990


🏁 Script executed:

# Also check for tailwind config in web directory
fd "tailwind" web/ -type f

Repository: kleros/escrow-v2

Length of output: 230


🏁 Script executed:

# Check if there's a monorepo structure
git ls-files | grep -E "package\.json$" | head -10

Repository: kleros/escrow-v2

Length of output: 212


🏁 Script executed:

# Check for tailwind config in web directory
fd . web -name "tailwind.config*" -o -name "postcss.config*" 2>/dev/null

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Also check for vite config that might have Tailwind plugin setup
fd . web -name "vite.config*" 2>/dev/null | head -5 | xargs cat

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# List web directory structure to find config files
ls -la web/ | head -20

Repository: kleros/escrow-v2

Length of output: 1067


🏁 Script executed:

# Check if there's a vite config file
find web -maxdepth 1 -type f -name "*.config.*"

Repository: kleros/escrow-v2

Length of output: 98


🏁 Script executed:

# Look for any PostCSS or Tailwind configuration files
find web -maxdepth 2 -type f \( -name "*.config.*" -o -name ".tailwindrc*" -o -name "postcss.config*" \)

Repository: kleros/escrow-v2

Length of output: 98


🏁 Script executed:

# Check vite config which should have Tailwind setup
cat web/vite.config.js

Repository: kleros/escrow-v2

Length of output: 886


🏁 Script executed:

# Check if there's a postcss.config at the root
find . -maxdepth 1 -name "postcss.config*" -o -name "tailwind.config*"

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Let me search for any Tailwind-related documentation or examples in the project
rg "@import.*tailwindcss" --type css -A 3 -B 3

Repository: kleros/escrow-v2

Length of output: 436


🏁 Script executed:

# Check if there are any other CSS files that show Tailwind setup patterns
rg "@theme|@source|@tailwind|@custom-variant" web/src -t css -l

Repository: kleros/escrow-v2

Length of output: 78


🏁 Script executed:

# Check if there's a biome or linting config that would flag this error
find . -maxdepth 2 -name "biome.json" -o -name ".biomeignore" -o -name "eslint.config*" | xargs cat 2>/dev/null

Repository: kleros/escrow-v2

Length of output: 1834


🏁 Script executed:

# Check if there are any references to Tailwind v4 guidelines in docs or comments
rg "tailwind|v4" . --type md -l | head -10

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Check Tailwind v4 documentation patterns - look for any setup examples in the repo
rg "Tailwind" . --type md -B 2 -A 2

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Verify the exact package being imported from ui-components-library
cat web/package.json | grep "@kleros/ui-components-library"

Repository: kleros/escrow-v2

Length of output: 106


🏁 Script executed:

# Verify package.json has `@tailwindcss/vite` plugin which is v4 specific
grep "@tailwindcss/vite" web/package.json

Repository: kleros/escrow-v2

Length of output: 95


🏁 Script executed:

# Double-check the current state of global.css lines 1-6 to ensure accuracy
sed -n '1,6p' web/src/global.css

Repository: kleros/escrow-v2

Length of output: 294


Fix invalid @import ordering and align with Tailwind v4 pattern.

Per CSS specification, all @import statements must come before other at-rules. Currently, line 4's @import "tailwindcss"; violates this rule by appearing after @tailwind utilities; on line 2. Additionally, Tailwind v4 no longer uses @tailwind directives—the single @import "tailwindcss"; entrypoint provides all layers automatically.

Proposed fix
-@import "../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
-@tailwind utilities;
-@source "../../node_modules/@kleros/ui-components-library";
-@import "tailwindcss";
+@import "tailwindcss";
+@import "@kleros/ui-components-library/dist/assets/theme.css";
+@source "@kleros/ui-components-library";
 `@custom-variant` dark (&:where(.dark, .dark *));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@import "../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
@tailwind utilities;
@source "../../node_modules/@kleros/ui-components-library";
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
`@import` "tailwindcss";
`@import` "@kleros/ui-components-library/dist/assets/theme.css";
`@source` "@kleros/ui-components-library";
`@custom-variant` dark (&:where(.dark, .dark *));
🧰 Tools
🪛 Biome (2.1.2)

[error] 4-4: This @import is in the wrong position.

Any @import rules must precede all other valid at-rules and style rules in a stylesheet (ignoring @charset and @layer), or else the @import rule is invalid.
Consider moving import position.

(lint/correctness/noInvalidPositionAtImportRule)

🤖 Prompt for AI Agents
In `@web/src/global.css` around lines 1 - 6, Move the Tailwind v4 entrypoint
import to be the very first `@import` and remove the legacy `@tailwind` utilities
directive: replace the current top lines so that `@import` "tailwindcss"; appears
before any other at-rules or rules, then have `@import`
"../../node_modules/@kleros/ui-components-library/dist/assets/theme.css"; (and
keep the `@source` if needed) after it, and remove the now-invalid `@tailwind`
utilities; line; ensure all `@import` statements come before the `@custom-variant`
dark (&:where(.dark, .dark *)); rule.

Comment on lines +51 to +58
className={clsx(
"flex flex-col absolute max-h-[80vh] w-[86vw] max-w-[444px]",
"overflow-y-auto z-1 p-3 pb-6",
"top-[5%] left-1/2 transform -translate-x-1/2",
"border border-klerosUIComponentsStroke rounded-base",
"bg-klerosUIComponentsWhiteBackground shadow-custom",
"lg:mt-16 lg:top-0 lg:right-0 lg:left-auto lg:transform-none lg:translate-x-0 lg:max-w-[260px]"
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find Tailwind configuration file
fd -t f "tailwind\.config\.(js|ts)" .

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Check package.json for Tailwind version
cat package.json | grep -A 5 "tailwindcss"

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# View the actual file in question
cat -n web/src/layout/Header/navbar/Menu/Help.tsx | sed -n '45,85p'

Repository: kleros/escrow-v2

Length of output: 1755


🏁 Script executed:

# Search for Tailwind config in src and all directories
fd -t f "tailwind" . --type f

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Check package.json directly
cat package.json

Repository: kleros/escrow-v2

Length of output: 1897


🏁 Script executed:

# Search for Tailwind utility definitions in the codebase
rg "z-1|leading-18px" --type jsx --type tsx --type ts -B 2 -A 2

Repository: kleros/escrow-v2

Length of output: 87


🏁 Script executed:

# Check web workspace package.json for Tailwind
cat web/package.json | grep -A 2 tailwindcss

Repository: kleros/escrow-v2

Length of output: 250


🏁 Script executed:

# Search for Tailwind config in web workspace
fd -t f "tailwind" web/

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Search for z-1 and leading-18px definitions
rg "z-1|leading-18px" web/ -t ts -t tsx -B 2 -A 2

Repository: kleros/escrow-v2

Length of output: 87


🏁 Script executed:

# Search for z-1 and leading-18px usage without type restrictions
rg "z-1|leading-18px" web/

Repository: kleros/escrow-v2

Length of output: 1018


🏁 Script executed:

# Look for Tailwind config with any extension
find web -name "*tailwind*" -o -name "*.config.*" | head -20

Repository: kleros/escrow-v2

Length of output: 98


🏁 Script executed:

# Check for CSS files that might define custom utilities
fd "\.(css|scss)" web/src -x grep -l "z-1\|leading-18px" {} \;

Repository: kleros/escrow-v2

Length of output: 78


🏁 Script executed:

# Check the global.css file for custom utility definitions
cat web/src/global.css

Repository: kleros/escrow-v2

Length of output: 6504


🏁 Script executed:

# Search for z-1 definition in CSS files
rg "z-1|z-\[1\]" web/src --type css --type scss -B 2 -A 2

Repository: kleros/escrow-v2

Length of output: 88


🏁 Script executed:

# Check if there's a custom Tailwind config inline or in CSS
cat web/vite.config.js

Repository: kleros/escrow-v2

Length of output: 886


Use Tailwind v4-compatible arbitrary values for z-index and line-height.

In Tailwind v4.1.17, z-1 is not a standard utility (defaults are z-0, z-10, z-20, etc.), and while --leading-18px is defined as a CSS variable in the theme, it requires explicit arbitrary value syntax in Tailwind v4. Replace with z-[1] and leading-[18px].

Required changes

Line 53 (Help.tsx):

-          "overflow-y-auto z-1 p-3 pb-6",
+          "overflow-y-auto z-[1] p-3 pb-6",

Line 74 (Help.tsx):

-                "text-base leading-18px text-klerosUIComponentsPrimaryText",
+                "text-base leading-[18px] text-klerosUIComponentsPrimaryText",

Also applies to other files using z-1:

  • web/src/layout/Header/navbar/index.tsx
  • web/src/layout/Header/navbar/Menu/Settings/index.tsx
  • web/src/layout/Header/navbar/DappList.tsx
🤖 Prompt for AI Agents
In `@web/src/layout/Header/navbar/Menu/Help.tsx` around lines 51 - 58, The
Tailwind utilities `z-1` and the CSS variable `--leading-18px` are not valid in
Tailwind v4; update the className usages in the Help component (component Help)
to use `z-[1]` and `leading-[18px]` respectively, and apply the same
replacements in the Navbar index component (component Navbar/index), the Menu
Settings component (component Settings), and the DappList component (component
DappList) wherever `z-1` or `--leading-18px` appear so all arbitrary values use
Tailwind v4 bracket syntax.

import AmountField from "./AmountField";
import Buttons from "./Buttons";
import { StyledModal } from "../StyledModal";
import StyledModal from "../StyledModal";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find any remaining named imports of StyledModal
rg -n --glob='**/*.ts*' 'import\s*\{\s*StyledModal\s*\}\s*from\s*".*\/StyledModal"' web

Repository: kleros/escrow-v2

Length of output: 226


🏁 Script executed:

# Also check for any mixed imports (default + named)
rg -n --glob='**/*.ts*' 'import\s+StyledModal\s*,\s*\{.*\}\s*from\s*".*\/StyledModal"' web

Repository: kleros/escrow-v2

Length of output: 232


🏁 Script executed:

# Find the StyledModal component definition
fd -t f 'StyledModal' web --type f

Repository: kleros/escrow-v2

Length of output: 110


🏁 Script executed:

# Check the ProposeSettlementModal component around lines 22-23
cat -n web/src/pages/MyTransactions/Modal/ProposeSettlementModal/index.tsx | head -30

Repository: kleros/escrow-v2

Length of output: 1314


🏁 Script executed:

# Search for named imports (fixed regex)
rg -n --glob='**/*.ts*' 'import\s*\{[^}]*StyledModal[^}]*\}' web

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Examine the StyledModal component
cat -n web/src/pages/MyTransactions/Modal/StyledModal.tsx

Repository: kleros/escrow-v2

Length of output: 904


🏁 Script executed:

# Search all imports of StyledModal to verify they're all default imports
rg -n --glob='**/*.ts*' 'import.*StyledModal.*from' web

Repository: kleros/escrow-v2

Length of output: 696


🏁 Script executed:

# Check the Overlay component for dialog semantics
fd -t f 'Overlay' web --type f | grep -E '\.(tsx?|jsx?)$'

Repository: kleros/escrow-v2

Length of output: 206


🏁 Script executed:

# Check other modal files for heading structure and semantic approach
cat -n web/src/pages/MyTransactions/Modal/PaymentReleased/index.tsx | head -30

Repository: kleros/escrow-v2

Length of output: 1173


🏁 Script executed:

# Check RaiseDisputeModal too
cat -n web/src/pages/MyTransactions/Modal/RaiseDisputeModal/index.tsx | head -30

Repository: kleros/escrow-v2

Length of output: 1160


🏁 Script executed:

# Examine the Overlay component
cat -n web/src/components/Overlay.tsx

Repository: kleros/escrow-v2

Length of output: 459


🏁 Script executed:

# Check git history or comments about dialog semantics
rg -n 'role="dialog"' web

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

# Check for aria-labelledby usage
rg -n 'aria-labelledby' web

Repository: kleros/escrow-v2

Length of output: 42


Accessibility gap: Add role="dialog" and heading linkage to StyledModal

The default import migration is clean—no remaining named imports found. However, StyledModal currently lacks proper dialog semantics (role="dialog" and aria-labelledby linking headings). The raw <h1> on line 22 should be used to identify the dialog via aria-labelledby. Update StyledModal to accept and apply these accessibility attributes:

const StyledModal = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
  ({ className, children, ...props }, ref) => (
    <div
      ref={ref}
      role="dialog"
      className={...}
      {...props}
    >
      {children}
    </div>
  )
);

Then ensure callers set aria-labelledby on StyledModal pointing to the heading id.

🤖 Prompt for AI Agents
In `@web/src/pages/MyTransactions/Modal/ProposeSettlementModal/index.tsx` at line
6, StyledModal is missing dialog semantics; update its component to use
forwardRef (e.g., forwardRef<HTMLDivElement,
React.ComponentPropsWithoutRef<"div">>) and spread incoming props so it applies
role="dialog" and aria-labelledby from callers, e.g., accept ({ className,
children, ...props }, ref) and render <div ref={ref} role="dialog" {...props}>.
In the ProposeSettlementModal ensure the heading <h1> has a stable id (e.g.,
settlement-modal-title) and pass that id as aria-labelledby on StyledModal so
the dialog is linked to the heading.

Comment on lines +32 to 38
const {
data: proposeSettlementConfig,
isLoading,
isError,
} = useSimulateEscrowUniversalProposeSettlement({
args: [BigInt(id), parseEther(amountProposed)],
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "ProposeSettlementButton.tsx" -type f

Repository: kleros/escrow-v2

Length of output: 156


🏁 Script executed:

cat -n ./web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ProposeSettlementButton.tsx

Repository: kleros/escrow-v2

Length of output: 2764


🏁 Script executed:

find . -path "*/hooks/contracts/generated*" -type f | head -20

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

find . -name "generated.ts" -o -name "generated.tsx" -o -name "generated.js" | grep -i hook

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

find . -type d -name "hooks" | head -10

Repository: kleros/escrow-v2

Length of output: 88


🏁 Script executed:

ls -la ./web/src/hooks/

Repository: kleros/escrow-v2

Length of output: 1091


🏁 Script executed:

rg "useSimulateEscrowUniversalProposeSettlement|useWriteEscrowUniversalProposeSettlement" --type ts --type tsx -l

Repository: kleros/escrow-v2

Length of output: 87


🏁 Script executed:

rg "useSimulateEscrowUniversalProposeSettlement|useWriteEscrowUniversalProposeSettlement" -l

Repository: kleros/escrow-v2

Length of output: 154


🏁 Script executed:

rg "useSimulateEscrowUniversalProposeSettlement" -A 10

Repository: kleros/escrow-v2

Length of output: 2956


🏁 Script executed:

find . -path "*/hooks/contracts*" -type f -o -path "*/hooks/contracts*" -type d

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

find . -name "*generated*" | grep -i hook | head -20

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

ls -la ./web/src/hooks/

Repository: kleros/escrow-v2

Length of output: 1091


🏁 Script executed:

find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "export.*useSimulateEscrowUniversalProposeSettlement" 2>/dev/null

Repository: kleros/escrow-v2

Length of output: 44


🏁 Script executed:

find ./web/src -type d -name "contracts" 2>/dev/null

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

rg "export.*useSimulateEscrowUniversalProposeSettlement" -A 5

Repository: kleros/escrow-v2

Length of output: 42


🏁 Script executed:

rg "hooks/contracts/generated" -A 2 -B 2

Repository: kleros/escrow-v2

Length of output: 6725


Guard parseEther(amountProposed) against render-time throws and reset isSending on successful settlement.

The component throws during render if amountProposed is invalid—button disable state doesn't prevent this since the error occurs in the hook setup. Additionally, isSending is never reset to false when the settlement succeeds (line 48 calls toggleModal but omits setIsSending(false)), leaving the spinner stuck if toggleModal is undefined.

Proposed patch
+  const parsedAmount = React.useMemo(() => {
+    if (!isAmountValid) return null;
+    try {
+      return parseEther(amountProposed);
+    } catch {
+      return null;
+    }
+  }, [amountProposed, isAmountValid]);
+
   const {
     data: proposeSettlementConfig,
     isLoading,
     isError,
   } = useSimulateEscrowUniversalProposeSettlement({
-    args: [BigInt(id), parseEther(amountProposed)],
+    args: [BigInt(id), parsedAmount ?? 0n],
+    query: { enabled: isAmountValid && parsedAmount !== null },
   });
🤖 Prompt for AI Agents
In
`@web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ProposeSettlementButton.tsx`
around lines 32 - 38, Guard parseEther(amountProposed) so it cannot throw during
render by computing the parsed value outside the hook call (e.g., useMemo or
try/catch) and only pass args to useSimulateEscrowUniversalProposeSettlement
when amountProposed is a valid number/parsable string (otherwise pass undefined
or an empty args array); additionally, ensure isSending is reset to false on
successful settlement by calling setIsSending(false) after the success path
(alongside toggleModal if present) and defensively check toggleModal before
calling it to avoid leaving the spinner stuck.

{prevRoute === "/new-transaction/notifications" ? (
<DepositPaymentButton />
) : (
<NextButton nextRoute={nextRoute ?? ""} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the NextButton component
cat -n web/src/pages/NewTransaction/NavigationButtons/NextButton.tsx

Repository: kleros/escrow-v2

Length of output: 6117


🏁 Script executed:

cat -n web/src/pages/NewTransaction/NavigationButtons/index.tsx

Repository: kleros/escrow-v2

Length of output: 911


Ensure nextRoute is defined before passing to NextButton.

When nextRoute is undefined, passing an empty string to NextButton causes navigate("") to be called (lines 95, 105, 116, 136), which won't navigate to a meaningful route. Either ensure nextRoute is always defined before this component renders, or pass undefined and add validation in NextButton to handle missing navigation targets.

🤖 Prompt for AI Agents
In `@web/src/pages/NewTransaction/NavigationButtons/index.tsx` at line 18, The
code passes nextRoute ?? "" into NextButton which triggers navigate("") when
nextRoute is undefined; update the call site to pass nextRoute directly (not
coalesced to empty string) and/or update the NextButton component to validate
its prop: in NextButton (the component handling navigate calls) check if
nextRoute is truthy before calling navigate (and disable the button or no-op if
missing), and at the parent where NextButton is used prefer passing nextRoute
as-is so undefined propagates and can be handled inside NextButton.

@netlify
Copy link

netlify bot commented Jan 14, 2026

Deploy Preview for kleros-escrow-v2 ready!

Name Link
🔨 Latest commit de89ae4
🔍 Latest deploy log https://app.netlify.com/projects/kleros-escrow-v2/deploys/6969196a76fd4500085de1f9
😎 Deploy Preview https://deploy-preview-136--kleros-escrow-v2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

kyrers and others added 9 commits January 14, 2026 16:16
fix: improve logic for dispute handling in escrow mapping
…dates

Bumps the npm_and_yarn group with 2 updates in the / directory: [undici](https://github.com/nodejs/undici) and [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware).


Updates `undici` from 5.28.2 to 5.29.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](nodejs/undici@v5.28.2...v5.29.0)

Updates `webpack-dev-middleware` from 5.3.3 to 5.3.4
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](webpack/webpack-dev-middleware@v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 5.29.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack-dev-middleware
  dependency-version: 5.3.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <[email protected]>
…arn-4ac3f3f31e

chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates
…pdates

Bumps the npm_and_yarn group with 21 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `5.4.8` | `5.4.21` |
| [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) | `7.23.8` | `7.28.6` |
| [base-x](https://github.com/cryptocoinjs/base-x) | `3.0.9` | `3.0.11` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.11` | `1.1.12` |
| [braces](https://github.com/micromatch/braces) | `3.0.2` | `3.0.3` |
| [cipher-base](https://github.com/crypto-browserify/cipher-base) | `1.0.4` | `1.0.7` |
| [dset](https://github.com/lukeed/dset) | `3.1.3` | `3.1.4` |
| [express](https://github.com/expressjs/express) | `4.18.2` | `4.22.1` |
| [fast-loops](https://github.com/robinweser/fast-loops) | `1.1.3` | `1.1.4` |
| [follow-redirects](https://github.com/follow-redirects/follow-redirects) | `1.15.5` | `1.15.11` |
| [form-data](https://github.com/form-data/form-data) | `3.0.1` | `3.0.4` |
| [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) | `2.0.6` | `2.0.9` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `3.14.1` | `3.14.2` |
| [node-forge](https://github.com/digitalbazaar/forge) | `1.3.1` | `1.3.3` |
| [pbkdf2](https://github.com/browserify/pbkdf2) | `3.1.2` | `3.1.5` |
| [rollup](https://github.com/rollup/rollup) | `2.79.1` | `2.79.2` |
| [secp256k1](https://github.com/cryptocoinjs/secp256k1-node) | `4.0.3` | `4.0.4` |
| [sha.js](https://github.com/crypto-browserify/sha.js) | `2.4.11` | `2.4.12` |
| [tar](https://github.com/isaacs/node-tar) | `6.2.0` | `6.2.1` |
| [webpack-dev-server](https://github.com/webpack/webpack-dev-server) | `4.15.1` | `4.15.2` |
| [webpack](https://github.com/webpack/webpack) | `5.89.0` | `5.104.1` |



Updates `vite` from 5.4.8 to 5.4.21
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.21/packages/vite)

Updates `@babel/helpers` from 7.23.8 to 7.28.6
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.6/packages/babel-helpers)

Updates `base-x` from 3.0.9 to 3.0.11
- [Commits](cryptocoinjs/base-x@v3.0.9...v3.0.11)

Updates `brace-expansion` from 1.1.11 to 1.1.12
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@1.1.11...v1.1.12)

Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](micromatch/braces@3.0.2...3.0.3)

Updates `cipher-base` from 1.0.4 to 1.0.7
- [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md)
- [Commits](browserify/cipher-base@v1.0.4...v1.0.7)

Updates `dset` from 3.1.3 to 3.1.4
- [Release notes](https://github.com/lukeed/dset/releases)
- [Commits](lukeed/dset@v3.1.3...v3.1.4)

Updates `express` from 4.18.2 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](expressjs/express@4.18.2...v4.22.1)

Updates `fast-loops` from 1.1.3 to 1.1.4
- [Commits](https://github.com/robinweser/fast-loops/commits)

Updates `follow-redirects` from 1.15.5 to 1.15.11
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](follow-redirects/follow-redirects@v1.15.5...v1.15.11)

Updates `form-data` from 3.0.1 to 3.0.4
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](form-data/form-data@v3.0.1...v3.0.4)

Updates `http-proxy-middleware` from 2.0.6 to 2.0.9
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](chimurai/http-proxy-middleware@v2.0.6...v2.0.9)

Updates `js-yaml` from 3.14.1 to 3.14.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@3.14.1...3.14.2)

Updates `node-forge` from 1.3.1 to 1.3.3
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](digitalbazaar/forge@v1.3.1...v1.3.3)

Updates `path-to-regexp` from 0.1.7 to 0.1.12
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](pillarjs/path-to-regexp@v0.1.7...v0.1.12)

Updates `pbkdf2` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md)
- [Commits](browserify/pbkdf2@v3.1.2...v3.1.5)

Updates `qs` from 6.11.0 to 6.11.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](ljharb/qs@v6.11.0...v6.11.2)

Updates `rollup` from 2.79.1 to 2.79.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG-2.md)
- [Commits](rollup/rollup@v2.79.1...v2.79.2)

Updates `secp256k1` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/cryptocoinjs/secp256k1-node/releases)
- [Commits](cryptocoinjs/secp256k1-node@v4.0.3...v4.0.4)

Updates `send` from 0.18.0 to 0.19.2
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](pillarjs/send@0.18.0...0.19.2)

Updates `serve-static` from 1.15.0 to 1.16.3
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md)
- [Commits](expressjs/serve-static@v1.15.0...v1.16.3)

Updates `sha.js` from 2.4.11 to 2.4.12
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](browserify/sha.js@v2.4.11...v2.4.12)

Updates `tar` from 6.2.0 to 6.2.1
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](isaacs/node-tar@v6.2.0...v6.2.1)

Updates `webpack-dev-server` from 4.15.1 to 4.15.2
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v4.15.2/CHANGELOG.md)
- [Commits](webpack/webpack-dev-server@v4.15.1...v4.15.2)

Updates `webpack` from 5.89.0 to 5.104.1
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](webpack/webpack@v5.89.0...v5.104.1)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.21
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: "@babel/helpers"
  dependency-version: 7.28.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: base-x
  dependency-version: 3.0.11
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: braces
  dependency-version: 3.0.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: cipher-base
  dependency-version: 1.0.7
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: dset
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-loops
  dependency-version: 1.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: follow-redirects
  dependency-version: 1.15.11
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: form-data
  dependency-version: 3.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: node-forge
  dependency-version: 1.3.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-version: 0.1.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: pbkdf2
  dependency-version: 3.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: qs
  dependency-version: 6.11.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: rollup
  dependency-version: 2.79.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: secp256k1
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: send
  dependency-version: 0.19.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: serve-static
  dependency-version: 1.16.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: tar
  dependency-version: 6.2.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack-dev-server
  dependency-version: 4.15.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack
  dependency-version: 5.104.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <[email protected]>
…arn-938924a938

chore(deps): bump the npm_and_yarn group across 1 directory with 25 updates
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@web/package.json`:
- Around line 108-109: Remove the legacy "@tailwind utilities" directive from
the global CSS so only the new CSS-first import remains; locate the file that
currently has both `@tailwind` utilities and `@import` "tailwindcss", delete the
"@tailwind utilities" line, and verify there are no other "@tailwind" directives
remaining so Tailwind v4's `@import` "tailwindcss" is the sole loader.
🧹 Nitpick comments (1)
web/package.json (1)

78-78: Consider moving build-time dependencies to devDependencies.

@tailwindcss/vite is a Vite plugin used only during the build process and should be in devDependencies rather than dependencies. This doesn't affect functionality but keeps the dependency categorization accurate.

Suggested change

Move @tailwindcss/vite to the devDependencies section alongside other Vite plugins like vite-plugin-svgr.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a37001c and bdecf3e.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • web/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Dependabot
  • GitHub Check: Redirect rules - kleros-escrow-v2
  • GitHub Check: Header rules - kleros-escrow-v2
  • GitHub Check: Redirect rules - kleros-escrow-v2
  • GitHub Check: Header rules - kleros-escrow-v2
  • GitHub Check: Pages changed - kleros-escrow-v2
  • GitHub Check: Pages changed - kleros-escrow-v2
🔇 Additional comments (5)
web/package.json (5)

64-64: LGTM!

Patch version bump for Vite within the same minor version. This should include bug fixes and security patches without breaking changes.


73-73: LGTM!

The MDX editor addition aligns with the new MarkdownEditor component mentioned in the PR summary.


84-84: LGTM!

clsx is a lightweight runtime utility for conditional className composition, correctly placed in dependencies.


104-106: Good security practice with rehype-sanitize.

The addition of rehype-sanitize alongside rehype-raw is important for security when rendering user-provided markdown content. This prevents XSS attacks while still allowing raw HTML rendering where appropriate.


72-72: No action required for this dependency upgrade.

The @kleros/ui-components-library package is listed as a dependency but is not imported or used anywhere in the codebase. The major version upgrade (2.x → 3.x) has no impact on this project since the library is unused. No code changes are necessary to address breaking changes from this upgrade.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +108 to +109
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.17",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check Tailwind v4 configuration

# Look for the new `@import` "tailwindcss" syntax in CSS files
echo "=== Checking for `@import` 'tailwindcss' syntax ==="
rg -n '@import.*tailwindcss' web/src/ 2>/dev/null || echo "No matches found"

# Check for any legacy `@tailwind` directives that should be migrated
echo -e "\n=== Checking for legacy `@tailwind` directives ==="
rg -n '@tailwind (base|components|utilities)' web/src/ 2>/dev/null || echo "No matches found"

# Verify vite.config includes the Tailwind plugin
echo -e "\n=== Checking vite.config ==="
if [ -f web/vite.config.ts ]; then
  echo "--- web/vite.config.ts ---"
  cat web/vite.config.ts
elif [ -f web/vite.config.js ]; then
  echo "--- web/vite.config.js ---"
  cat web/vite.config.js
else
  echo "No vite.config found in web/"
fi

Repository: kleros/escrow-v2

Length of output: 1129


Remove legacy @tailwind directive to complete Tailwind v4 migration.

tailwind-merge and tailwindcss versions are correct, and vite.config.js properly uses the @tailwindcss/vite plugin. However, web/src/global.css contains both the new @import "tailwindcss" syntax (line 4) and a legacy @tailwind utilities directive (line 2). In Tailwind v4's CSS-first approach, the @import statement is the primary way to load Tailwind, and the legacy @tailwind directive should be removed to avoid conflicts and ensure the configuration aligns with v4 best practices.

🤖 Prompt for AI Agents
In `@web/package.json` around lines 108 - 109, Remove the legacy "@tailwind
utilities" directive from the global CSS so only the new CSS-first import
remains; locate the file that currently has both `@tailwind` utilities and `@import`
"tailwindcss", delete the "@tailwind utilities" line, and verify there are no
other "@tailwind" directives remaining so Tailwind v4's `@import` "tailwindcss" is
the sole loader.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants