Runs / loan-contract 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 169 counted, under 7 rules. “Not counted” marks a compiler error that restates a design-system violation, “Ignored” one the agent silenced with a comment. The first 200 are listed.
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 826 65 $0.0003 1 read_file, read_file 770 235 $0.0005 2 read_file, read_file 283 789 $0.0010 3
Output cap The provider's default 3
4 type InstalmentStatus = "paid" | "due" | "upcoming";
5
6 type Contract = {
7 number: string;
8 product: string;
9 status: string;
10 borrower: string;
11 currency: string;
12 principal: number;
13 nominalRate: number;
14 apr: number;
15 termMonths: number;
16 monthlyPayment: number;
17 startDate: string;
18 outstanding: number;
19 };
20
21 type Instalment = {
22 number: number;
23 dueDate: string;
24 payment: number;
25 principal: number;
26 interest: number;
27 remaining: number;
28 status: InstalmentStatus;
29 };
30
31 const contract = data.contract as Contract;
32 const schedule = data.schedule as Instalment[];
33 const documents = data.documents as { name: string; signedAt: string }[];
34
35 const money = new Intl.NumberFormat("en-IE", {
36 style: "currency",
37 currency: contract.currency,
38 minimumFractionDigits: 2,
39 maximumFractionDigits: 2,
40 });
41
42 const dateFormat = new Intl.DateTimeFormat("en-GB", {
43 day: "numeric",
44 month: "short",
45 year: "numeric",
46 timeZone: "UTC",
47 });
48
49 const formatMoney = (value: number) => money.format(value);
50 const formatDate = (value: string) => dateFormat.format(new Date(value));
51 const formatRate = (value: number) => `${(value * 100).toFixed(2)}%`;
52
53 const paidInstalments = schedule.filter((item) => item.status === "paid");
54 const paidPrincipal = paidInstalments.reduce((sum, item) => sum + item.principal, 0);
55 const repaidShare = contract.principal > 0 ? (paidPrincipal / contract.principal) * 100 : 0;
56 const repaidShareLabel = `${repaidShare.toFixed(1)}%`;
57
58 const shownTotals = schedule.reduce(
59 (sum, item) => ({
60 payment: sum.payment + item.payment,
61 principal: sum.principal + item.principal,
62 interest: sum.interest + item.interest,
63 }),
64 { payment: 0, principal: 0, interest: 0 },
65 );
66
67 const contractStatusLabel =
68 contract.status.charAt(0).toUpperCase() + contract.status.slice(1);
69
70 /* ---------------------------------- styles --------------------------------- */
71
72 const pageStyle = css({
73 minHeight: "100dvh",
74 background: "#f5f6f8",
75 color: "#111827",
76 fontFamily:
77 "Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
78 padding: "32px 16px 80px",
79 md: { padding: "56px 32px 96px" },
80 });
81
82 const shell = css({
83 width: "100%",
84 maxWidth: "1060px",
85 marginInline: "auto",
86 display: "grid",
87 gap: "20px",
88 });
89
90 const card = css({
91 background: "#ffffff",
92 border: "1px solid #e6e8ec",
93 borderRadius: "18px",
94 boxShadow: "0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 28px -24px rgba(16, 24, 40, 0.45)",
95 padding: "22px",
96 md: { padding: "28px 32px" },
97 });
98
99 const headerTop = css({
100 display: "flex",
101 alignItems: "flex-start",
102 justifyContent: "space-between",
103 flexWrap: "wrap",
104 gap: "16px",
105 });
106
107 const eyebrow = css({
108 margin: 0,
109 fontSize: "11.5px",
110 fontWeight: 600,
111 letterSpacing: "0.09em",
112 textTransform: "uppercase",
113 color: "#6b7280",
114 });
115
116 const title = css({
117 margin: "8px 0 0",
118 fontSize: "28px",
119 lineHeight: 1.15,
120 fontWeight: 700,
121 letterSpacing: "-0.02em",
122 md: { fontSize: "34px" },
123 });
124
125 const contractNumber = css({
126 margin: "8px 0 0",
127 fontSize: "14px",
128 color: "#4b5563",
129 fontVariantNumeric: "tabular-nums",
130 });
131
132 const contractNumberValue = css({
133 fontWeight: 600,
134 color: "#111827",
135 letterSpacing: "0.01em",
136 });
137
138 const statusPill = css({
139 display: "inline-flex",
140 alignItems: "center",
141 gap: "7px",
142 padding: "6px 12px",
143 borderRadius: "999px",
144 fontSize: "13px",
145 fontWeight: 600,
146 border: "1px solid #a7f3d0",
147 background: "#ecfdf5",
148 color: "#047857",
149 whiteSpace: "nowrap",
150 });
151
152 const statusDot = css({
153 width: "7px",
154 height: "7px",
155 borderRadius: "999px",
156 background: "currentColor",
157 flexShrink: 0,
158 });
159
160 const balanceGrid = css({
161 marginTop: "22px",
162 paddingTop: "22px",
163 borderTop: "1px solid #eef0f3",
164 display: "grid",
165 gap: "18px",
166 md: { gridTemplateColumns: "minmax(0, 1fr) minmax(220px, 300px)", gap: "32px", alignItems: "end" },
167 });
168
169 const balanceLabel = css({
170 margin: 0,
171 fontSize: "13px",
172 fontWeight: 500,
173 color: "#6b7280",
174 });
175
176 const balanceAmount = css({
177 margin: "6px 0 0",
178 fontSize: "32px",
179 lineHeight: 1.1,
180 fontWeight: 700,
181 letterSpacing: "-0.02em",
182 fontVariantNumeric: "tabular-nums",
183 md: { fontSize: "38px" },
184 });
185
186 const balanceCaption = css({
187 margin: "10px 0 0",
188 fontSize: "13.5px",
189 color: "#6b7280",
190 });
191
192 const progressBlock = css({ display: "grid", gap: "9px" });
193
194 const progressTrack = css({
195 height: "8px",
196 width: "100%",
197 borderRadius: "999px",
198 background: "#eceef2",
199 overflow: "hidden",
200 });
201
202 const progressFill = css({
203 height: "100%",
204 borderRadius: "999px",
205 background: "linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%)",
206 });
207
208 const progressLegend = css({
209 display: "flex",
210 alignItems: "baseline",
211 justifyContent: "space-between",
212 gap: "12px",
213 fontSize: "12.5px",
214 color: "#6b7280",
215 });
216
217 const progressValue = css({ fontWeight: 600, color: "#111827" });
218
219 const sectionHeader = css({
220 display: "flex",
221 alignItems: "baseline",
222 justifyContent: "space-between",
223 flexWrap: "wrap",
224 gap: "8px 16px",
225 marginBottom: "18px",
226 });
227
228 const sectionTitle = css({
229 margin: 0,
230 fontSize: "17px",
231 fontWeight: 700,
232 letterSpacing: "-0.01em",
233 });
234
235 const sectionCaption = css({
236 margin: 0,
237 fontSize: "12.5px",
238 color: "#6b7280",
239 });
240
241 const termsGrid = css({
242 display: "grid",
243 gridTemplateColumns: "repeat(auto-fit, minmax(148px, 1fr))",
244 gap: "20px 28px",
245 margin: 0,
246 });
247
248 const termItem = css({ display: "grid", gap: "6px" });
249
250 const termLabel = css({
251 fontSize: "12.5px",
252 fontWeight: 500,
253 color: "#6b7280",
254 });
255
256 const termValue = css({
257 margin: 0,
258 fontSize: "17px",
259 fontWeight: 600,
260 letterSpacing: "-0.01em",
261 fontVariantNumeric: "tabular-nums",
262 });
263
264 const tableWrap = css({
265 overflowX: "auto",
266 marginInline: "-8px",
267 paddingInline: "8px",
268 });
269
270 const table = css({
271 width: "100%",
272 minWidth: "740px",
273 borderCollapse: "collapse",
274 fontSize: "14px",
275 });
276
277 const th = css({
278 padding: "10px 12px",
279 textAlign: "left",
280 fontSize: "11.5px",
281 fontWeight: 600,
282 letterSpacing: "0.06em",
283 textTransform: "uppercase",
284 color: "#6b7280",
285 borderBottom: "1px solid #e6e8ec",
286 whiteSpace: "nowrap",
287 });
288
289 const thRight = css({ textAlign: "right" });
290
291 const td = css({
292 padding: "12px",
293 borderBottom: "1px solid #f1f2f5",
294 verticalAlign: "middle",
295 });
296
297 const tdRight = css({ textAlign: "right", fontVariantNumeric: "tabular-nums" });
298
299 const tdIndex = css({ color: "#9ca3af", fontVariantNumeric: "tabular-nums", width: "44px" });
300
301 const tdStrong = css({ fontWeight: 600, fontVariantNumeric: "tabular-nums" });
302
303 const rowStyle = css({
304 _hover: { background: "#fafbfd" },
305 _last: { "& td": { borderBottom: "none" } },
306 });
307
308 const pillBase = {
309 display: "inline-flex",
310 alignItems: "center",
311 padding: "3px 10px",
312 borderRadius: "999px",
313 fontSize: "12px",
314 fontWeight: 600,
315 border: "1px solid transparent",
316 whiteSpace: "nowrap" as const,
317 };
318
319 const paidPill = css({ ...pillBase, background: "#ecfdf5", color: "#047857", borderColor: "#b7ebd3" });
320 const duePill = css({ ...pillBase, background: "#fff7ed", color: "#b45309", borderColor: "#fed7aa" });
321 const upcomingPill = css({ ...pillBase, background: "#f4f5f7", color: "#4b5563", borderColor: "#e3e5ea" });
322
323 const statusMeta: Record<InstalmentStatus, { label: string; pill: string }> = {
324 paid: { label: "Paid", pill: paidPill },
325 due: { label: "Due", pill: duePill },
326 upcoming: { label: "Upcoming", pill: upcomingPill },
327 };
328
329 const totalsRow = css({
330 "& td": {
331 paddingTop: "14px",
332 paddingBottom: "2px",
333 fontWeight: 600,
334 fontSize: "13px",
335 color: "#374151",
336 borderBottom: "none",
337 fontVariantNumeric: "tabular-nums",
338 },
339 });
340
341 const tableNote = css({
342 margin: "14px 0 0",
343 fontSize: "12.5px",
344 color: "#6b7280",
345 });
346
347 const documentList = css({
348 listStyle: "none",
349 margin: 0,
350 padding: 0,
351 display: "grid",
352 });
353
354 const documentItem = css({
355 display: "flex",
356 alignItems: "center",
357 gap: "14px",
358 padding: "14px 0",
359 borderBottom: "1px solid #f1f2f5",
360 _last: { borderBottom: "none", paddingBottom: 0 },
361 _first: { paddingTop: 0 },
362 });
363
364 const documentIcon = css({
365 width: "34px",
366 height: "34px",
367 borderRadius: "10px",
368 background: "#eef2ff",
369 color: "#3730a3",
370 display: "grid",
371 placeItems: "center",
372 flexShrink: 0,
373 });
374
375 const documentName = css({
376 margin: 0,
377 fontSize: "14.5px",
378 fontWeight: 600,
379 });
380
381 const documentDate = css({
382 margin: "2px 0 0",
383 fontSize: "12.5px",
384 color: "#6b7280",
385 });
386
387 const signedBadge = css({
388 marginLeft: "auto",
389 flexShrink: 0,
390 display: "inline-flex",
391 alignItems: "center",
392 gap: "6px",
393 fontSize: "12px",
394 fontWeight: 600,
395 color: "#047857",
396 background: "#ecfdf5",
397 border: "1px solid #b7ebd3",
398 borderRadius: "999px",
399 padding: "3px 10px",
400 whiteSpace: "nowrap",
401 });
402
403 /* ---------------------------------- page ---------------------------------- */
404
405 export default function LoanContractDetailPage() {
406 return (
407 <main className={pageStyle}>
408 <div className={shell}>
409 {/* Contract overview */}
410 <section className={card} aria-labelledby="contract-title">
411 <div className={headerTop}>
412 <div>
413 <p className={eyebrow}>Loan contract</p>
414 <h1 id="contract-title" className={title}>
415 {contract.product}
416 </h1>
417 <p className={contractNumber}>
418 Contract number <span className={contractNumberValue}>{contract.number}</span>
419 </p>
420 </div>
421 <span className={statusPill}>
422 <span className={statusDot} aria-hidden="true" />
423 {contractStatusLabel}
424 </span>
425 </div>
426
427 <div className={balanceGrid}>
428 <div>
429 <p className={balanceLabel}>Outstanding balance</p>
430 <p className={balanceAmount}>{formatMoney(contract.outstanding)}</p>
431 <p className={balanceCaption}>
432 {formatMoney(paidPrincipal)} of {formatMoney(contract.principal)} principal repaid
433 </p>
434 </div>
435
436 <div className={progressBlock}>
437 <div
438 className={progressTrack}
439 role="progressbar"
440 aria-label="Principal repaid"
441 aria-valuemin={0}
442 aria-valuemax={100}
443 aria-valuenow={Math.round(repaidShare)}
444 aria-valuetext={repaidShareLabel}
445 >
446 <div
447 className={progressFill}
448 style={{ width: `${Math.min(100, Math.max(0, repaidShare))}%` }}
449 />
450 </div>
451 <div className={progressLegend}>
452 <span className={progressValue}>{repaidShareLabel} repaid</span>
453 <span>
454 {paidInstalments.length} of {contract.termMonths} instalments
455 </span>
456 </div>
457 </div>
458 </div>
459 </section>
460
461 {/* Key terms */}
462 <section className={card} aria-labelledby="terms-title">
463 <div className={sectionHeader}>
464 <h2 id="terms-title" className={sectionTitle}>
465 Key terms
466 </h2>
467 <p className={sectionCaption}>Fixed rate, equal monthly instalments</p>
468 </div>
469
470 <dl className={termsGrid}>
471 <div className={termItem}>
472 <dt className={termLabel}>Amount borrowed</dt>
473 <dd className={termValue}>{formatMoney(contract.principal)}</dd>
474 </div>
475 <div className={termItem}>
476 <dt className={termLabel}>Nominal interest rate</dt>
477 <dd className={termValue}>{formatRate(contract.nominalRate)}</dd>
478 </div>
479 <div className={termItem}>
480 <dt className={termLabel}>APR</dt>
481 <dd className={termValue}>{formatRate(contract.apr)}</dd>
482 </div>
483 <div className={termItem}>
484 <dt className={termLabel}>Term</dt>
485 <dd className={termValue}>{contract.termMonths} months</dd>
486 </div>
487 <div className={termItem}>
488 <dt className={termLabel}>Monthly payment</dt>
489 <dd className={termValue}>{formatMoney(contract.monthlyPayment)}</dd>
490 </div>
491 <div className={termItem}>
492 <dt className={termLabel}>Start date</dt>
493 <dd className={termValue}>{formatDate(contract.startDate)}</dd>
494 </div>
495 <div className={termItem}>
496 <dt className={termLabel}>Still owed</dt>
497 <dd className={termValue}>{formatMoney(contract.outstanding)}</dd>
498 </div>
499 </dl>
500 </section>
501
502 {/* Repayment schedule */}
503 <section className={card} aria-labelledby="schedule-title">
504 <div className={sectionHeader}>
505 <h2 id="schedule-title" className={sectionTitle}>
506 Repayment schedule
507 </h2>
508 <p className={sectionCaption}>
509 Instalments {schedule.length > 0 ? schedule[0].number : 0}–
510 {schedule.length > 0 ? schedule[schedule.length - 1].number : 0} of{" "}
511 {contract.termMonths}
512 </p>
513 </div>
514
515 <div className={tableWrap}>
516 <table className={table}>
517 <caption className={sectionCaption} style={{ textAlign: "left", paddingBottom: 10 }}>
518 Monthly instalments with capital and interest split
519 </caption>
520 <thead>
521 <tr>
522 <th scope="col" className={th}>
523 #
524 </th>
525 <th scope="col" className={th}>
526 Due date
527 </th>
528 <th scope="col" className={thRight}>
529 Payment
530 </th>
531 <th scope="col" className={thRight}>
532 Principal
533 </th>
534 <th scope="col" className={thRight}>
535 Interest
536 </th>
537 <th scope="col" className={thRight}>
538 Balance after
539 </th>
540 <th scope="col" className={th}>
541 Status
542 </th>
543 </tr>
544 </thead>
545 <tbody>
546 {schedule.map((instalment) => {
547 const meta = statusMeta[instalment.status] ?? statusMeta.upcoming;
548 return (
549 <tr key={instalment.number} className={rowStyle}>
550 <td className={tdIndex}>{instalment.number}</td>
551 <td className={td}>{formatDate(instalment.dueDate)}</td>
552 <td className={`${td} ${tdStrong} ${tdRight}`}>
553 {formatMoney(instalment.payment)}
554 </td>
555 <td className={`${td} ${tdRight}`}>{formatMoney(instalment.principal)}</td>
556 <td className={`${td} ${tdRight}`}>{formatMoney(instalment.interest)}</td>
557 <td className={`${td} ${tdRight}`}>{formatMoney(instalment.remaining)}</td>
558 <td className={td}>
559 <span className={meta.pill}>{meta.label}</span>
560 </td>
561 </tr>
562 );
563 })}
564 </tbody>
565 <tfoot>
566 <tr className={totalsRow}>
567 <td />
568 <td>Subtotal</td>
569 <td className={tdRight}>{formatMoney(shownTotals.payment)}</td>
570 <td className={tdRight}>{formatMoney(shownTotals.principal)}</td>
571 <td className={tdRight}>{formatMoney(shownTotals.interest)}</td>
572 <td className={tdRight}>{formatMoney(contract.outstanding)}</td>
573 <td />
574 </tr>
575 </tfoot>
576 </table>
577 </div>
578
579 <p className={tableNote}>
580 {paidInstalments.length} instalments paid,{" "}
581 {schedule.filter((item) => item.status === "due").length} due now and{" "}
582 {schedule.filter((item) => item.status === "upcoming").length} upcoming.
583 </p>
584 </section>
585
586 {/* Signed documents */}
587 <section className={card} aria-labelledby="documents-title">
588 <div className={sectionHeader}>
589 <h2 id="documents-title" className={sectionTitle}>
590 Signed documents
591 </h2>
592 <p className={sectionCaption}>
593 {documents.length} {documents.length === 1 ? "document" : "documents"}
594 </p>
595 </div>
596
597 <ul className={documentList}>
598 {documents.map((document) => (
599 <li key={document.name} className={documentItem}>
600 <span className={documentIcon} aria-hidden="true">
601 <svg
602 width="16"
603 height="16"
604 viewBox="0 0 24 24"
605 fill="none"
606 stroke="currentColor"
607 strokeWidth="1.8"
608 strokeLinecap="round"
609 strokeLinejoin="round"
610 >
611 <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
612 <path d="M14 3v5h5" />
613 <path d="M9 13h6M9 17h4" />
614 </svg>
615 </span>
616 <div>
617 <p className={documentName}>{document.name}</p>
618 <p className={documentDate}>Signed on {formatDate(document.signedAt)}</p>
619 </div>
620 <span className={signedBadge}>Signed</span>
621 </li>
622 ))}
623 </ul>
624 </section>
625 </div>
626 </main>
627 );
628 }
629
page.tsx:166:30 · minmax(0, 1fr) minmax(220px, 300px) `minmax(0, 1fr) minmax(220px, 300px)` is a raw length. Use a token. raw-values/raw-color 24 design-system page.tsx:74:15 · #f5f6f8 `#f5f6f8` is a raw color. Use a semantic color token. page.tsx:75:10 · #111827 `#111827` is a raw color. Use a semantic color token. page.tsx:91:15 · #ffffff `#ffffff` is a raw color. Use a semantic color token. page.tsx:92:11 · 1px solid #e6e8ec `1px solid #e6e8ec` is a raw color. Use a semantic color token. page.tsx:94:14 · 0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 28px -24px rgba(16, 24, 40, 0.45) `0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 28px -24px rgba(16, 24, 40, 0.45)` is a raw color. Use a semantic color token. page.tsx:113:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:128:10 · #4b5563 `#4b5563` is a raw color. Use a semantic color token. page.tsx:134:10 · #111827 `#111827` is a raw color. Use a semantic color token. page.tsx:146:11 · 1px solid #a7f3d0 `1px solid #a7f3d0` is a raw color. Use a semantic color token. page.tsx:147:15 · #ecfdf5 `#ecfdf5` is a raw color. Use a semantic color token. page.tsx:148:10 · #047857 `#047857` is a raw color. Use a semantic color token. page.tsx:163:14 · 1px solid #eef0f3 `1px solid #eef0f3` is a raw color. Use a semantic color token. page.tsx:173:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:189:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:198:15 · #eceef2 `#eceef2` is a raw color. Use a semantic color token. page.tsx:205:15 · linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%) `linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%)` is a raw color. Use a semantic color token. page.tsx:214:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:217:53 · #111827 `#111827` is a raw color. Use a semantic color token. page.tsx:238:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:253:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:284:10 · #6b7280 `#6b7280` is a raw color. Use a semantic color token. page.tsx:285:17 · 1px solid #e6e8ec `1px solid #e6e8ec` is a raw color. Use a semantic color token. page.tsx:293:17 · 1px solid #f1f2f5 `1px solid #f1f2f5` is a raw color. Use a semantic color token. page.tsx:299:30 · #9ca3af `#9ca3af` is a raw color. Use a semantic color token. Every run that broke raw-values/raw-color raw-values/raw-number 14 design-system page.tsx:110:15 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:119:15 · 1.15 `lineHeight: 1.15` skips the token scale. Use a lineHeight token. page.tsx:120:15 · 700 `fontWeight: 700` skips the token scale. Use a fontWeight token. page.tsx:133:15 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:145:15 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:172:15 · 500 `fontWeight: 500` skips the token scale. Use a fontWeight token. page.tsx:179:15 · 1.1 `lineHeight: 1.1` skips the token scale. Use a lineHeight token. page.tsx:180:15 · 700 `fontWeight: 700` skips the token scale. Use a fontWeight token. page.tsx:217:41 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:231:15 · 700 `fontWeight: 700` skips the token scale. Use a fontWeight token. page.tsx:252:15 · 500 `fontWeight: 500` skips the token scale. Use a fontWeight token. page.tsx:259:15 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:281:15 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. page.tsx:301:36 · 600 `fontWeight: 600` skips the token scale. Use a fontWeight token. Every run that broke raw-values/raw-number typecheck/TS2769 0 quality