ui: plan 01-27 done #1

Merged
developer merged 120 commits from ai/ui-client into main 2026-05-13 18:55:14 +00:00
3 changed files with 32 additions and 5 deletions
Showing only changes of commit e2a4790f6c - Show all commits
@@ -148,6 +148,15 @@ data fetching is performed here — the layout is responsible.
async function setStance(acceptor: string, relation: Relation): Promise<void> { async function setStance(acceptor: string, relation: Relation): Promise<void> {
if (draft === undefined) return; if (draft === undefined) return;
// No-op when the row already reflects the requested stance — the
// engine would accept the duplicate, but queueing a wire entry
// that re-states the current state inflates the order tab and
// the auto-sync envelope for nothing. The current stance reads
// off the overlay (`races[i].relation`), so a queued-but-not-
// applied stance change correctly suppresses a duplicate click
// that matches the *queued* intent, not just the server snapshot.
const current = races.find((r) => r.name === acceptor)?.relation;
if (current === relation) return;
await draft.add({ await draft.add({
kind: "setDiplomaticStance", kind: "setDiplomaticStance",
id: crypto.randomUUID(), id: crypto.randomUUID(),
+12
View File
@@ -226,6 +226,18 @@ describe("races table", () => {
expect(names).toEqual(["Beta", "Gamma", "Alpha"]); expect(names).toEqual(["Beta", "Gamma", "Alpha"]);
}); });
test("clicking the already-active stance is a no-op (no command queued)", async () => {
const ui = mountTable(
makeReport([race({ name: "Andori", relation: "WAR" })]),
);
await fireEvent.click(ui.getByTestId("races-stance-war"));
// Give the async handler one microtask to settle, then assert
// the draft remained empty — the click matched the current
// stance, so nothing should land in the order queue.
await Promise.resolve();
expect(draft.commands).toHaveLength(0);
});
test("clicking PEACE on a WAR row appends setDiplomaticStance and flips the overlay", async () => { test("clicking PEACE on a WAR row appends setDiplomaticStance and flips the overlay", async () => {
const ui = mountTable( const ui = mountTable(
makeReport([race({ name: "Andori", relation: "WAR" })]), makeReport([race({ name: "Andori", relation: "WAR" })]),
+11 -5
View File
@@ -1,9 +1,15 @@
import { defineConfig, mergeConfig } from "vitest/config"; import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config"; import viteConfig from "./vite.config";
export default mergeConfig( // `vite.config.ts` exports a `defineConfig(({ mode }) => …)` callback
viteConfig, // so the dev server can load `.env*` files through Vite's `loadEnv`.
defineConfig({ // `mergeConfig` does not accept callback-form configs, so resolve the
// callback here with the same context Vitest would supply, then hand
// the plain object to the merge.
export default defineConfig(async (ctx) => {
const resolved =
typeof viteConfig === "function" ? await viteConfig(ctx) : viteConfig;
return mergeConfig(resolved, {
resolve: { resolve: {
// Force the browser entry of Svelte so `mount` is available in jsdom. // Force the browser entry of Svelte so `mount` is available in jsdom.
conditions: ["browser"], conditions: ["browser"],
@@ -14,5 +20,5 @@ export default mergeConfig(
globals: true, globals: true,
setupFiles: ["./tests/setup.ts"], setupFiles: ["./tests/setup.ts"],
}, },
}), });
); });