Runs / transfer-form Batch Runs from different design-system versions should not be compared.
The page A screenshot in each theme, as the benchmark took it.
page.tsx As the agent left it; marked lines broke a rule.
Violations 32 counted, under 7 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 read_file, list_files 1,069 104 $0.0002 1 write_file 1,474 4,425 $0.0024 2 stop 5,920 50 $0.0006
Output cap The provider's default 4 import { css } from "styled-system/css";
5 import data from "./fixture.json";
6
7 type Account = { id: string; name: string; iban: string; available: number };
8
9 const accounts: Account[] = data.accounts;
10 const currency: string = data.currency;
11
12 const money = new Intl.NumberFormat("en-IE", { style: "currency", currency });
13
14 const AMOUNT_PATTERN = /^\d+(\.\d{1,2})?$/;
15
16 function normalizeIban(value: string): string {
17 return value.replace(/\s+/g, "").toUpperCase();
18 }
19
20 function isPlausibleIban(value: string): boolean {
21 return /^[A-Z]{2}[0-9A-Z]{13,32}$/.test(normalizeIban(value));
22 }
23
24 function parseAmount(value: string): number | null {
25 const normalized = value.trim().replace(",", ".");
26 if (!AMOUNT_PATTERN.test(normalized)) return null;
27 const parsed = Number(normalized);
28 return parsed > 0 ? parsed : null;
29 }
30
31 const initialBalances: Record<string, number> = Object.fromEntries(
32 accounts.map((account) => [account.id, account.available]),
33 );
34
35 const styles = {
36 page: css({
37 maxWidth: "480px",
38 margin: "0 auto",
39 padding: "24px",
40 display: "flex",
41 flexDirection: "column",
42 gap: "16px",
43 fontFamily: "sans-serif",
44 }),
45 title: css({ fontSize: "xl", fontWeight: "bold", margin: 0 }),
46 form: css({ display: "flex", flexDirection: "column", gap: "16px" }),
47 field: css({ display: "flex", flexDirection: "column", gap: "4px" }),
48 label: css({ fontSize: "sm", fontWeight: "medium" }),
49 input: css({
50 padding: "8px 10px",
51 borderRadius: "md",
52 border: "1px solid",
53 borderColor: "gray.400",
54 fontSize: "md",
55 }),
56 inputInvalid: css({ borderColor: "red.600" }),
57 hint: css({ fontSize: "sm", color: "gray.600" }),
58 error: css({ fontSize: "sm", color: "red.600" }),
59 button: css({
60 padding: "10px 16px",
61 borderRadius: "md",
62 border: "none",
63 backgroundColor: "blue.600",
64 color: "white",
65 fontWeight: "medium",
66 cursor: "pointer",
67 _disabled: { backgroundColor: "gray.400", cursor: "not-allowed" },
68 }),
69 confirmation: css({
70 padding: "12px",
71 borderRadius: "md",
72 backgroundColor: "green.50",
73 border: "1px solid",
74 borderColor: "green.600",
75 }),
76 };
77
78 export default function SepaTransferPage() {
79 const [accountId, setAccountId] = useState<string>(data.defaultAccountId);
80 const [balances, setBalances] = useState<Record<string, number>>(initialBalances);
81 const [beneficiaryName, setBeneficiaryName] = useState("");
82 const [beneficiaryIban, setBeneficiaryIban] = useState("");
83 const [amount, setAmount] = useState("");
84 const [reference, setReference] = useState("");
85 const [confirmation, setConfirmation] = useState<{ amount: number; beneficiary: string } | null>(null);
86
87 const selectedAccount = accounts.find((account) => account.id === accountId) ?? accounts[0];
88 const available = balances[selectedAccount.id] ?? 0;
89
90 const parsedAmount = parseAmount(amount);
91 let amountError: string | null = null;
92 if (amount.trim() !== "" && parsedAmount === null) {
93 amountError = "Enter a valid amount greater than 0 (up to 2 decimals).";
94 } else if (parsedAmount !== null && parsedAmount > available) {
95 amountError = `Amount exceeds the available balance of ${money.format(available)}.`;
96 }
97
98 const canSubmit =
99 parsedAmount !== null &&
100 amountError === null &&
101 beneficiaryName.trim() !== "" &&
102 isPlausibleIban(beneficiaryIban);
103
104 function handleAccountChange(nextId: string) {
105 setAccountId(nextId);
106 setConfirmation(null);
107 }
108
109 function handleSubmit(event: FormEvent<HTMLFormElement>) {
110 event.preventDefault();
111 if (!canSubmit || parsedAmount === null) return;
112
113 setBalances((current) => ({
114 ...current,
115 [selectedAccount.id]: (current[selectedAccount.id] ?? 0) - parsedAmount,
116 }));
117 setConfirmation({ amount: parsedAmount, beneficiary: beneficiaryName.trim() });
118 setBeneficiaryName("");
119 setBeneficiaryIban("");
120 setAmount("");
121 setReference("");
122 }
123
124 return (
125 <main className={styles.page}>
126 <h1 className={styles.title}>SEPA transfer</h1>
127
128 {confirmation && (
129 <div role="status" className={styles.confirmation}>
130 Transfer of {money.format(confirmation.amount)} to {confirmation.beneficiary} has been sent.
131 </div>
132 )}
133
134 <form className={styles.form} onSubmit={handleSubmit} noValidate>
135 <div className={styles.field}>
136 <label className={styles.label} htmlFor="from-account">From account</label>
137 <select
138 id="from-account"
139 className={styles.input}
140 value={accountId}
141 onChange={(event) => handleAccountChange(event.target.value)}
142 >
143 {accounts.map((account) => (
144 <option key={account.id} value={account.id}>
145 {account.name} ({account.iban})
146 </option>
147 ))}
148 </select>
149 <span className={styles.hint}>Available balance: {money.format(available)}</span>
150 </div>
151
152 <div className={styles.field}>
153 <label className={styles.label} htmlFor="beneficiary-name">Beneficiary name</label>
154 <input
155 id="beneficiary-name"
156 className={styles.input}
157 type="text"
158 autoComplete="off"
159 required
160 value={beneficiaryName}
161 onChange={(event) => setBeneficiaryName(event.target.value)}
162 />
163 </div>
164
165 <div className={styles.field}>
166 <label className={styles.label} htmlFor="beneficiary-iban">Beneficiary IBAN</label>
167 <input
168 id="beneficiary-iban"
169 className={styles.input}
170 type="text"
171 autoComplete="off"
172 spellCheck={false}
173 required
174 value={beneficiaryIban}
175 onChange={(event) => setBeneficiaryIban(event.target.value)}
176 />
177 </div>
178
179 <div className={styles.field}>
180 <label className={styles.label} htmlFor="amount">Amount ({currency})</label>
181 <input
182 id="amount"
183 className={`${styles.input} ${amountError ? styles.inputInvalid : ""}`}
184 type="text"
185 inputMode="decimal"
186 autoComplete="off"
187 required
188 value={amount}
189 aria-invalid={amountError !== null}
190 aria-describedby={amountError ? "amount-error" : undefined}
191 onChange={(event) => setAmount(event.target.value)}
192 />
193 {amountError && (
194 <span id="amount-error" role="alert" className={styles.error}>
195 {amountError}
196 </span>
197 )}
198 </div>
199
200 <div className={styles.field}>
201 <label className={styles.label} htmlFor="reference">Reference (optional)</label>
202 <input
203 id="reference"
204 className={styles.input}
205 type="text"
206 autoComplete="off"
207 value={reference}
208 onChange={(event) => setReference(event.target.value)}
209 />
210 </div>
211
212 <button type="submit" className={styles.button} disabled={!canSubmit}>
213 Send transfer
214 </button>
215 </form>
216 </main>
217 );
218 }
219
`10px 16px` is a raw length. Use a token.
token-roles/palette 8 design-system page.tsx:53:18 · gray.400 `gray.400` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:56:36 · red.600 `red.600` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:57:38 · gray.600 `gray.600` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:58:39 · red.600 `red.600` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:63:22 · blue.600 `blue.600` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:67:35 · gray.400 `gray.400` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:72:22 · green.50 `green.50` is a palette shade: it ignores the theme. Use a semantic color token. page.tsx:74:18 · green.600 `green.600` is a palette shade: it ignores the theme. Use a semantic color token. Every run that broke token-roles/palette typecheck/TS18048 3 quality typecheck/TS2769 3 quality lint/jsx-a11y(prefer-tag-over-role) 1 quality