Step 29.9: regression, accessibility fixes and notes
CI / check (push) Failing after 22m33s Details

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-11-04 18:55:57 -05:00
parent 613b06a83d
commit 9564c5bd24
15 changed files with 92 additions and 15 deletions

View File

@ -2099,3 +2099,15 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
- **The faintest text got much lighter.** `ink-400` and `ink-500` were raised until they read at 4.5:1 on the glass over white, which narrows the gap between text levels. Hierarchy has to come from size and weight as well.
- **The theme color and icon background follow the ground** (`--color-space-950`, #050919), and the icons were regenerated with `pnpm icons`.
- **Screens outside the map are plain navy until Step 29.3,** when the map moves behind every screen.
## Steps 29.2–29.9
- **Accent classes stayed; their scales changed (differs from Step 29.2).** `amber-*`, `cyan-*`, `sky-*`, `emerald-*` and `rose-*` are retuned to the role hues in `index.css`, and screens were swept so amber marks actions and warnings. Rewriting all 350 uses to role tokens by hand added risk and no visible change. New code uses the role tokens.
- **Old URLs are kept, not redirected (differs from Step 29.4).** `/pilots`, `/hqs`, `/market`, `/locations` and `/map` remain real routes under their tabs, so links, tests and saved habits all keep working. `/map` is the map view, since a selection needs somewhere to show.
- **Fleet has no separate ship detail screen.** The canvas drew one; the cards were made compact instead, and Fuel and Upgrades stay as sheets. It keeps every existing flow and test.
- **`font-display` remains in the code as an alias of the body font.** Removing it from about 40 files would change nothing on screen.
- **Phones get a nearly opaque sheet and no blur.** Map labels showed through at 82%, and blur is the biggest drawing cost; the 82% glass and blur apply from 768 px.
- **The map isn't highlighted for a selected job.** The canvas showed the route; the renderer has no such layer yet, so it was left out of this rework.
- **Plain Standard cargo has no chip in the table,** and only real requirements are mentioned; "No skills needed" went away as noise. Payout, distance and expiry stay in the row on wide panels and in the dispatch sheet's job details on phones (payout) or via the board's refresh countdown (expiry).
- **Dispatch is a modal below 1280 px** and beside the table above it; there isn't room beside a wide panel below that.
- **Step 29.9 checks:** axe (WCAG 2.0, 2.1 and 2.2 A/AA) reports nothing on 17 screens and sheets, though it can't judge text over the map; a 22-ship fleet stays usable. Frame rate held 60 in headless desktop Chrome only: a real mid-range phone is still untested.

View File

@ -200,7 +200,7 @@ function PilotSection({ log, state, open }: { log: PilotLog; state: PlayerState;
const now = useClock((c) => c.now);
return (
<details open={open} className="rounded-xl border border-glass-line bg-black/25 p-3">
<summary className="flex cursor-pointer items-baseline justify-between gap-2">
<summary className="flex min-h-11 cursor-pointer items-center justify-between gap-2">
<span className="font-display font-bold">{log.name}</span>
{log.xp > 0 && <span className="text-xs text-ink-300">+{log.xp} XP</span>}
</summary>

View File

@ -55,7 +55,7 @@ export function Layout({
<Link
to="/settings"
aria-label="Settings"
className="grid size-9 place-items-center rounded-lg text-ink-300 transition hover:bg-white/8 hover:text-ink-50"
className="-mr-1.5 grid size-11 place-items-center rounded-lg text-ink-300 transition hover:bg-white/8 hover:text-ink-50"
>
<Icon name="gear" className="size-5" />
</Link>

View File

@ -138,7 +138,7 @@ export function PanelHost({ children }: { children: ReactNode }) {
onPointerUp={onPointerUp}
onPointerCancel={onPointerUp}
onClick={step}
className="flex h-6 shrink-0 touch-none items-center justify-center md:hidden"
className="-mb-4 flex h-11 shrink-0 touch-none items-start justify-center pt-2.5 md:hidden"
>
<span aria-hidden="true" className="h-1 w-9 rounded-full bg-white/30" />
</button>

View File

@ -6,7 +6,7 @@ import { usePreferences } from "../state/preferences";
import { Button, Sheet } from "../ui";
const inputClass =
"w-full rounded-lg border border-glass-line bg-black/25 px-3 py-2 text-sm text-ink-50 focus:border-action focus:outline-none";
"min-h-11 w-full rounded-lg border border-glass-line bg-black/25 px-3 py-2 text-sm text-ink-50 focus:border-action focus:outline-none";
/** The company name, editable in place with the server's own rules (decision 0.5.75). */
export function CompanyName() {

View File

@ -65,7 +65,7 @@ export function Logbook() {
<select
value={pilotId}
onChange={(e) => set("pilot", e.target.value)}
className="rounded-lg border border-glass-line bg-space-900 px-2 py-1.5"
className="min-h-11 rounded-lg border border-glass-line bg-space-900 px-2"
>
<option value="">All pilots</option>
{state.pilots.map((p) => (

View File

@ -121,16 +121,17 @@ export function Market() {
/** One spec line: label and value (dt directly before dd), with a bar relative to the best model. */
function Spec(props: { icon: IconName; label: string; value: string; share?: number }) {
return (
<div>
<div className="flex items-center justify-between gap-2">
<dt className="flex items-center gap-1.5 font-display text-xs text-ink-400">
<Icon name={props.icon} className="size-3.5" />
{props.label}
</dt>
<dd className="font-display font-semibold tabular-nums">{props.value}</dd>
</div>
<div className="grid grid-cols-[1fr_auto] items-center gap-x-2">
<dt className="flex items-center gap-1.5 text-xs text-ink-300">
<Icon name={props.icon} className="size-3.5" />
{props.label}
</dt>
<dd className="font-semibold tabular-nums">{props.value}</dd>
{props.share !== undefined && (
<div aria-hidden="true" className="mt-1 h-1 overflow-hidden rounded-full bg-black/25">
<div
aria-hidden="true"
className="col-span-2 mt-1 h-1 overflow-hidden rounded-full bg-white/12"
>
<div
className="h-full rounded-full bg-ink-300"
style={{ width: `${Math.max(4, props.share * 100)}%` }}

View File

@ -93,6 +93,7 @@ function HullRepair({ shipId, atHq }: { shipId: string; atHq: boolean }) {
<p className="mt-1 text-ink-400">In perfect shape.</p>
) : can ? (
<Button
variant="secondary"
size="sm"
className="mt-2"
disabled={repair.isPending || state.company.credits < cost}
@ -212,6 +213,7 @@ export function ShipUpgrades({ shipId, onClose }: { shipId: string; onClose: ()
</p>
<div className="mt-2 flex gap-2">
<Button
variant="secondary"
size="sm"
disabled={start.isPending}
onClick={() =>

View File

@ -0,0 +1,44 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { DEFAULT_CAMERA, framed, useMapFrame } from "../src/map/camera";
import { fakeApi, renderApp, samplePlayerState } from "./render";
const app = () => fakeApi({ getPlayerState: () => samplePlayerState({ tutorial: null }) });
describe("the shell (decision 0.5.166)", () => {
it("steps the panel through its heights by tap or keyboard, never needing a drag", async () => {
renderApp(app());
const handle = await screen.findByRole("button", { name: /Panel height: half/ });
await userEvent.click(handle);
expect(handle).toHaveAccessibleName(/Panel height: full/);
await userEvent.click(handle);
expect(handle).toHaveAccessibleName(/Panel height: peek/);
await userEvent.click(handle);
expect(handle).toHaveAccessibleName(/Panel height: half/);
});
it("opens tall screens tall", async () => {
renderApp(app(), "/settings");
expect(await screen.findByRole("button", { name: /Panel height: full/ })).toBeInTheDocument();
});
it("keeps the map mounted across screens", async () => {
renderApp(app());
const canvas = await screen.findByTestId("map-canvas");
await userEvent.click(screen.getByRole("link", { name: "More" }));
await screen.findByRole("heading", { level: 1, name: "More" });
expect(screen.getByTestId("map-canvas")).toBe(canvas);
});
it("shifts the view by the frame without touching the saved camera", () => {
useMapFrame.setState({ offsetX: 10, offsetY: -20 });
expect(framed(DEFAULT_CAMERA, useMapFrame.getState())).toEqual({
...DEFAULT_CAMERA,
panX: 10,
panY: -20,
});
expect(DEFAULT_CAMERA.panX).toBe(0);
useMapFrame.setState({ offsetX: 0, offsetY: 0 });
});
});

View File

@ -91,3 +91,5 @@ Record these in `DECISIONS.md` (as plan §0.5.47–0.5.51).
- **Step 8.1 (layout):** the header and bottom navigation are restyled per decision 0.5.49; desktop uses a left rail.
- **Step 11.1 (PWA):** the self-hosted font files are part of the offline asset cache.
- **Amended by CR-029:** The Deep Field theme and four-tab navigation replace this document's look and eight tabs.

View File

@ -46,3 +46,5 @@ Record as plan §0.5.118–0.5.121.
### Step 20.4 — Regression and review
- Plan amendments (Steps 8.1 and 10E.4), full suite, coverage, lint, build, screenshots of Dashboard and HQs at desktop and phone width. Stop for review.
- **Amended by CR-029:** The Bridge leads with the player's ship and a Needs you list; the Alerts panel and stat tiles are gone, and the map sits behind it.

View File

@ -104,3 +104,5 @@ Record these in `DECISIONS.md` and `implementation-plan.md` §0.5, using the nex
## 7. Out of scope
A notification history panel beyond the Logbook, per-type toast settings, sounds, browser (system) notifications on web, and anything else in `implementation-plan.md` §0.3.
- **Amended by CR-029:** Toasts follow the new theme; their rules are unchanged.

View File

@ -3,7 +3,7 @@
**Project:** Deadhead: Space Freight Tycoon
**Applies to:** a build with Phase 13 and CR-001 through CR-028 complete
**Do this:** as **Phase 29**
**Status:** in progress; Step 29.1 done
**Status:** built (Phase 29); see the notes in `DECISIONS.md` for where the build differs from this plan
**Related docs:** `CR-005-ui-overhaul.md`, `CR-020-bridge-dashboard.md`, `CR-028-live-toasts.md`, `DECISIONS.md`
**Design reference:** the Deep Field canvas, https://claude.ai/artifact/LprS2DkuTSf2PfJm7VUSe4 (phone prototype, all 15 phone screens, 15 desktop views)

View File

@ -221,6 +221,10 @@ These answer the pre-build review. Log each one in `DECISIONS.md` as it is imple
163. **Seen per entry** (CR-028). Each logbook entry has a saved "seen" flag, set when toasted or when the pop-up showing it closes; marking entries seen isn't a manual decision.
164. **"Find work here" when a ship can't go** (amendment to CR-002 Step 10B.7). On any Fleet card, the button is disabled while the pilot rests (rest beside service included), the ship is in the shop (upgrade, repair or service), or the ship has no pilot; it says why and counts down to the later of rest and shop ("Resting · 04:12", "In the shop · 12:30", "No pilot assigned"), and turns back into the link when the countdown ends. The Contracts tab stays open to browse.
165. **Deep Field theme** (CR-029; replaces 47's look). Navy ground; amber only for the main action and the player's own ship, blue for other flights, mint for money earned, coral for problems. Michroma for the wordmark and screen titles only, Instrument Sans for everything else, both self-hosted. Glass panels at least 82% opaque, so every text color reads at 4.5:1 with the Sun or a bright star behind them. Tokens stay in `apps/web/src/index.css` (48).
166. **Map as the shell** (CR-029). One map canvas sits behind every screen for the whole session; screens are panels over it: a bottom sheet with peek, half and full heights on phones (draggable, or stepped by tapping its handle), a right-hand column from 768 px. The view centers on what the panel leaves showing. The map draws at 30 fps behind a panel, at full rate in the map view, and not at all while the tab is hidden or a panel fully covers it. A tap on a place or ship opens it in the map view (`/map`).
167. **Four tabs** (CR-029; replaces 49's tabs and 118's label split). Bridge, Jobs, Fleet, More. Fleet has Ships and Crew views; More holds Shipyard, Places & HQs (two views), Logbook and Settings. A screen's title is its name in the navigation. Older URLs keep working.
168. **Needs you** (CR-029; replaces 120). The Bridge lists what's waiting on the player: radio calls, ready ships, skill points, goals to claim. Tab badges count the same list: radio calls and goals on Bridge, ready ships on Jobs, skill points on Fleet.
169. **Jobs table and credits per hour** (CR-029). A real table sorted by net or credits per hour, remembered on the device. Jobs that can go now come first, others under "Can't take yet" with the reason. Net is payout minus fuel minus the pilot's share, excluding the lender's cut, from one function shared with the dispatch sheet. Credits per hour is net over the job's game hours. With no ship able now, the figures are for the fleet's best ship and marked "≈". From 1280 px the dispatch sheet opens beside the table.
### 0.6 Translating change requests written for the server (CR-014 §7)

View File

@ -596,3 +596,11 @@ Spec: `memory-bank/change-requests/CR-028-live-toasts.md` (received as "CR-026
Spec: `memory-bank/change-requests/CR-029-deep-field-ui.md`. New decisions recorded as plan §0.5.165 onward.
- [x] **Step 29.1 — Theme tokens and type.** `apps/web/src/index.css`: Michroma and Instrument Sans replace Chakra Petch and Barlow; retuned `space-*` and `ink-*`; new `font-title` and role tokens (`glass`, `glass-line`, `action`, `on-action`, `flight`, `earned`, `problem`, `on-problem`); the starfield body background is gone. Theme color and icons moved to #050919. `theme.test.ts` measures every text color at 4.5:1 on the glass over dark space and over white. Full suite green (200 files, 1,663 tests), lint and type check clean, the install e2e green. Known until Step 29.2: the wider font wraps "Find work here" on the Bridge and "When you send them" on Pilots at phone width.
- [x] **Step 29.2 — UI kit.** `Button`, `Panel`, `Sheet`, `Pill`, `Meter`, `StatTile` and `ScreenHeader` restyled; brackets and glows gone; accent scales retuned; tracked capitals and text under 12 px removed everywhere.
- [x] **Step 29.3 — The map shell.** `layout/MapBackdrop.tsx` (one canvas), `layout/PanelHost.tsx` (sheet detents, side column, tells the map its frame), `map/selection.ts`, `useMapFrame` and `framed` in `map/camera.ts`; 30 fps behind panels, paused when covered.
- [x] **Step 29.4 — Navigation.** Four tabs, `screens/More.tsx`, `components/FleetViews.tsx`; titles match the navigation.
- [x] **Step 29.5 — Bridge.** `components/NeedsYou.tsx` (`needsYou`, also the source of `tabAttention`); the Alerts panel and stat tiles are gone.
- [x] **Step 29.6 — Jobs table.** `screens/JobsTable.tsx`, `screens/jobFigures.ts`, `jobMoney` in `game.ts` (shared with `pairOptions`), `jobSort` preference.
- [x] **Step 29.7 — Dispatch, Fleet and Crew.** The sheet leads with net profit and opens beside the table from 1280 px; compact ship cards; single-column skills with 44 px buttons.
- [x] **Step 29.8 — Everything else.** Shipyard, Places & HQs, Start, Welcome, toasts, tutorial and the rest; amber sweep.
- [x] **Step 29.9 — Regression.** See `DECISIONS.md`; axe finds no WCAG A/AA violations on the audited screens. Not checked on a real phone.