glytch/fktry/src/ui/pages.ts
type-two 83e3ad2107 [orchestrator] round 3 review + contracts v4 + composite seam codex + round 4 orders
Review: 5/5 lanes pass, 238/238, tsc clean. Verified live: scram duty-cycling with
tanks covering (zero brownout), shipment stamps, strain fever 0.824. Found 1 real bug
in review: UI BROWNOUT banner fires on covered deficit (topstrip.ts:66) - ordered.

Contracts v4: research (lab kind, setResearch, ResearchState, researched event),
SelectionState protocol (kills __remove sentinel, main.ts owns remove dispatch),
accent?, coolRadius? (spatial cooling), bandwidth.capacity?, save/load hardened,
heat ruled GROSS (net = heat - cool). tsconfig now typechecks data/ (DATA's proof).
Orchestrator patched lab entries into exhaustive kind maps (my breakage, my fix).

Rulings: pathspec-commit rule after 3 shared-index collisions (worktrees = named
escalation); bloom escalator reading accepted; UI ?uidemo + SCREEN boredom approved.

Codex: docs/FKTRY_LORE.md grew §10 THE COMPOSITE SEAM - keying/transparency artifact
family from the Corridor Crew compositing history + our corridorkey-mrp-mlx (which
doubles as a PYXLFK ground-truth artifact factory). DATA transcribes it round 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:43:48 +10:00

75 lines
2.5 KiB
TypeScript

/**
* LANE-UI — build bar pagination (pure; see pages.test.ts).
*
* Round 2 orders: kind tabs, `[`/`]` to page, 1-9 within the active page, "every machine
* hotkey-reachable at any catalog size".
*
* Kind tabs alone don't deliver that last clause: LANE-DATA's 21 machines include 14
* crafters, and 1-9 cannot reach 14. So a page is at most PER_PAGE machines, and a kind
* group larger than that spills into numbered pages (REFINE 1/2, REFINE 2/2). That keeps
* the invariant true no matter how big the catalog gets.
*/
import type { MachineDef, MachineKind } from '../contracts';
/** Hotkeys are 1-9, so a page can never hold more than nine. */
export const PER_PAGE = 9;
/** Kinds collapse into these tabs, in house order: dig, move, refine, power, ship. */
const GROUP_OF: Record<MachineKind, string> = {
extractor: 'EXTRACT',
belt: 'LOGISTICS',
splitter: 'LOGISTICS',
buffer: 'LOGISTICS',
crafter: 'REFINE',
power: 'POWER',
shipper: 'SHIP',
lab: 'RESEARCH', // v4 kind
};
const GROUP_ORDER = ['EXTRACT', 'LOGISTICS', 'REFINE', 'POWER', 'SHIP'];
export interface BuildPage {
/** Tab label, e.g. "REFINE" or "REFINE 2/2". */
label: string;
machines: MachineDef[];
}
export function paginate(machines: MachineDef[], perPage: number = PER_PAGE): BuildPage[] {
const groups = new Map<string, MachineDef[]>();
for (const m of machines) {
// An unknown kind still gets a home rather than vanishing from the bar.
const g = GROUP_OF[m.kind] ?? 'OTHER';
const list = groups.get(g);
if (list) list.push(m);
else groups.set(g, [m]);
}
const names = [...groups.keys()].sort((a, b) => {
const ia = GROUP_ORDER.indexOf(a);
const ib = GROUP_ORDER.indexOf(b);
// Unknown groups sort last, alphabetically, rather than jumping the queue.
if (ia === -1 && ib === -1) return a < b ? -1 : a > b ? 1 : 0;
if (ia === -1) return 1;
if (ib === -1) return -1;
return ia - ib;
});
const pages: BuildPage[] = [];
for (const name of names) {
const list = groups.get(name)!;
const chunks = Math.max(1, Math.ceil(list.length / perPage));
for (let i = 0; i < chunks; i++) {
pages.push({
label: chunks > 1 ? `${name} ${i + 1}/${chunks}` : name,
machines: list.slice(i * perPage, (i + 1) * perPage),
});
}
}
return pages;
}
/** Page index holding `defId`, or -1. Used to follow a click-selection to its tab. */
export function pageOf(pages: BuildPage[], defId: string): number {
return pages.findIndex((p) => p.machines.some((m) => m.id === defId));
}