fix(ui): F8-10 owner-feedback — persistent filters, camera, disabled visual, dropdown narrowing (#53)
Tests · UI / test (push) Has been cancelled
Tests · UI / test (pull_request) Successful in 2m53s

Polish pass after the first F8-10 walkthrough:

  - table-planets: moved the `foreign` chip to the end of the row and
    hid the race dropdown until `foreign` is on (it never made sense
    to pick a race while the bucket itself was off).
  - persistent per-table filter / sort state — extracted to
    `table-{planets,ship-groups,fleets}-state.svelte.ts` singletons so
    a row click → map → back to the table restores the prior chip /
    dropdown / sort state. Held in memory only; an F5 still resets.
  - table-ship-groups: the planet and class dropdowns now narrow to
    the slice surviving the owner checkboxes, so toggling `foreign`
    off removes planets / classes touched only by foreign rows.
  - map.svelte: camera (centre + zoom) is captured on every dispose
    path into a new `GameStateStore.lastCamera` and consumed on the
    next mount, so leaving the map for any other active view and
    coming back restores the prior pan / zoom. A pending focus from
    the tables still wins for the centre point.
  - table-ship-classes: `:disabled` now reads as disabled (muted
    colour, no hover ring, not-allowed cursor) — the click was already
    a no-op, only the visual was lying.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-27 21:18:11 +02:00
parent 80ed11e3b6
commit 8e552f556d
12 changed files with 302 additions and 116 deletions
@@ -30,14 +30,10 @@ Click semantics:
} from "$lib/selection.svelte";
import ViewState from "$lib/ui/view-state.svelte";
import { formatFloat, formatInt } from "$lib/util/number-format";
type SortColumn =
| "name"
| "groupCount"
| "state"
| "location"
| "speed";
type SortDirection = "asc" | "desc";
import {
fleetsTableState as persistent,
type FleetsSortColumn as SortColumn,
} from "./table-fleets-state.svelte";
const COLUMN_LABELS: Record<SortColumn, TranslationKey> = {
name: "game.table.fleets.column.name",
@@ -60,9 +56,8 @@ Click semantics:
);
const selection = getContext<SelectionStore | undefined>(SELECTION_CONTEXT_KEY);
let sortColumn: SortColumn = $state("name");
let sortDirection: SortDirection = $state("asc");
let planetFilter: string = $state("");
// `persistent` (module-level rune above) drives the dropdown
// selection so the user's planet filter survives navigation.
const reportLoaded = $derived(
rendered?.report !== null && rendered?.report !== undefined,
@@ -85,7 +80,8 @@ Click semantics:
});
const filtered = $derived.by(() => {
const planet = planetFilter === "" ? null : Number(planetFilter);
const planet =
persistent.planetFilter === "" ? null : Number(persistent.planetFilter);
return fleets.filter((f) => {
if (planet === null) return true;
return f.destination === planet || f.origin === planet;
@@ -94,8 +90,8 @@ Click semantics:
const sorted = $derived.by(() => {
const list = [...filtered];
const dir = sortDirection === "asc" ? 1 : -1;
list.sort((a, b) => compare(a, b, sortColumn) * dir);
const dir = persistent.sortDirection === "asc" ? 1 : -1;
list.sort((a, b) => compare(a, b, persistent.sortColumn) * dir);
return list;
});
@@ -123,17 +119,17 @@ Click semantics:
}
function toggleSort(column: SortColumn): void {
if (sortColumn === column) {
sortDirection = sortDirection === "asc" ? "desc" : "asc";
if (persistent.sortColumn === column) {
persistent.sortDirection = persistent.sortDirection === "asc" ? "desc" : "asc";
return;
}
sortColumn = column;
sortDirection = "asc";
persistent.sortColumn = column;
persistent.sortDirection = "asc";
}
function ariaSort(column: SortColumn): "ascending" | "descending" | "none" {
if (sortColumn !== column) return "none";
return sortDirection === "asc" ? "ascending" : "descending";
if (persistent.sortColumn !== column) return "none";
return persistent.sortDirection === "asc" ? "ascending" : "descending";
}
function isInSpace(f: ReportLocalFleet): boolean {
@@ -189,7 +185,7 @@ Click semantics:
<span>{i18n.t("game.table.fleets.filter.planet")}</span>
<select
data-testid="fleets-filter-planet"
bind:value={planetFilter}
bind:value={persistent.planetFilter}
>
<option value=""
>{i18n.t("game.table.fleets.filter.planet.all")}</option
@@ -233,9 +229,9 @@ Click semantics:
onclick={() => toggleSort(column)}
>
{i18n.t(COLUMN_LABELS[column])}
{#if sortColumn === column}
{#if persistent.sortColumn === column}
<span class="sort-indicator" aria-hidden="true">
{sortDirection === "asc" ? "▲" : "▼"}
{persistent.sortDirection === "asc" ? "▲" : "▼"}
</span>
{/if}
</button>