From 38b636014ffba1fbf9d3c1f05edd7a717f2ac316 Mon Sep 17 00:00:00 2001 From: fireshaper Date: Wed, 4 Nov 2026 18:10:40 -0500 Subject: [PATCH] Step 29.4: four tabs, More and Fleet's views Co-Authored-By: Claude Opus 5.5 --- apps/web/e2e/support.ts | 2 +- apps/web/src/App.tsx | 15 ++++---- apps/web/src/components/FleetViews.tsx | 31 ++++++++++++++++ apps/web/src/layout/Layout.tsx | 21 +++++------ apps/web/src/layout/attention.ts | 2 +- apps/web/src/routes.ts | 21 ++++++----- apps/web/src/screens/Contracts.tsx | 4 +-- apps/web/src/screens/Dashboard.tsx | 6 ++-- apps/web/src/screens/Fleet.tsx | 4 ++- apps/web/src/screens/Locations.tsx | 2 +- apps/web/src/screens/Market.tsx | 2 +- apps/web/src/screens/More.tsx | 33 +++++++++++++++++ apps/web/src/screens/Pilots.tsx | 4 ++- apps/web/src/ui/icons.tsx | 8 +++++ apps/web/test/attention.test.tsx | 10 +++--- apps/web/test/away-summary.test.tsx | 2 +- apps/web/test/company-settings.test.tsx | 2 +- apps/web/test/dashboard.test.tsx | 6 ++-- apps/web/test/deal-modal.test.tsx | 4 +-- apps/web/test/fleet-sleep.test.tsx | 4 +-- apps/web/test/goals.test.tsx | 2 +- apps/web/test/layout.test.tsx | 43 ++++++++++++++++++++--- apps/web/test/onboarding.test.tsx | 4 +-- apps/web/test/player-state.test.tsx | 4 +-- apps/web/test/skill-point-prompt.test.tsx | 8 ++--- apps/web/test/states.test.tsx | 4 +-- apps/web/test/welcome.test.tsx | 2 +- packages/content/src/data/onboarding.ts | 2 +- packages/sim/test/goals.test.ts | 2 +- 29 files changed, 185 insertions(+), 69 deletions(-) create mode 100644 apps/web/src/components/FleetViews.tsx create mode 100644 apps/web/src/screens/More.tsx diff --git a/apps/web/e2e/support.ts b/apps/web/e2e/support.ts index 69c9ca6..611de28 100644 --- a/apps/web/e2e/support.ts +++ b/apps/web/e2e/support.ts @@ -130,7 +130,7 @@ export async function playToDeal(page: Page) { await page.goto("/"); await dismissLogbook(page); await page.getByRole("button", { name: "Shake on it" }).click(); - await expect(page.getByRole("heading", { level: 1, name: "Dashboard" })).toBeVisible(); + await expect(page.getByRole("heading", { level: 1, name: "Bridge" })).toBeVisible(); // Both pilots have skill points at the deal (CR-017); the pop-up says so. const skills = page.getByRole("dialog", { name: "Your pilots are ready to train" }); await expect(skills.getByLabel("Unspent skill points")).toContainText(/You: \d+ points?/); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 0e62789..9d6c02a 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -19,6 +19,7 @@ import { HQs } from "./screens/HQs"; import { Locations } from "./screens/Locations"; import { MapScreen } from "./screens/MapScreen"; import { Market } from "./screens/Market"; +import { More } from "./screens/More"; import { Pilots } from "./screens/Pilots"; import { Logbook } from "./screens/Logbook"; import { Settings } from "./screens/Settings"; @@ -30,13 +31,9 @@ import { Button } from "./ui"; const SCREEN_COMPONENTS: Record<(typeof SCREENS)[number]["path"], ReactNode> = { "/": , - "/map": , "/contracts": , "/fleet": , - "/pilots": , - "/hqs": , - "/market": , - "/locations": , + "/more": , }; /** Reads the game clock (the engine's device clock, CR-014), then ticks it every second. */ @@ -106,7 +103,13 @@ export function App() { element={SCREEN_COMPONENTS[screen.path]} /> ))} - {/* Settings opens from the header gear, not a tab (CR-011). */} + {/* Screens without a tab of their own (decision 0.5.167). */} + } /> + } /> + } /> + } /> + } /> + {/* Settings opens from the header gear and More, not a tab (CR-011). */} } /> {/* The Logbook opens from "While you were away" and the Bridge (CR-025). */} } /> diff --git a/apps/web/src/components/FleetViews.tsx b/apps/web/src/components/FleetViews.tsx new file mode 100644 index 0000000..f56c7c8 --- /dev/null +++ b/apps/web/src/components/FleetViews.tsx @@ -0,0 +1,31 @@ +import { NavLink } from "react-router"; +import { useLoadedPlayer } from "../state/actions"; + +/** Fleet's two views, Ships and Crew (decision 0.5.167). */ +export function FleetViews() { + const state = useLoadedPlayer(); + const views = [ + { to: "/fleet", label: `Ships ${state.ships.length}` }, + { to: "/pilots", label: `Crew ${state.pilots.length}` }, + ]; + return ( + + ); +} diff --git a/apps/web/src/layout/Layout.tsx b/apps/web/src/layout/Layout.tsx index 3f463a3..615dbbf 100644 --- a/apps/web/src/layout/Layout.tsx +++ b/apps/web/src/layout/Layout.tsx @@ -1,7 +1,7 @@ import type { CSSProperties, ReactNode } from "react"; -import { Link, NavLink, Outlet, useLocation } from "react-router"; +import { Link, Outlet, useLocation } from "react-router"; import { GameClock } from "../clock/GameClock"; -import { SCREENS, type ScreenPath } from "../routes"; +import { SCREENS, tabOf, type ScreenPath } from "../routes"; import { Icon } from "../ui"; import { useTrackScreen } from "./lastScreen"; import { MapBackdrop } from "./MapBackdrop"; @@ -82,6 +82,7 @@ export function Layout({ label={screen.label} icon={screen.icon} badge={attention[screen.path] ?? 0} + active={tabOf(pathname) === screen.path} /> ))} @@ -94,21 +95,21 @@ function NavTab({ label, icon, badge, + active, }: { path: ScreenPath; label: string; icon: (typeof SCREENS)[number]["icon"]; badge: number; + active: boolean; }) { return ( - - `relative flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5 text-xs font-medium transition-colors md:flex-none md:py-2 ${ - isActive ? "text-action" : "text-ink-300 hover:text-ink-50" - }` - } + aria-current={active ? "page" : undefined} + className={`relative flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5 text-xs font-medium transition-colors md:flex-none md:py-2 ${ + active ? "text-action" : "text-ink-300 hover:text-ink-50" + }`} > @@ -123,7 +124,7 @@ function NavTab({ )} {label} - + ); } diff --git a/apps/web/src/layout/attention.ts b/apps/web/src/layout/attention.ts index 437e441..4bc5e0f 100644 --- a/apps/web/src/layout/attention.ts +++ b/apps/web/src/layout/attention.ts @@ -13,6 +13,6 @@ export function tabAttention(state: PlayerState): Partial g.status === "ready").length + state.radioCalls.length, "/contracts": ready, - "/pilots": state.pilots.reduce((sum, p) => sum + p.skillPointsAvailable, 0), + "/fleet": state.pilots.reduce((sum, p) => sum + p.skillPointsAvailable, 0), }; } diff --git a/apps/web/src/routes.ts b/apps/web/src/routes.ts index 3d4f0df..8423603 100644 --- a/apps/web/src/routes.ts +++ b/apps/web/src/routes.ts @@ -1,16 +1,19 @@ import type { IconName } from "./ui/icons"; -/** The app's top-level screens, in navigation order. Labels are short to fit eight tabs. */ +/** The main tabs, in navigation order (decision 0.5.167). */ export const SCREENS = [ - // The Dashboard, "Bridge" in the navigation (CR-020, decision 0.5.118). - { path: "/", title: "Dashboard", label: "Bridge", icon: "helm" }, - { path: "/map", title: "Map", label: "Map", icon: "map" }, - { path: "/contracts", title: "Contracts", label: "Jobs", icon: "jobs" }, + { path: "/", title: "Bridge", label: "Bridge", icon: "helm" }, + { path: "/contracts", title: "Jobs", label: "Jobs", icon: "jobs" }, { path: "/fleet", title: "Fleet", label: "Fleet", icon: "fleet" }, - { path: "/pilots", title: "Pilots", label: "Pilots", icon: "pilots" }, - { path: "/hqs", title: "HQs", label: "HQs", icon: "hq" }, - { path: "/market", title: "Market", label: "Shop", icon: "market" }, - { path: "/locations", title: "Locations", label: "Places", icon: "places" }, + { path: "/more", title: "More", label: "More", icon: "more" }, ] as const satisfies readonly { path: string; title: string; label: string; icon: IconName }[]; export type ScreenPath = (typeof SCREENS)[number]["path"]; + +/** The tab a screen belongs to: Crew sits under Fleet, the map under the Bridge. */ +export function tabOf(pathname: string): ScreenPath { + if (pathname === "/" || pathname === "/map") return "/"; + if (pathname === "/contracts") return "/contracts"; + if (pathname === "/fleet" || pathname === "/pilots") return "/fleet"; + return "/more"; +} diff --git a/apps/web/src/screens/Contracts.tsx b/apps/web/src/screens/Contracts.tsx index dfcfa13..96cb2fe 100644 --- a/apps/web/src/screens/Contracts.tsx +++ b/apps/web/src/screens/Contracts.tsx @@ -12,9 +12,9 @@ export function Contracts() { const state = useLoadedPlayer(); const [selected, setSelected] = useState(null); return ( - + diff --git a/apps/web/src/screens/Dashboard.tsx b/apps/web/src/screens/Dashboard.tsx index 51ec173..693ff95 100644 --- a/apps/web/src/screens/Dashboard.tsx +++ b/apps/web/src/screens/Dashboard.tsx @@ -94,7 +94,7 @@ export function Dashboard() { return ( - + {/* While the fleet sleeps, first thing on the Bridge (CR-024). */} {/* Calls waiting for an answer, each reopening its card (CR-026). */} @@ -216,7 +216,7 @@ export function Dashboard() { )} {!state.tutorial && state.ships.length === 0 && ( -

No ships yet — buy one in the Market.

+

No ships yet — buy one in the Shipyard.

)} @@ -260,7 +260,7 @@ function YourShip({ className = "" }: { className?: string }) {

You're not flying a ship right now.

- Assign yourself on Pilots + Assign yourself in Crew ); diff --git a/apps/web/src/screens/Fleet.tsx b/apps/web/src/screens/Fleet.tsx index ebf9ee1..ec0a16a 100644 --- a/apps/web/src/screens/Fleet.tsx +++ b/apps/web/src/screens/Fleet.tsx @@ -7,6 +7,7 @@ import { validateReposition, type RepositionFailure, } from "@deadhead/sim"; +import { FleetViews } from "../components/FleetViews"; import { useState } from "react"; import { Link } from "react-router"; import { useClock } from "../clock/clock"; @@ -52,13 +53,14 @@ export function Fleet() { } icon="fleet" /> + {state.ships.length === 0 ? (

No ships yet —{" "} - visit the Market + visit the Shipyard .

diff --git a/apps/web/src/screens/Locations.tsx b/apps/web/src/screens/Locations.tsx index 7284b9d..2707ba3 100644 --- a/apps/web/src/screens/Locations.tsx +++ b/apps/web/src/screens/Locations.tsx @@ -27,7 +27,7 @@ export function Locations() { return ( - + {lease.isError && (

{errorMessage(lease.error)} diff --git a/apps/web/src/screens/Market.tsx b/apps/web/src/screens/Market.tsx index 742f3fd..1a2be21 100644 --- a/apps/web/src/screens/Market.tsx +++ b/apps/web/src/screens/Market.tsx @@ -29,7 +29,7 @@ export function Market() { return ( + +

    + {ROWS.map((row) => ( +
  • + + + + {row.title} + {row.detail} + + + +
  • + ))} +
+
+ ); +} diff --git a/apps/web/src/screens/Pilots.tsx b/apps/web/src/screens/Pilots.tsx index 5ebd9ab..3d0be9c 100644 --- a/apps/web/src/screens/Pilots.tsx +++ b/apps/web/src/screens/Pilots.tsx @@ -1,3 +1,4 @@ +import { FleetViews } from "../components/FleetViews"; import { content } from "@deadhead/content"; import { PilotIndependence } from "../components/IndependentPilots"; import type { Pilot } from "@deadhead/schema"; @@ -29,7 +30,8 @@ export function Pilots() { const state = useLoadedPlayer(); return ( - + + {!state.pilots.some((p) => !p.isAvatar) && (

diff --git a/apps/web/src/ui/icons.tsx b/apps/web/src/ui/icons.tsx index 8178d7c..1d8ba87 100644 --- a/apps/web/src/ui/icons.tsx +++ b/apps/web/src/ui/icons.tsx @@ -90,6 +90,14 @@ const PATHS = { ), + // Three dots. + more: ( + <> + + + + + ), // A cog (outline after Lucide "settings", ISC licence). gear: ( <> diff --git a/apps/web/test/attention.test.tsx b/apps/web/test/attention.test.tsx index a471810..cefc339 100644 --- a/apps/web/test/attention.test.tsx +++ b/apps/web/test/attention.test.tsx @@ -53,7 +53,7 @@ describe("navigation attention badges (Step 10E.3)", () => { ], } as Partial); // Skill points: 2 + 0 + 1 across the three pilots (decision 0.5.109). - expect(tabAttention(state)).toEqual({ "/": 2, "/contracts": 1, "/pilots": 3 }); + expect(tabAttention(state)).toEqual({ "/": 2, "/contracts": 1, "/fleet": 3 }); }); it("nudges toward Jobs during the tutorial until a run is in flight", () => { @@ -67,7 +67,7 @@ describe("navigation attention badges (Step 10E.3)", () => { const nav = await screen.findByRole("navigation", { name: "Main" }); const jobs = within(nav).getByRole("link", { name: "Jobs" }); expect(within(jobs).getByTestId("badge-Jobs")).toHaveTextContent("1"); - expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument(); + expect(within(nav).queryByTestId("badge-Fleet")).not.toBeInTheDocument(); }); }); @@ -99,16 +99,16 @@ describe("Pilots tab badge (Step 17.5, decision 0.5.109)", () => { }); renderApp(api, "/pilots"); const nav = await screen.findByRole("navigation", { name: "Main" }); - expect(await within(nav).findByTestId("badge-Pilots")).toHaveTextContent("3"); + expect(await within(nav).findByTestId("badge-Fleet")).toHaveTextContent("3"); const raise = async (pilot: string) => userEvent.click( (await screen.findAllByRole("button", { name: new RegExp(`for ${pilot}$`) }))[0]!, ); await raise(avatar.name); - await waitFor(() => expect(within(nav).getByTestId("badge-Pilots")).toHaveTextContent("2")); + await waitFor(() => expect(within(nav).getByTestId("badge-Fleet")).toHaveTextContent("2")); await raise(avatar.name); await raise("Rosa Vance"); - await waitFor(() => expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument()); + await waitFor(() => expect(within(nav).queryByTestId("badge-Fleet")).not.toBeInTheDocument()); }); }); diff --git a/apps/web/test/away-summary.test.tsx b/apps/web/test/away-summary.test.tsx index ac1245b..0c735a4 100644 --- a/apps/web/test/away-summary.test.tsx +++ b/apps/web/test/away-summary.test.tsx @@ -250,7 +250,7 @@ describe("the highlights pop-up (Step 25.3, decision 0.5.140)", () => { it("does not show the dialog when there is nothing to report", async () => { renderApp(fakeApi({ getPlayerState: () => samplePlayerState() })); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); }); diff --git a/apps/web/test/company-settings.test.tsx b/apps/web/test/company-settings.test.tsx index c153b8f..ba8cc1f 100644 --- a/apps/web/test/company-settings.test.tsx +++ b/apps/web/test/company-settings.test.tsx @@ -61,7 +61,7 @@ describe("company settings (Step 10K.7)", () => { await userEvent.click(confirm); await waitFor(() => expect(api.call).toHaveBeenCalledWith("newCompany", {})); - expect(await screen.findByRole("heading", { level: 1, name: "Contracts" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { level: 1, name: "Jobs" })).toBeInTheDocument(); expect(await screen.findByLabelText("Intro")).toBeInTheDocument(); }); }); diff --git a/apps/web/test/dashboard.test.tsx b/apps/web/test/dashboard.test.tsx index 89d0669..6e52aa5 100644 --- a/apps/web/test/dashboard.test.tsx +++ b/apps/web/test/dashboard.test.tsx @@ -54,7 +54,7 @@ describe("Dashboard (CR-020)", () => { activeDispatches: [dispatch("b", NOW + 125_000), dispatch("c", NOW + 65_000)], }); renderApp(fakeApi({ getPlayerState: () => state })); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); const statValue = (label: string) => screen.getByText(label).nextElementSibling; expect(statValue("Ships idle")).toHaveTextContent("1"); @@ -135,7 +135,7 @@ describe("loan on HQ", () => { it("is hidden once the loan is paid off", async () => { renderApp(fakeApi({ getPlayerState: () => owner(2000, 0) })); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(screen.queryByText("Loan from Halvorsen")).not.toBeInTheDocument(); }); @@ -283,7 +283,7 @@ describe("your ship on the Dashboard (CR-020)", () => { renderApp(fakeApi({ getPlayerState: () => owner({ ships: [] }) })); const p = await panel(); expect(p).toHaveTextContent("You're not flying a ship right now."); - expect(within(p).getByRole("link", { name: "Assign yourself on Pilots" })).toHaveAttribute( + expect(within(p).getByRole("link", { name: "Assign yourself in Crew" })).toHaveAttribute( "href", "/pilots", ); diff --git a/apps/web/test/deal-modal.test.tsx b/apps/web/test/deal-modal.test.tsx index e5ed042..6fdcc03 100644 --- a/apps/web/test/deal-modal.test.tsx +++ b/apps/web/test/deal-modal.test.tsx @@ -67,7 +67,7 @@ describe("the deal", () => { }), "/contracts", ); - await screen.findByRole("heading", { level: 1, name: "Contracts" }); + await screen.findByRole("heading", { level: 1, name: "Jobs" }); expect( screen.queryByRole("dialog", { name: "An offer from Halvorsen" }), ).not.toBeInTheDocument(); @@ -154,7 +154,7 @@ describe("the deal", () => { ); await userEvent.click(await screen.findByRole("button", { name: "Shake on it" })); await waitFor(() => expect(api.call).toHaveBeenCalledWith("acceptDeal", {})); - expect(await screen.findByRole("heading", { level: 1, name: "Dashboard" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { level: 1, name: "Bridge" })).toBeInTheDocument(); expect( screen.queryByRole("dialog", { name: "An offer from Halvorsen" }), ).not.toBeInTheDocument(); diff --git a/apps/web/test/fleet-sleep.test.tsx b/apps/web/test/fleet-sleep.test.tsx index e7a5d5b..c284157 100644 --- a/apps/web/test/fleet-sleep.test.tsx +++ b/apps/web/test/fleet-sleep.test.tsx @@ -106,7 +106,7 @@ describe("fleet sleep on screen (Step 24.5, decisions 0.5.137, 0.5.138)", () => it("shows nothing on the Bridge while awake", async () => { renderApp(fakeApi({ getPlayerState: () => owner(false) }), "/"); - expect(await screen.findByRole("heading", { name: "Dashboard" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { name: "Bridge" })).toBeInTheDocument(); expect(screen.queryByLabelText("Fleet asleep")).not.toBeInTheDocument(); }); @@ -115,7 +115,7 @@ describe("fleet sleep on screen (Step 24.5, decisions 0.5.137, 0.5.138)", () => pilots: [samplePlayerState().pilots[0]!, { ...rosa, mode: "manual" }], }); renderApp(fakeApi({ getPlayerState: () => manual }), "/"); - expect(await screen.findByRole("heading", { name: "Dashboard" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { name: "Bridge" })).toBeInTheDocument(); expect(screen.queryByLabelText("Fleet asleep")).not.toBeInTheDocument(); }); diff --git a/apps/web/test/goals.test.tsx b/apps/web/test/goals.test.tsx index 8ccba43..c61ed58 100644 --- a/apps/web/test/goals.test.tsx +++ b/apps/web/test/goals.test.tsx @@ -66,7 +66,7 @@ describe("goals on HQ", () => { it("is not shown during the tutorial", async () => { renderApp(fakeApi({ getPlayerState: () => samplePlayerState() })); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(screen.queryByRole("region", { name: "Goals" })).not.toBeInTheDocument(); }); }); diff --git a/apps/web/test/layout.test.tsx b/apps/web/test/layout.test.tsx index e84a5eb..686a108 100644 --- a/apps/web/test/layout.test.tsx +++ b/apps/web/test/layout.test.tsx @@ -39,14 +39,47 @@ describe("app layout", () => { ); }); - it("opens on the Bridge tab, the Dashboard, with HQs among eight tabs (CR-020)", async () => { + it("opens on the Bridge among four tabs (CR-029)", async () => { renderApp(signedIn()); const nav = await screen.findByRole("navigation", { name: "Main" }); const links = within(nav).getAllByRole("link"); - expect(links).toHaveLength(8); - expect(links[0]).toHaveAccessibleName("Bridge"); + expect(links.map((l) => l.textContent)).toEqual(["Bridge", "Jobs", "Fleet", "More"]); expect(links[0]).toHaveAttribute("aria-current", "page"); - expect(screen.getByRole("heading", { level: 1, name: "Dashboard" })).toBeInTheDocument(); - expect(within(nav).getByRole("link", { name: "HQs" })).toHaveAttribute("href", "/hqs"); + expect(screen.getByRole("heading", { level: 1, name: "Bridge" })).toBeInTheDocument(); + }); + + it.each([ + ["/map", "Map", "Bridge"], + ["/pilots", "Crew", "Fleet"], + ["/market", "Shipyard", "More"], + ["/locations", "Places", "More"], + ["/hqs", "HQs", "More"], + ["/logbook", "Logbook", "More"], + ["/settings", "Settings", "More"], + ])("still opens %s, as %s under %s", async (path, title, tab) => { + renderApp(signedIn(), path); + expect(await screen.findByRole("heading", { level: 1, name: title })).toBeInTheDocument(); + const nav = screen.getByRole("navigation", { name: "Main" }); + expect(within(nav).getByRole("link", { name: tab })).toHaveAttribute("aria-current", "page"); + }); + + it("switches between Ships and Crew inside Fleet", async () => { + renderApp(signedIn(), "/fleet"); + const views = await screen.findByRole("navigation", { name: "Fleet views" }); + await userEvent.click(within(views).getByRole("link", { name: /^Crew/ })); + expect(await screen.findByRole("heading", { level: 1, name: "Crew" })).toBeInTheDocument(); + }); + + it("More reaches every screen without a tab", async () => { + renderApp(signedIn(), "/more"); + await screen.findByRole("heading", { level: 1, name: "More" }); + for (const [name, href] of [ + [/^Shipyard/, "/market"], + [/^Places/, "/locations"], + [/^HQs/, "/hqs"], + [/^Logbook/, "/logbook"], + ] as const) { + expect(screen.getByRole("link", { name })).toHaveAttribute("href", href); + } }); }); diff --git a/apps/web/test/onboarding.test.tsx b/apps/web/test/onboarding.test.tsx index 3e0547c..5946754 100644 --- a/apps/web/test/onboarding.test.tsx +++ b/apps/web/test/onboarding.test.tsx @@ -24,7 +24,7 @@ describe("guest onboarding", () => { expect(api.call).toHaveBeenCalledWith("createGuest", {}); // Lands on Contracts with the intro and the light contract suggested. - expect(await screen.findByRole("heading", { level: 1, name: "Contracts" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { level: 1, name: "Jobs" })).toBeInTheDocument(); expect(await screen.findByLabelText("Intro")).toHaveTextContent( "Halvorsen Lunar Freight needs a pilot.", ); @@ -44,7 +44,7 @@ describe("guest onboarding", () => { it("skips the start screen when a stored session is still valid", async () => { const api = fakeApi({ getPlayerState: () => samplePlayerState() }); renderApp(api); - expect(await screen.findByRole("heading", { level: 1, name: "Dashboard" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { level: 1, name: "Bridge" })).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Start your company" })).not.toBeInTheDocument(); expect(api.call).not.toHaveBeenCalledWith("createGuest", expect.anything()); }); diff --git a/apps/web/test/player-state.test.tsx b/apps/web/test/player-state.test.tsx index 3d9f03c..7153d2e 100644 --- a/apps/web/test/player-state.test.tsx +++ b/apps/web/test/player-state.test.tsx @@ -46,7 +46,7 @@ describe("player state store", () => { getPlayerState: () => samplePlayerState({ activeDispatches: [inFlight(NOW + 30_000)] }), }); renderApp(api); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(stateCalls(api)).toBe(1); act(() => vi.advanceTimersByTime(29_000)); @@ -67,7 +67,7 @@ describe("player state store on focus", () => { it("refetches when the app regains focus", async () => { const api = fakeApi({ getPlayerState: () => samplePlayerState() }); renderApp(api); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); const before = stateCalls(api); // Leaving and returning to the app, as the browser reports it to TanStack Query. diff --git a/apps/web/test/skill-point-prompt.test.tsx b/apps/web/test/skill-point-prompt.test.tsx index b1e19fc..814ddea 100644 --- a/apps/web/test/skill-point-prompt.test.tsx +++ b/apps/web/test/skill-point-prompt.test.tsx @@ -70,7 +70,7 @@ describe("skill point pop-up (Step 17.6, decision 0.5.109)", () => { expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: "Shake on it" })); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); // The deal is done, but the permission prompt is still open. expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(); @@ -85,7 +85,7 @@ describe("skill point pop-up (Step 17.6, decision 0.5.109)", () => { it("Go to Pilots opens the Pilots tab and the pop-up doesn't come back", async () => { renderApp(fakeApi({ getPlayerState: () => afterDeal() }), "/"); await userEvent.click(await screen.findByRole("button", { name: "Go to Pilots" })); - await screen.findByRole("heading", { level: 1, name: "Pilots" }); + await screen.findByRole("heading", { level: 1, name: "Crew" }); expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(); }); @@ -100,7 +100,7 @@ describe("skill point pop-up (Step 17.6, decision 0.5.109)", () => { await waitFor(() => expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(), ); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(); act(() => usePreferences.getState().showTipsAgain()); @@ -109,7 +109,7 @@ describe("skill point pop-up (Step 17.6, decision 0.5.109)", () => { it("doesn't appear with no unspent points", async () => { renderApp(fakeApi({ getPlayerState: () => afterDeal(0, 0) }), "/"); - await screen.findByRole("heading", { level: 1, name: "Dashboard" }); + await screen.findByRole("heading", { level: 1, name: "Bridge" }); // Give the prompt check a turn to settle. await act(async () => {}); expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(); diff --git a/apps/web/test/states.test.tsx b/apps/web/test/states.test.tsx index 25f5a39..e968ca3 100644 --- a/apps/web/test/states.test.tsx +++ b/apps/web/test/states.test.tsx @@ -86,13 +86,13 @@ describe("empty states", () => { it("HQ: no ships points to the Market", async () => { show("/"); - expect(await screen.findByText("No ships yet — buy one in the Market.")).toBeInTheDocument(); + expect(await screen.findByText("No ships yet — buy one in the Shipyard.")).toBeInTheDocument(); expect(screen.getByText("Nothing in flight.")).toBeInTheDocument(); }); it("Fleet: no ships links to the Market", async () => { show("/fleet"); - expect(await screen.findByRole("link", { name: "visit the Market" })).toHaveAttribute( + expect(await screen.findByRole("link", { name: "visit the Shipyard" })).toHaveAttribute( "href", "/market", ); diff --git a/apps/web/test/welcome.test.tsx b/apps/web/test/welcome.test.tsx index 415cb98..884b050 100644 --- a/apps/web/test/welcome.test.tsx +++ b/apps/web/test/welcome.test.tsx @@ -52,7 +52,7 @@ describe("welcome cards (Step 22.2, decision 0.5.131)", () => { await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument()); expect(api.call).toHaveBeenCalledWith("markWelcomeSeen", {}); expect(state().welcomeSeen).toBe(true); - expect(await screen.findByRole("heading", { level: 1, name: "Contracts" })).toBeInTheDocument(); + expect(await screen.findByRole("heading", { level: 1, name: "Jobs" })).toBeInTheDocument(); expect(await screen.findByLabelText("Intro")).toHaveTextContent("needs a pilot"); }); diff --git a/packages/content/src/data/onboarding.ts b/packages/content/src/data/onboarding.ts index 4e93bf5..90bc58a 100644 --- a/packages/content/src/data/onboarding.ts +++ b/packages/content/src/data/onboarding.ts @@ -111,7 +111,7 @@ export const onboardingGoals: OnboardingGoal[] = [ { // Was "Buy a second ship"; the deal now brings two (CR-013). id: "market-ship", - title: "Buy a ship from the Market", + title: "Buy a ship from the Shipyard", condition: { kind: "market-ships-bought", count: 1 }, reward: 1000, }, diff --git a/packages/sim/test/goals.test.ts b/packages/sim/test/goals.test.ts index 25e703b..c65f4e0 100644 --- a/packages/sim/test/goals.test.ts +++ b/packages/sim/test/goals.test.ts @@ -45,7 +45,7 @@ describe("onboarding goal evaluation", () => { it("doesn't count the deal's two ships as a Market purchase; a bought Lighter does (CR-013)", () => { expect(isComplete("market-ship", fresh)).toBe(false); - expect(goals.find((g) => g.id === "market-ship")?.title).toBe("Buy a ship from the Market"); + expect(goals.find((g) => g.id === "market-ship")?.title).toBe("Buy a ship from the Shipyard"); // After buying a Lighter: three ships, one of them from the Market. expect(isComplete("market-ship", { ...fresh, shipsOwned: 3, marketShipsBought: 1 })).toBe(true); });