UI / react-bootstrap: Migrate client components to react-bootstrap (Card, Table, Form, Alert, Badge, Nav, Button, Spinner, Row, Col): the ZXDB explorers and detail pages (Labels, Genres, Languages, MachineTypes, Releases, Entries), TapeIdentifier, home page, Navbar and ThemeDropdown. Server components (home, zxdb hub, magazines, issues) keep raw HTML+className — react-bootstrap barrel imports resolve to undefined under Turbopack in server components. Replace bi bi-* CSS icons with react-bootstrap-icons. Add aria-labels to search inputs and visually-hidden captions to data tables. Code-review remediation (docs/todo.md): - FileViewer: replace useState-as-effect with a proper useEffect. - register.service: restore request-level caching of parsed registers. - middleware: convert .js to .ts, dev-only request logging. - Extract shared types to src/types/zxdb.ts; add src/server/repo barrel for incremental per-domain splitting. - Extract helpers: parseIdList (params.ts), serialize (serialize.ts), buildRegisterSummary/isInfoLine (register_helpers.ts). - Add loading.tsx skeletons for dynamic ZXDB detail routes. - generateMetadata + notFound() on entry/release/label detail pages. - opengraph-image: stable keys; ThemeDropdown: drop hardcoded cookie domain; remove unused page.module.css. Register parser & data: - Update data/nextreg.txt from upstream tbblue (SpectrumNext FPGA): 0x04/0x0A/0x0F/0x80/0x81 bit changes, new Issue 5 board id, 0x43 renamed "Palette Control", 0xF0/0xF8/0xF9/0xFA now "Issues 4 and 5 Only". - Add reg_44 custom parser for 0x44 (Palette Value 9-bit): the two consecutive writes render as separate "1st write" / "2nd write" modes. - Skip commented-out register headers so the disabled 0xA3 block no longer leaks a phantom register. - Add detailHasContent guard so body-less registers (0xC7/0xCB/0xCF/ 0xFF) and 0xF0's leading blank no longer emit empty tab strips. - Capture 0xF0's leading "Issues 4 and 5 Only" line as register text. - Add isIssueRestricted (case-sensitive) to detect the issue badge across rewording without flagging per-bit "(issue 5 only)" notes; update badge label to "Issues 4 & 5 Only". claude-opus-4-8@lucy
58 lines
2.9 KiB
TypeScript
58 lines
2.9 KiB
TypeScript
import ReleasesExplorer from "./ReleasesExplorer";
|
|
import { listCasetypes, listFiletypes, listLanguages, listMachinetypes, listSchemetypes, listSourcetypes, searchReleases } from "@/server/repo";
|
|
import { parseIdList } from "@/utils/params";
|
|
import { serialize } from "@/utils/serialize";
|
|
|
|
export const metadata = {
|
|
title: "ZXDB Releases",
|
|
};
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function Page({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) {
|
|
const sp = await searchParams;
|
|
const hasParams = Object.values(sp).some((value) => value !== undefined);
|
|
const page = Math.max(1, Number(Array.isArray(sp.page) ? sp.page[0] : sp.page) || 1);
|
|
const q = (Array.isArray(sp.q) ? sp.q[0] : sp.q) ?? "";
|
|
const yearStr = (Array.isArray(sp.year) ? sp.year[0] : sp.year) ?? "";
|
|
const year = yearStr ? Number(yearStr) : undefined;
|
|
const sort = ((Array.isArray(sp.sort) ? sp.sort[0] : sp.sort) ?? "year_desc") as "year_desc" | "year_asc" | "title" | "entry_id_desc";
|
|
const dLanguageId = (Array.isArray(sp.dLanguageId) ? sp.dLanguageId[0] : sp.dLanguageId) ?? "";
|
|
const preferredMachineIds = [27, 26, 8, 9];
|
|
const dMachinetypeIds = parseIdList(sp.dMachinetypeId) ?? preferredMachineIds;
|
|
const dMachinetypeIdStr = dMachinetypeIds.join(",");
|
|
const filetypeIdStr = (Array.isArray(sp.filetypeId) ? sp.filetypeId[0] : sp.filetypeId) ?? "";
|
|
const filetypeId = filetypeIdStr ? Number(filetypeIdStr) : undefined;
|
|
const schemetypeId = (Array.isArray(sp.schemetypeId) ? sp.schemetypeId[0] : sp.schemetypeId) ?? "";
|
|
const sourcetypeId = (Array.isArray(sp.sourcetypeId) ? sp.sourcetypeId[0] : sp.sourcetypeId) ?? "";
|
|
const casetypeId = (Array.isArray(sp.casetypeId) ? sp.casetypeId[0] : sp.casetypeId) ?? "";
|
|
const isDemoStr = (Array.isArray(sp.isDemo) ? sp.isDemo[0] : sp.isDemo) ?? "";
|
|
const isDemo = isDemoStr ? (isDemoStr === "true" || isDemoStr === "1") : undefined;
|
|
|
|
const [initial, langs, machines, filetypes, schemes, sources, cases] = await Promise.all([
|
|
searchReleases({ page, pageSize: 20, q, year, sort, dLanguageId: dLanguageId || undefined, dMachinetypeId: dMachinetypeIds, filetypeId, schemetypeId: schemetypeId || undefined, sourcetypeId: sourcetypeId || undefined, casetypeId: casetypeId || undefined, isDemo }),
|
|
listLanguages(),
|
|
listMachinetypes(),
|
|
listFiletypes(),
|
|
listSchemetypes(),
|
|
listSourcetypes(),
|
|
listCasetypes(),
|
|
]);
|
|
|
|
return (
|
|
<ReleasesExplorer
|
|
initial={serialize(initial)}
|
|
initialLists={serialize({
|
|
languages: langs,
|
|
machinetypes: machines,
|
|
filetypes,
|
|
schemetypes: schemes,
|
|
sourcetypes: sources,
|
|
casetypes: cases,
|
|
})}
|
|
initialUrlState={{ q, page, year: yearStr, sort, dLanguageId, dMachinetypeId: dMachinetypeIdStr, filetypeId: filetypeIdStr, schemetypeId, sourcetypeId, casetypeId, isDemo: isDemoStr }}
|
|
initialUrlHasParams={hasParams}
|
|
/>
|
|
);
|
|
}
|