ui/phase-21: sciences CRUD list, designer, and production-picker integration

Lights up the player-defined sciences feature: a table view with sort
and filter, a designer with four percent inputs and a strict
sum-equals-100 gate, and a Research-sub-row integration so the
planet production picker lists the user's sciences alongside the
four tech buttons. Phase 21 decisions are baked back into ui/PLAN.md
(no UpdateScience on the wire — write-once via createScience +
removeScience; percentages instead of fractions; sciences live under
the existing Research segment).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-10 21:32:37 +02:00
parent 0509f2cde2
commit 7bea22b0b5
31 changed files with 2751 additions and 71 deletions
+8 -13
View File
@@ -2,12 +2,13 @@
// stub renders the localised view title plus the `coming soon` body
// copy and exposes a stable `data-testid` so later phases can replace
// the content without renaming the test hook. Phase 17 lit up the
// ship-classes table and the ship-class designer, so the assertions
// for those slugs / components moved to the dedicated suites
// (`table-ship-classes.test.ts`, `designer-ship-class.test.ts`); the
// `table.svelte` router still falls back to the stub for the
// not-yet-implemented entities (planets, ship-groups, fleets,
// sciences, races) and that fallback is exercised here.
// ship-classes table and the ship-class designer; Phase 21 lit up
// the sciences table and the science designer. Their assertions
// moved to dedicated suites (`table-ship-classes.test.ts`,
// `designer-ship-class.test.ts`, `table-sciences.test.ts`,
// `designer-science.test.ts`); the `table.svelte` router still falls
// back to the stub for the remaining entities (planets, ship-groups,
// fleets, races) and that fallback is exercised here.
import "@testing-library/jest-dom/vitest";
import { render } from "@testing-library/svelte";
@@ -20,7 +21,6 @@ import TableView from "../src/lib/active-view/table.svelte";
import ReportView from "../src/lib/active-view/report.svelte";
import BattleView from "../src/lib/active-view/battle.svelte";
import MailView from "../src/lib/active-view/mail.svelte";
import DesignerScience from "../src/lib/active-view/designer-science.svelte";
beforeEach(() => {
i18n.resetForTests("en");
@@ -56,7 +56,7 @@ describe("active-view stubs", () => {
expect(node).toHaveTextContent("ship groups");
});
test("report / mail / designer-science stubs render their localised titles", () => {
test("report / mail stubs render their localised titles", () => {
const r = render(ReportView);
expect(r.getByTestId("active-view-report")).toHaveTextContent(
"turn report",
@@ -66,11 +66,6 @@ describe("active-view stubs", () => {
expect(m.getByTestId("active-view-mail")).toHaveTextContent(
"diplomatic mail",
);
const sci = render(DesignerScience);
expect(
sci.getByTestId("active-view-designer-science"),
).toHaveTextContent("science designer");
});
test("battle stub stamps the battleId on the host element", () => {