Runs / transfer-form Batch Runs from different design-system versions should not be compared.
page.tsx As the agent left it; marked lines broke a rule.
Violations 67 counted, under 9 rules. “Not counted” marks a compiler error that restates a design-system violation, “Ignored” one the agent silenced with a comment.
Steps One row per model call, with the tools it asked for. An agent that reads the guidelines before writing shows it here; what a checker or submit_view answered is under its call.
Step Tools Tokens in Tokens out Cost 0 list_files, read_file 290 66 $0.0002 1 read_file, read_file 1,079 161 $0.0005 2 read_file, read_file 254 288 $0.0004 3
e02ef3974528
Output cap The provider's default 11 amount: number;
12 beneficiary: string;
13 iban: string;
14 reference: string;
15 fromAccount: string;
16 available: number;
17 };
18
19 const IBAN_PATTERN = /^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$/;
20
21 const currency = new Intl.NumberFormat("en-GB", {
22 style: "currency",
23 currency: data.currency,
24 minimumFractionDigits: 2,
25 maximumFractionDigits: 2,
26 });
27
28 function formatMoney(value: number): string {
29 return currency.format(value);
30 }
31
32 function parseAmount(raw: string): number | null {
33 const normalised = raw.replace(/\s/g, "").replace(",", ".");
34 if (normalised === "" || !/^\d*(\.\d*)?$/.test(normalised)) return null;
35 const value = Number(normalised);
36 return Number.isFinite(value) ? value : null;
37 }
38
39 function normaliseIban(raw: string): string {
40 return raw.replace(/\s+/g, "").toUpperCase();
41 }
42
43 function displayIban(raw: string): string {
44 return normaliseIban(raw).replace(/(.{4})/g, "$1 ").trim();
45 }
46
47 const pageStyle = css({
48 minHeight: "100vh",
49 display: "flex",
50 justifyContent: "center",
51 alignItems: "flex-start",
52 padding: "48px 20px",
53 background: "#f4f5f7",
54 color: "#101828",
55 });
56
57 const cardStyle = css({
58 width: "100%",
59 maxWidth: "480px",
60 background: "#ffffff",
61 borderRadius: "14px",
62 border: "1px solid #e4e7ec",
63 padding: "32px",
64 boxShadow: "0 1px 2px rgba(16, 24, 40, 0.04), 0 16px 40px -24px rgba(16, 24, 40, 0.3)",
65 });
66
67 const titleStyle = css({
68 margin: 0,
69 fontSize: "22px",
70 lineHeight: 1.3,
71 fontWeight: 600,
72 letterSpacing: "-0.01em",
73 });
74
75 const subtitleStyle = css({
76 margin: "6px 0 0",
77 fontSize: "14px",
78 lineHeight: 1.5,
79 color: "#667085",
80 });
81
82 const formStyle = css({
83 display: "flex",
84 flexDirection: "column",
85 gap: "20px",
86 marginTop: "24px",
87 });
88
89 const balanceStyle = css({
90 display: "inline-flex",
91 alignItems: "center",
92 gap: "6px",
93 marginTop: "8px",
94 padding: "4px 10px",
95 borderRadius: "999px",
96 background: "#eef2ff",
97 color: "#3538cd",
98 fontSize: "13px",
99 fontWeight: 600,
100 });
101
102 const balanceDotStyle = css({
103 width: "6px",
104 height: "6px",
105 borderRadius: "999px",
106 background: "currentColor",
107 });
108
109 const fieldHintStyle = css({
110 margin: "8px 0 0",
111 fontSize: "13px",
112 lineHeight: 1.4,
113 color: "#667085",
114 });
115
116 const actionsStyle = css({
117 display: "flex",
118 flexDirection: "column",
119 gap: "12px",
120 marginTop: "4px",
121 });
122
123 const footnoteStyle = css({
124 margin: 0,
125 fontSize: "12px",
126 lineHeight: 1.5,
127 color: "#98a2b3",
128 textAlign: "center",
129 });
130
131 const successWrapStyle = css({
132 display: "flex",
133 flexDirection: "column",
134 gap: "20px",
135 });
136
137 const successBadgeStyle = css({
138 width: "44px",
139 height: "44px",
140 display: "flex",
141 alignItems: "center",
142 justifyContent: "center",
143 borderRadius: "999px",
144 background: "#ecfdf3",
145 color: "#027a48",
146 fontSize: "20px",
147 fontWeight: 700,
148 });
149
150 const summaryStyle = css({
151 display: "flex",
152 flexDirection: "column",
153 gap: "12px",
154 margin: 0,
155 padding: "16px",
156 borderRadius: "10px",
157 background: "#f9fafb",
158 border: "1px solid #eaecf0",
159 });
160
161 const summaryRowStyle = css({
162 display: "flex",
163 alignItems: "baseline",
164 justifyContent: "space-between",
165 gap: "16px",
166 });
167
168 const summaryLabelStyle = css({
169 margin: 0,
170 fontSize: "13px",
171 lineHeight: 1.4,
172 color: "#667085",
173 });
174
175 const summaryValueStyle = css({
176 margin: 0,
177 fontSize: "14px",
178 fontWeight: 600,
179 lineHeight: 1.4,
180 textAlign: "right",
181 wordBreak: "break-word",
182 });
183
184 export default function SepaTransferPage() {
185 const [accountId, setAccountId] = useState<string>(data.defaultAccountId);
186 const [beneficiary, setBeneficiary] = useState("");
187 const [iban, setIban] = useState("");
188 const [amount, setAmount] = useState("");
189 const [reference, setReference] = useState("");
190 const [touched, setTouched] = useState({
191 beneficiary: false,
192 iban: false,
193 amount: false,
194 });
195 const [submitAttempted, setSubmitAttempted] = useState(false);
196 const [confirmation, setConfirmation] = useState<Confirmation | null>(null);
197
198 const account: Account | undefined = useMemo(
199 () => data.accounts.find((item) => item.id === accountId),
200 [accountId],
201 );
202
203 const available = account?.available ?? 0;
204 const parsedAmount = parseAmount(amount);
205 const normalisedIban = normaliseIban(iban);
206
207 const beneficiaryError =
208 beneficiary.trim() === "" ? "Enter the beneficiary name." : null;
209
210 const ibanError =
211 normalisedIban === ""
212 ? "Enter the beneficiary IBAN."
213 : IBAN_PATTERN.test(normalisedIban)
214 ? null
215 : "Enter a valid IBAN, for example IT60 X054 2811 1010 0000 0123 456.";
216
217 const amountError =
218 parsedAmount === null
219 ? amount.trim() === ""
220 ? "Enter an amount."
221 : "Enter a valid amount, for example 150.00."
222 : parsedAmount <= 0
223 ? "Enter an amount greater than zero."
224 : parsedAmount > available
225 ? `The amount is higher than the available balance (${formatMoney(available)}).`
226 : null;
227
228 const canSubmit = beneficiaryError === null && ibanError === null && amountError === null;
229
230 const showBeneficiaryError = (touched.beneficiary || submitAttempted) && beneficiaryError !== null;
231 const showIbanError = (touched.iban || submitAttempted) && ibanError !== null;
232 const showAmountError = (touched.amount || submitAttempted) && amountError !== null;
233
234 function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
235 event.preventDefault();
236 setSubmitAttempted(true);
237
238 if (!canSubmit || parsedAmount === null) return;
239
240 setConfirmation({
241 amount: parsedAmount,
242 beneficiary: beneficiary.trim(),
243 iban: normalisedIban,
244 reference: reference.trim(),
245 fromAccount: account ? `${account.name} · ${displayIban(account.iban)}` : "",
246 available,
247 });
248 }
249
250 function handleNewTransfer() {
251 setConfirmation(null);
252 setAccountId(data.defaultAccountId);
253 setBeneficiary("");
254 setIban("");
255 setAmount("");
256 setReference("");
257 setTouched({ beneficiary: false, iban: false, amount: false });
258 setSubmitAttempted(false);
259 }
260
261 if (confirmation) {
262 return (
263 <main className={pageStyle}>
264 <div className={cardStyle}>
265 <div className={successWrapStyle}>
266 <div className={successBadgeStyle} aria-hidden="true">
267 ✓
268 </div>
269 <div>
270 <h1 className={titleStyle}>Transfer sent</h1>
271 <p className={subtitleStyle}>
272 {formatMoney(confirmation.amount)} is on its way to {confirmation.beneficiary}.
273 </p>
274 </div>
275
276 <div className={summaryStyle} role="status">
277 <div className={summaryRowStyle}>
278 <p className={summaryLabelStyle}>Amount</p>
279 <p className={summaryValueStyle}>{formatMoney(confirmation.amount)}</p>
280 </div>
281 <div className={summaryRowStyle}>
282 <p className={summaryLabelStyle}>Beneficiary</p>
283 <p className={summaryValueStyle}>{confirmation.beneficiary}</p>
284 </div>
285 <div className={summaryRowStyle}>
286 <p className={summaryLabelStyle}>Beneficiary IBAN</p>
287 <p className={summaryValueStyle}>{displayIban(confirmation.iban)}</p>
288 </div>
289 <div className={summaryRowStyle}>
290 <p className={summaryLabelStyle}>From</p>
291 <p className={summaryValueStyle}>{confirmation.fromAccount}</p>
292 </div>
293 {confirmation.reference !== "" ? (
294 <div className={summaryRowStyle}>
295 <p className={summaryLabelStyle}>Reference</p>
296 <p className={summaryValueStyle}>{confirmation.reference}</p>
297 </div>
298 ) : null}
299 <div className={summaryRowStyle}>
300 <p className={summaryLabelStyle}>Remaining balance</p>
301 <p className={summaryValueStyle}>
302 {formatMoney(Math.max(confirmation.available - confirmation.amount, 0))}
303 </p>
304 </div>
305 </div>
306
307 <Button type="button" onClick={handleNewTransfer}>
308 Make another transfer
309 </Button>
310 </div>
311 </div>
312 </main>
313 );
314 }
315
316 return (
317 <main className={pageStyle}>
318 <div className={cardStyle}>
319 <h1 className={titleStyle}>New SEPA transfer</h1>
320 <p className={subtitleStyle}>
321 Send money to a beneficiary in the Single Euro Payments Area.
322 </p>
323
324 <form className={formStyle} onSubmit={handleSubmit} noValidate>
325 <Field label="Pay from">
326 <Select
327 value={accountId}
328 onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
329 setAccountId(event.target.value)
330 }
331 >
332 {data.accounts.map((item) => (
333 <option key={item.id} value={item.id}>
334 {item.name} · {displayIban(item.iban)}
335 </option>
336 ))}
337 </Select>
338 <p className={balanceStyle}>
339 <span className={balanceDotStyle} aria-hidden="true" />
340 Available balance {formatMoney(available)}
341 </p>
342 </Field>
343
344 <Field label="Beneficiary name" error={showBeneficiaryError ? beneficiaryError : undefined}>
345 <Input
346 value={beneficiary}
347 onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
348 setBeneficiary(event.target.value)
349 }
350 onBlur={() => setTouched((prev) => ({ ...prev, beneficiary: true }))}
351 placeholder="e.g. Marie Dupont"
352 autoComplete="off"
353 />
354 </Field>
355
356 <Field label="Beneficiary IBAN" error={showIbanError ? ibanError : undefined}>
357 <Input
358 value={iban}
359 onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
360 setIban(event.target.value.toUpperCase())
361 }
362 onBlur={() => setTouched((prev) => ({ ...prev, iban: true }))}
363 placeholder="IT60 X054 2811 1010 0000 0123 456"
364 autoComplete="off"
365 spellCheck={false}
366 />
367 </Field>
368
369 <Field label={`Amount (${data.currency})`} error={showAmountError ? amountError : undefined}>
370 <Input
371 value={amount}
372 onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
373 setAmount(event.target.value)
374 }
375 onBlur={() => setTouched((prev) => ({ ...prev, amount: true }))}
376 placeholder="0.00"
377 inputMode="decimal"
378 autoComplete="off"
379 />
380 {!showAmountError ? (
381 <p className={fieldHintStyle}>
382 You can send up to {formatMoney(available)} from this account.
383 </p>
384 ) : null}
385 </Field>
386
387 <Field label="Reference (optional)">
388 <Input
389 value={reference}
390 onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
391 setReference(event.target.value)
392 }
393 placeholder="e.g. Invoice 2024-042"
394 autoComplete="off"
395 maxLength={140}
396 />
397 </Field>
398
399 <div className={actionsStyle}>
400 <Button type="submit" disabled={!canSubmit}>
401 Send transfer
402 </Button>
403 <p className={footnoteStyle}>
404 Transfers are processed within one business day. Check the IBAN carefully,
405 transfers cannot be recalled.
406 </p>
407 </div>
408 </form>
409 </div>
410 </main>
411 );
412 }
413
`6px 0 0` is a raw length. Use a token.
raw-values/raw-color 15 design-system page.tsx:53:15 · #f4f5f7 `#f4f5f7` is a raw color. Use a semantic color token. page.tsx:54:10 · #101828 `#101828` is a raw color. Use a semantic color token. page.tsx:60:15 · #ffffff `#ffffff` is a raw color. Use a semantic color token. page.tsx:62:11 · 1px solid #e4e7ec `1px solid #e4e7ec` is a raw color. Use a semantic color token. page.tsx:64:14 · 0 1px 2px rgba(16, 24, 40, 0.04), 0 16px 40px -24px rgba(16, 24, 40, 0.3) `0 1px 2px rgba(16, 24, 40, 0.04), 0 16px 40px -24px rgba(16, 24, 40, 0.3)` is a raw color. Use a semantic color token. page.tsx:79:10 · #667085 `#667085` is a raw color. Use a semantic color token. page.tsx:96:15 · #eef2ff `#eef2ff` is a raw color. Use a semantic color token. page.tsx:97:10 · #3538cd `#3538cd` is a raw color. Use a semantic color token. page.tsx:113:10 · #667085 `#667085` is a raw color. Use a semantic color token. page.tsx:127:10 · #98a2b3 `#98a2b3` is a raw color. Use a semantic color token. page.tsx:144:15 · #ecfdf3 `#ecfdf3` is a raw color. Use a semantic color token. page.tsx:145:10 · #027a48 `#027a48` is a raw color. Use a semantic color token. page.tsx:157:15 · #f9fafb `#f9fafb` is a raw color. Use a semantic color token. page.tsx:158:11 · 1px solid #eaecf0 `1px solid #eaecf0` is a raw color. Use a semantic color token. page.tsx:172:10 · #667085 `#667085` is a raw color. Use a semantic color token. Every run that broke raw-values/raw-color lint/jsx-a11y(prefer-tag-over-role) 1 quality typecheck/TS2769 0 quality