Port the explorer shell: tokens, header, hero, and a generic table.

CONTENT-WP-0002-T01 adapts the pinned OnCo visual system under ui/ without the corpus, mark, or kind routes.

Assistant: grok
Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
This commit is contained in:
tegwick 2026-10-13 22:51:00 +02:00
parent 695060e38d
commit a6e360aaa5
27 changed files with 4013 additions and 3 deletions

27
ui/lib/fixture.ts Normal file
View file

@ -0,0 +1,27 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { RecordRow } from "./types";
type Snapshot = {
dataspace: { id: string; title: string; description: string };
kinds: { id: string; label: string; plural: string }[];
entities: { id: string; kindId: string; title: string; tldr: string }[];
};
export function loadSystemsThinking(): { title: string; description: string; rows: RecordRow[] } {
const path = join(process.cwd(), "../examples/dataspaces/systems-thinking/snapshot.json");
const snapshot = JSON.parse(readFileSync(path, "utf8")) as Snapshot;
const labels = new Map(snapshot.kinds.map((kind) => [kind.id, kind.label]));
const rows = snapshot.entities.map((entity) => ({
id: entity.id,
kindId: entity.kindId,
kindLabel: labels.get(entity.kindId) ?? entity.kindId,
title: entity.title,
tldr: entity.tldr,
}));
return {
title: snapshot.dataspace.title,
description: snapshot.dataspace.description,
rows,
};
}

23
ui/lib/sort-rows.test.ts Normal file
View file

@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { nextSort, sortRows } from "./sort-rows";
const rows = [
{ title: "Emergence" },
{ title: "Feedback" },
{ title: "Requisite variety" },
];
describe("sortRows", () => {
it("orders by the selected key and flips with the direction", () => {
const ascending = sortRows(rows, { key: "title", dir: 1 }, (row, key) => row[key as "title"]);
expect(ascending.map((row) => row.title)).toEqual(["Emergence", "Feedback", "Requisite variety"]);
const descending = sortRows(rows, { key: "title", dir: -1 }, (row, key) => row[key as "title"]);
expect(descending.map((row) => row.title)).toEqual(["Requisite variety", "Feedback", "Emergence"]);
});
it("toggles direction when the same key is chosen again", () => {
expect(nextSort(undefined, "title")).toEqual({ key: "title", dir: 1 });
expect(nextSort({ key: "title", dir: 1 }, "title")).toEqual({ key: "title", dir: -1 });
expect(nextSort({ key: "title", dir: -1 }, "kindLabel")).toEqual({ key: "kindLabel", dir: 1 });
});
});

13
ui/lib/sort-rows.ts Normal file
View file

@ -0,0 +1,13 @@
import type { SortState } from "./types";
export function sortRows<T>(rows: readonly T[], sort: SortState | undefined, value: (row: T, key: string) => string): T[] {
if (!sort) return [...rows];
const copy = [...rows];
copy.sort((a, b) => value(a, sort.key).localeCompare(value(b, sort.key)) * sort.dir);
return copy;
}
export function nextSort(current: SortState | undefined, key: string): SortState {
if (current?.key === key) return { key, dir: current.dir === 1 ? -1 : 1 };
return { key, dir: 1 };
}

9
ui/lib/types.ts Normal file
View file

@ -0,0 +1,9 @@
export type RecordRow = {
id: string;
kindId: string;
kindLabel: string;
title: string;
tldr: string;
};
export type SortState = { key: string; dir: 1 | -1 };