Map records to generic browser rows.

CONTENT-WP-0002-T03 renders manifest columns through a pure row mapper and replaceable cell renderers.

Assistant: grok
Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
This commit is contained in:
tegwick 2026-10-13 23:01:09 +02:00
parent e16aa03393
commit d92eb25f92
12 changed files with 237 additions and 53 deletions

View file

@ -25,7 +25,7 @@ export function DataspaceHome({ id }: { id: string }) {
))}
</p>
) : null}
<RecordBrowser rows={dataspace.rows} />
<RecordBrowser rows={dataspace.rows} columns={dataspace.columns} />
</>
);
}

View file

@ -12,6 +12,8 @@ describe("kind and detail views", () => {
expect(html).toContain(">Concepts<");
expect(html).toContain("Coupling");
expect(html).toContain('href="/software-architecture/concept/coupling/"');
expect(html).toContain("dependency");
expect(html).toContain(">Tags<");
});
it("renders the entity title, an unknown claim, and a neighbour", () => {

View file

@ -9,7 +9,7 @@ export function KindIndex({ kind }: { kind: KindView }) {
return (
<>
<PageHeader kicker={kind.dataspaceTitle} title={kind.plural} lede={lede} />
<RecordBrowser rows={kind.rows} />
<RecordBrowser rows={kind.rows} columns={kind.columns} />
</>
);
}

View file

@ -1,36 +1,36 @@
"use client";
import { useState } from "react";
import { renderCell, type CellRenderer } from "@/components/cell-renderers";
import { ResultsTable } from "@/components/ResultsTable";
import { cellSortValue, type BrowserRow, type ColumnDef } from "@/lib/browser-row";
import { nextSort, sortRows } from "@/lib/sort-rows";
import type { RecordRow, SortState } from "@/lib/types";
import type { SortState } from "@/lib/types";
const columns = [
{
key: "title",
label: "Title",
export function RecordBrowser({
rows,
columns,
heading = "Records",
renderers,
}: {
rows: BrowserRow[];
columns: ColumnDef[];
heading?: string;
renderers?: Record<string, CellRenderer>;
}) {
const [sort, setSort] = useState<SortState | undefined>({ key: columns[0]?.key ?? "name", dir: 1 });
const ordered = sortRows(rows, sort, cellSortValue);
const tableColumns = columns.map((column) => ({
key: column.key,
label: column.label,
sortable: true,
render: (row: RecordRow) => row.href ? <a className="underline" href={row.href}>{row.title}</a> : row.title,
},
{
key: "kindLabel",
label: "Kind",
sortable: true,
render: (row: RecordRow) => row.kindHref
? <a className="chip" href={row.kindHref}>{row.kindLabel}</a>
: <span className="chip">{row.kindLabel}</span>,
},
{ key: "tldr", label: "Summary", sortable: true, render: (row: RecordRow) => row.tldr },
];
export function RecordBrowser({ rows, heading = "Records" }: { rows: RecordRow[]; heading?: string }) {
const [sort, setSort] = useState<SortState | undefined>({ key: "title", dir: 1 });
const ordered = sortRows(rows, sort, (row, key) => String(row[key as keyof RecordRow] ?? ""));
render: (row: BrowserRow) => renderCell(column.key, row, renderers),
}));
return (
<section id="records" aria-labelledby="records-heading" className="mx-auto max-w-7xl px-4 py-14 sm:px-6">
<h2 id="records-heading" className="mb-6 text-2xl">{heading}</h2>
<ResultsTable
columns={columns}
columns={tableColumns}
rows={ordered}
rowKey={(row) => row.id}
sort={sort}

View file

@ -0,0 +1,39 @@
import type { ReactNode } from "react";
import type { BrowserRow, CellValue } from "@/lib/browser-row";
/**
* Generic cell renderers. Callers replace any key or cell kind.
* OnCo's table cells for molecules, flags, and domain icons are not included.
*/
export type CellRenderer = (cell: CellValue, row: BrowserRow) => ReactNode;
export function renderGenericCell(cell: CellValue): ReactNode {
if (cell.kind === "text") return cell.text;
if (cell.kind === "link") {
return <a className="underline" href={cell.href}>{cell.label}</a>;
}
return (
<span className="inline-flex flex-wrap gap-1">
{cell.values.map((value) => (
<span key={value} className="chip">{value}</span>
))}
</span>
);
}
export const genericCellRenderers: Record<string, CellRenderer> = {
text: (cell) => renderGenericCell(cell),
link: (cell) => renderGenericCell(cell),
chips: (cell) => renderGenericCell(cell),
};
export function renderCell(
key: string,
row: BrowserRow,
registry: Record<string, CellRenderer> = genericCellRenderers,
): ReactNode {
const cell = row.cols[key];
if (!cell) return null;
const render = registry[key] ?? registry[cell.kind] ?? renderGenericCell;
return render(cell, row);
}