-
Notifications
You must be signed in to change notification settings - Fork 6
Deploy to Production #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Deploy to Production #136
Conversation
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
also run linter
feat: add getters to interface
feat: add markdown features
docs(Escrow): typo
…s it prevents valid ENS names
…raton chore: tailwind and ui-component-library migration
WalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~90 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
⏰ 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)
🔇 Additional comments (5)
✏️ Tip: You can disable this entire section by setting Comment |
chore: fix typo in comment
There was a problem hiding this 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:setTokensshould accept an array.The parameter type is
ITokenbut line 43 passes an array (uniqueTokens). This should beIToken[].Proposed fix
- setTokens: (tokens: IToken) => void, + setTokens: (tokens: IToken[]) => void,web/src/layout/Header/navbar/Menu/Help.tsx (1)
43-46: FixcontainerReftyping (likely TS incompatibility withref+useClickAway).
useRef(null)produces a ref whosecurrentis typed as onlynull, which typically won’t type-check as adivref and may not satisfyreact-use’suseClickAwaytyping.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:insufficientBalancecompares different units (ETH string vs wei bigint)—this can allow deposits with insufficient funds.
Right nowsendingQuantity(parsed later viaparseEther/parseUnits) is compared againstnativeBalance.value/tokenBalanceraw values viaparseFloat(...), 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—verifysendingTokenalways uses 18 or use the token’s actual decimals.)web/src/components/PreviewCard/EscrowTimeline/index.tsx (1)
6-18: AddfeeTimeoutandsettlementTimeoutto theIEscrowTimelineinterface.The
EscrowTimelinecomponent destructuresfeeTimeoutandsettlementTimeout(lines 32-33) and passes them touseEscrowTimelineItems(lines 47-48), but theIEscrowTimelineinterface (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*.requestand always resetisSending(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-scopedisLoading/isErrorderived 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: ResetisSendingon 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()likeReleasePaymentButtondoes 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:isSendingnever resets on success → button can stay stuck loading/disabled.
On the success path you refetch but don’t clearisSending(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: Addaddresspresence check to wagmiquery.enabledconditions to prevent queries with undefined address.The
useBalanceanduseReadContracthooks are currently enabled based only on transaction type, ignoring thataddressfromuseAccount()is undefined until a wallet is connected. This causes force-casting of undefined to0x${string}. Explicitly guard both hooks with&& !!addressin 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:Reacttypes referenced without import after addingexport {}.
Bring React types into scope (or useimport("react").FC/SVGProps) soglobal.d.tsstill 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: FixisBuyercomputation;undefined === undefinedmakes ittrue(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 fromuser?.emailUpdateableAt!when it’s undefined.
isEmailUpdateablebecomesfalseifuser?.emailexists butuser?.emailUpdateableAtis 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 (brokenhref).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 anumber(e.g.,1.5), whileparseEther(amountProposed)returns abigintin 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 unnecessaryMath.ceil()and nullish coalescing ontotalPages.
totalPagesis already computed as a page count inTransactionsFetcher.tsx(line 76:Math.ceil(totalTransactions / transactionsPerPage)), and typed as a requirednumber. Passing it tonumPages={Math.ceil(totalPages ?? 0)}applies ceiling redundantly and the?? 0is unnecessary. Change to:numPages={totalPages}.web/src/layout/Header/navbar/DappList.tsx (1)
83-111: Tailwind tokenz-1is invalid—usez-[1]or define in config. Thez-1class 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 tokensw-fluid-300-480,px-fluid-8-24, androunded-baseare properly defined and working correctly.Additionally, the code has minor improvements:
- Type
containerRefasuseRef<HTMLDivElement | null>(null)for clarity- Simplify the callback:
useClickAway(containerRef, toggleIsDappListOpen)instead of wrapping with an arrow function- Remove the redundant
transformclass (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
refusage type-safe and avoids an unnecessary lambda wrapper. TheStyledModalcomponent is typed asforwardRef<HTMLDivElement, ...>, so the ref should be properly typed, andtoggleModalcan be passed directly touseClickAwaysince it already has the correct() => voidsignature.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 + guardJSON.parseagainst corrupted localStorage.Right now
containerRefis never attached to an element, souseClickAwaylikely won’t work as intended. Also, a badselectedTokenvalue 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/!heightchecks (treats0as “unset”).Use nullish checks so callers can intentionally pass
0without gettingw-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
quantityis empty,parseFloat("")returnsNaN, which bypasses both the "insufficient balance" and "amount is zero" checks. The code then incorrectly setssetHasSufficientNativeBalance(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:Termsno longer passes required props toDescription(likely typecheck + runtime issues).
DescriptionexpectsreceivingTokenandsendingToken(and uses them incryptoSwapSummary), 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 ifwalletAddressis 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 nestedTextFieldremains tabbable and focusable when hidden, and screen readers will still announce its content. Addpointer-events-nonewhen collapsed and usearia-hiddento 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:deliverableTextisstringbut handled as optional.
Either makedeliverableTextoptional inIDescription(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.appis hardcoded and will be deployed to production. The codebase already has environment variable infrastructure (.env.devnet.public,.env.testnet.public,.env.mainnet.public) and usesREACT_APP_DEPLOYMENTto differentiate builds. Add the court base URL as an environment variable and reference it here, following the pattern used for other external URLs likeREACT_APP_IPFS_GATEWAYand subgraph URLs.
10-25: Fix potential crash inbuttonLinkand add security flags towindow.open.Line 18 in
buttonLink's fallback branch unconditionally interpolatesdisputeRequest.id, which produces an invalid URL like.../#/cases/undefinedwhendisputeRequest?.idis falsy. Although the component conditionally renders only whendisputeRequest?.idexists, the useMemo executes before that check. Additionally,window.openon line 23 lacks security flags—usenoopener,noreferrerto prevent reverse tabnabbing, matching the pattern inMarkdownRenderer.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: AlignIPreviewCard.escrowTitlewithHeader(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 whendeadlineis 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 theurlquery 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: useBigInt(id)to matchExecuteTransactionButtonpattern.
ClaimFullPaymentButtonpassesargs: [id], but the identical componentExecuteTransactionButtonpassesargs: [BigInt(id)]. Since both useuseSimulateEscrowUniversalExecuteTransactionwith the same context value, consistency is required—wagmi/viem expectsbigintforuint256parameters. 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 missingexecuteTransactionConfig.requestbefore callingwriteContractAsync.The current guard only checks
!isUndefined(executeTransaction), butexecuteTransactionConfig.requestcan 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/awaitwrapper inwrapWithToast()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: GuardBigInt(id)andtimeOutBy*Config.requestto avoid runtime throws.
Right now a missing/invalididor 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: FixisBuyerhere too;undefined === undefinedmakes ittrue.
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
LightButtonwithtext=""is icon-only and requires anaria-label(or equivalent accessibility attribute) to identify it to assistive technologies. The component's interface doesn't currently support passingaria-label, so this either needs to be added toLightButtonor passed through as a spread prop.web/src/components/OverlayPortal.tsx-5-8 (1)
5-8: Changez-9999toz-[9999]to apply the z-index value correctly.The class
z-9999is 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 syntaxz-[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 ofLinkfor external URLs.React Router's
Linkcomponent is designed for internal navigation and will not correctly handle external URLs. Theurlfrom 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 inFooter/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 + settype="button"(current implementation is a subtle breaking change).Right now you drop all props except
onClick, despite extendingReact.ButtonHTMLAttributes<HTMLButtonElement>(e.g.,disabled,aria-*,type). Also, default buttontypecan 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: GuardparseZonedDateTime()against non-zonedDateValuetypes to prevent runtime errors.While
DatepickerwithminValueset to aZonedDateTimeandtimeprop typically emitsZonedDateTime, the function signature accepts the union typeDateValue(which includesCalendarDateandCalendarDateTime). CallingparseZonedDateTime(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,
sendingTokenmay be undefined during initial render, causing a crash on line 23 when accessing.symboland.logowithout 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: GateuseEnsNamehook calls with address validation.The addresses are cast to
0x${string}without validation, which allows invalid strings through the type system. In wagmi v2,useEnsNamewill throw errors and make noisy requests if passed an invalid address. Passundefinedunless the address is validated viavalidateAddress()(already available in the codebase), or use thequery: { 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
StatusBannertheme references--klerosUIComponentsWhiteBackground,--klerosUIComponentsPrimaryText, and--klerosUIComponentsSecondaryText(unprefixed), butmdxEditorStyles.cssandmarkdownRendererStyles.cssconsistently use the--color-prefixed versions (--color-klerosUIComponentsWhiteBackground, etc.). Since these variables are defined in the external@kleros/ui-components-librarypackage, 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: Allowingstyleattribute enables CSS-based attacks.The global allowlist for
styleattribute 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
stylefrom the global allowlist, or if inline styles are required, use a CSS sanitizer likecss.escapeor 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: Usinghiddenprevents the close animation from running.
On close, the wrapper becomeshiddenimmediately, so the innerscale-ytransition won’t be visible. Preferpointer-events-none+ opacity/visibility transitions instead of togglingdisplay.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> ); };
| <div className={cn(!width && "w-full", !height && "h-full", className)} style={{ width, height }}> | ||
| <KlerosIcon className="fill-klerosUIComponentsStroke animate-breathing" /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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/nullRepository: 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 -5Repository: 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 -60Repository: 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 20Repository: 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 10Repository: 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 20Repository: 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:
- Add Tailwind utility definitions in
global.cssortailwind.config.tsthat map these classes to the CSS variables, or - Use the CSS variables directly with
styleattributes
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.
| @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 *)); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 catRepository: kleros/escrow-v2
Length of output: 3744
🏁 Script executed:
# Look for tailwind config files
fd "tailwind.config" -t fRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
# Check the current global.css file
cat -n web/src/global.cssRepository: 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 -10Repository: kleros/escrow-v2
Length of output: 155
🏁 Script executed:
# Find web package.json
find . -path "*/web/package.json" -type f | head -1 | xargs catRepository: kleros/escrow-v2
Length of output: 3990
🏁 Script executed:
# Also check for tailwind config in web directory
fd "tailwind" web/ -type fRepository: kleros/escrow-v2
Length of output: 230
🏁 Script executed:
# Check if there's a monorepo structure
git ls-files | grep -E "package\.json$" | head -10Repository: 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/nullRepository: 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 catRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
# List web directory structure to find config files
ls -la web/ | head -20Repository: 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.jsRepository: 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 3Repository: 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 -lRepository: 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/nullRepository: 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 -10Repository: 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 2Repository: 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.jsonRepository: 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.cssRepository: 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.
| @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.
| 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]" | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 fRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
# Check package.json directly
cat package.jsonRepository: 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 2Repository: kleros/escrow-v2
Length of output: 87
🏁 Script executed:
# Check web workspace package.json for Tailwind
cat web/package.json | grep -A 2 tailwindcssRepository: 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 2Repository: 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 -20Repository: 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.cssRepository: 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 2Repository: 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.jsRepository: 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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find any remaining named imports of StyledModal
rg -n --glob='**/*.ts*' 'import\s*\{\s*StyledModal\s*\}\s*from\s*".*\/StyledModal"' webRepository: 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"' webRepository: kleros/escrow-v2
Length of output: 232
🏁 Script executed:
# Find the StyledModal component definition
fd -t f 'StyledModal' web --type fRepository: 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 -30Repository: kleros/escrow-v2
Length of output: 1314
🏁 Script executed:
# Search for named imports (fixed regex)
rg -n --glob='**/*.ts*' 'import\s*\{[^}]*StyledModal[^}]*\}' webRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
# Examine the StyledModal component
cat -n web/src/pages/MyTransactions/Modal/StyledModal.tsxRepository: 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' webRepository: 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 -30Repository: kleros/escrow-v2
Length of output: 1173
🏁 Script executed:
# Check RaiseDisputeModal too
cat -n web/src/pages/MyTransactions/Modal/RaiseDisputeModal/index.tsx | head -30Repository: kleros/escrow-v2
Length of output: 1160
🏁 Script executed:
# Examine the Overlay component
cat -n web/src/components/Overlay.tsxRepository: kleros/escrow-v2
Length of output: 459
🏁 Script executed:
# Check git history or comments about dialog semantics
rg -n 'role="dialog"' webRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
# Check for aria-labelledby usage
rg -n 'aria-labelledby' webRepository: 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.
| const { | ||
| data: proposeSettlementConfig, | ||
| isLoading, | ||
| isError, | ||
| } = useSimulateEscrowUniversalProposeSettlement({ | ||
| args: [BigInt(id), parseEther(amountProposed)], | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "ProposeSettlementButton.tsx" -type fRepository: kleros/escrow-v2
Length of output: 156
🏁 Script executed:
cat -n ./web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/ProposeSettlementButton.tsxRepository: kleros/escrow-v2
Length of output: 2764
🏁 Script executed:
find . -path "*/hooks/contracts/generated*" -type f | head -20Repository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
find . -name "generated.ts" -o -name "generated.tsx" -o -name "generated.js" | grep -i hookRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
find . -type d -name "hooks" | head -10Repository: 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 -lRepository: kleros/escrow-v2
Length of output: 87
🏁 Script executed:
rg "useSimulateEscrowUniversalProposeSettlement|useWriteEscrowUniversalProposeSettlement" -lRepository: kleros/escrow-v2
Length of output: 154
🏁 Script executed:
rg "useSimulateEscrowUniversalProposeSettlement" -A 10Repository: kleros/escrow-v2
Length of output: 2956
🏁 Script executed:
find . -path "*/hooks/contracts*" -type f -o -path "*/hooks/contracts*" -type dRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
find . -name "*generated*" | grep -i hook | head -20Repository: 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/nullRepository: kleros/escrow-v2
Length of output: 44
🏁 Script executed:
find ./web/src -type d -name "contracts" 2>/dev/nullRepository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
rg "export.*useSimulateEscrowUniversalProposeSettlement" -A 5Repository: kleros/escrow-v2
Length of output: 42
🏁 Script executed:
rg "hooks/contracts/generated" -A 2 -B 2Repository: 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 ?? ""} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the NextButton component
cat -n web/src/pages/NewTransaction/NavigationButtons/NextButton.tsxRepository: kleros/escrow-v2
Length of output: 6117
🏁 Script executed:
cat -n web/src/pages/NewTransaction/NavigationButtons/index.tsxRepository: 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.
✅ Deploy Preview for kleros-escrow-v2 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
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
There was a problem hiding this 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/viteis a Vite plugin used only during the build process and should be indevDependenciesrather thandependencies. This doesn't affect functionality but keeps the dependency categorization accurate.Suggested change
Move
@tailwindcss/viteto thedevDependenciessection alongside other Vite plugins likevite-plugin-svgr.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis 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!
clsxis a lightweight runtime utility for conditional className composition, correctly placed in dependencies.
104-106: Good security practice with rehype-sanitize.The addition of
rehype-sanitizealongsiderehype-rawis 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-librarypackage 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.
| "tailwind-merge": "^3.4.0", | ||
| "tailwindcss": "^4.1.17", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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/"
fiRepository: 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.
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
web/src/stylesandweb/src/components.styled-componentswith Tailwind CSS classes in various components.onClicktoonPressfor better consistency.subgraph/mappings/escrow.tsto improve event handling.Summary by CodeRabbit
Release Notes
New Features
Dependencies
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.