Migrate the PaperHeader leaderboard from a basic HTML table to a shadcn-svelte DataTable component using svelte-headless-table (Svelte 4 compatible). This will provide better mobile responsiveness, sorting, filtering, and a more professional appearance.
npx shadcn-svelte@latest add table
npm install svelte-headless-tableCreate src/data/results.json with structure:
{
"leaderboard": [
{
"id": "1",
"agent": "terminus-2,gpt-5",
"displayName": "GPT-5",
"levels": {
"param-level": 0.1500,
"func-level": 0.1200,
"class-level": 0.0800,
"module-level": 0.0500
},
"overall": 0.1000
},
{
"id": "2",
"agent": "terminus-2,claude",
"displayName": "Claude Sonnet 4.0",
"levels": {
"param-level": 0.1300,
"func-level": 0.1000,
"class-level": 0.0600,
"module-level": 0.0300
},
"overall": 0.0800
},
{
"id": "3",
"agent": "terminus-2,oracle",
"displayName": "Expert Human (Human)",
"levels": {
"param-level": 0.2500,
"func-level": 0.2200,
"class-level": 0.1800,
"module-level": 0.1500
},
"overall": 0.2000
}
],
"colorThresholds": {
"high": 0.1,
"medium": 0
}
}Create in src/components/api-leaderboard/:
APILeaderboard.svelte- Main componentapi-leaderboard-columns.ts- Column definitionsapi-leaderboard.css- Custom styling
import { createRender } from "svelte-headless-table";
import type { Payment } from "./types";
// Helper functions
function formatAdvantage(value: number | null | undefined): string {
if (value === null || value === undefined) return "—";
return value.toFixed(4);
}
function getCellClass(value: number | null | undefined): string {
if (value === null || value === undefined) return "";
if (value >= 0.1) return "high";
if (value >= 0) return "medium";
return "low";
}
// Column definitions using svelte-headless-table
export const columns = table.createColumns([
table.column({
accessor: "displayName",
header: "Agent",
plugins: {
sort: { disable: false },
filter: { exclude: false }
}
}),
table.column({
accessor: (row) => row.levels["param-level"],
header: "L1: Parameter",
cell: ({ value }) => {
const formatted = formatAdvantage(value);
const colorClass = getCellClass(value);
return `<span class="score-cell ${colorClass}">${formatted}</span>`;
},
plugins: {
sort: { disable: false }
}
}),
table.column({
accessor: (row) => row.levels["func-level"],
header: "L2: Function",
cell: ({ value }) => {
const formatted = formatAdvantage(value);
const colorClass = getCellClass(value);
return `<span class="score-cell ${colorClass}">${formatted}</span>`;
}
}),
table.column({
accessor: (row) => row.levels["class-level"],
header: "L3: Class",
cell: ({ value }) => {
const formatted = formatAdvantage(value);
const colorClass = getCellClass(value);
return `<span class="score-cell ${colorClass}">${formatted}</span>`;
}
}),
table.column({
accessor: (row) => row.levels["module-level"],
header: "L4: Module",
cell: ({ value }) => {
const formatted = formatAdvantage(value);
const colorClass = getCellClass(value);
return `<span class="score-cell ${colorClass}">${formatted}</span>`;
}
}),
table.column({
accessor: "overall",
header: "Overall",
cell: ({ value }) => {
const formatted = formatAdvantage(value);
const colorClass = getCellClass(value);
return `<span class="score-cell overall-cell ${colorClass}">${formatted}</span>`;
}
})
]);<script lang="ts">
import { readable } from "svelte/store";
import { createTable, Render, Subscribe } from "svelte-headless-table";
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns
} from "svelte-headless-table/plugins";
import * as Table from "$lib/components/ui/table";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import leaderboardData from "$data/results.json";
// Initialize table with data
const table = createTable(readable(leaderboardData.leaderboard), {
page: addPagination({ initialPageSize: 10 }),
sort: addSortBy({ initialSortKeys: [{ id: "overall", order: "desc" }] }),
filter: addTableFilter({
fn: ({ filterValue, value }) => value.toLowerCase().includes(filterValue.toLowerCase())
}),
hide: addHiddenColumns()
});
// Create columns (import from columns.ts)
const columns = table.createColumns([/* column definitions */]);
// Setup table
const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } =
table.createViewModel(columns);
const { hasNextPage, hasPreviousPage, pageIndex } = pluginStates.page;
const { filterValue } = pluginStates.filter;
</script>
<div class="api-leaderboard">
<div class="leaderboard-header">
<h3>Leaderboard Snapshot</h3>
<p class="description">
This leaderboard displays the agent advantage scores by aggregation level.
Higher scores indicate better performance relative to the oracle.
</p>
</div>
<!-- Filter input -->
<div class="table-controls">
<Input
class="max-w-sm"
placeholder="Filter agents..."
type="text"
bind:value={$filterValue}
/>
</div>
<!-- Table -->
<div class="table-wrapper">
<Table.Root {...$tableAttrs}>
<Table.Header>
{#each $headerRows as headerRow}
<Subscribe rowAttrs={headerRow.attrs()}>
<Table.Row>
{#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()}>
<Table.Head {...attrs} class={cell.id === 'displayName' ? 'agent-col' : 'level-col'}>
<Render of={cell.render()} />
</Table.Head>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Header>
<Table.Body {...$tableBodyAttrs}>
{#each $pageRows as row (row.id)}
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
<Table.Row {...rowAttrs}>
{#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Cell {...attrs}>
<Render of={cell.render()} />
</Table.Cell>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Body>
</Table.Root>
</div>
<!-- Pagination -->
<div class="table-pagination">
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex - 1)}
disabled={!$hasPreviousPage}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex + 1)}
disabled={!$hasNextPage}
>
Next
</Button>
</div>
</div>
<style>
/* Import or inline custom styles matching current leaderboard */
</style>Match existing Leaderboard.svelte styles:
.api-leaderboard {
width: 100%;
padding: 4rem 0;
background: var(--wine-black);
}
.leaderboard-header h3 {
color: var(--wine-tan);
font-size: var(--36px);
font-weight: 700;
margin: 0 0 1rem 0;
text-align: center;
}
.description {
color: var(--wine-dark-tan);
font-size: var(--16px);
text-align: center;
margin: 0 0 2rem 0;
max-width: 700px;
margin-left: auto;
margin-right: auto;
}
.table-wrapper {
overflow-x: auto;
border-radius: 3px;
border: 1px solid var(--wine-dark-gray);
}
/* Score cell color coding */
:global(.score-cell.high) {
color: #0f9d58;
}
:global(.score-cell.medium) {
color: #d8d8d8;
}
:global(.score-cell.low) {
color: #e84545;
}
:global(.overall-cell) {
font-weight: 700;
font-size: var(--16px);
background: rgba(207, 202, 191, 0.05);
}
/* Responsive breakpoints */
@media (max-width: 900px) {
.leaderboard-header h3 {
font-size: var(--28px);
}
}
@media (max-width: 700px) {
.leaderboard-header h3 {
font-size: var(--24px);
}
}
@media (max-width: 480px) {
.leaderboard-header h3 {
font-size: var(--20px);
}
}Replace the hardcoded table section with:
<script>
import APILeaderboard from "$components/api-leaderboard/APILeaderboard.svelte";
</script>
<!-- Replace existing leaderboard section -->
<APILeaderboard />- Remove hardcoded
tableData,levels,LEVEL_DISPLAY_LABELSfrom PaperHeader.svelte - Remove old table HTML structure (lines 188-223)
- Remove old table CSS (lines 382-487)
- Keep the helper functions if reusable elsewhere
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="outline" builders={[builder]}>
Columns <ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each flatColumns as col}
<DropdownMenu.CheckboxItem bind:checked={hideForId[col.id]}>
{col.header}
</DropdownMenu.CheckboxItem>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>function exportToCSV() {
const csv = /* generate CSV from tableData */;
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'leaderboard.csv';
a.click();
}✅ Table displays with same visual appearance as current leaderboard
✅ Color coding (red/white/green) works correctly
✅ Sorting by any column works
✅ Filtering by agent name works
✅ Mobile responsive at all breakpoints (900px, 700px, 480px)
✅ Pagination controls work
✅ Data loads from src/data/results.json
✅ Component is reusable and importable
src/data/results.json- Leaderboard datasrc/components/api-leaderboard/APILeaderboard.svelte- Main componentsrc/components/api-leaderboard/api-leaderboard-columns.ts- Column definitionssrc/components/api-leaderboard/api-leaderboard.css- Custom stylesDATATABLE.md- This implementation plan
src/components/PaperHeader.svelte- Replace table with APILeaderboard componentpackage.json- Add svelte-headless-table dependency
- Uses svelte-headless-table (Svelte 4 compatible) instead of TanStack Table
- Maintains exact color scheme: green (#0f9d58), gray (#d8d8d8), red (#e84545)
- Preserves 4 decimal place formatting
- Keeps monospace font for score values
- Sortable by default on "Overall" column (descending)