Step 17.5: client: Pilots tab badge
CI / check (push) Successful in 6m11s
Details
CI / check (push) Successful in 6m11s
Details
The Pilots tab badge shows the total unspent skill points across all pilots, hidden at zero (decision 0.5.109). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1520cb30fd
commit
b0502d4a7a
|
|
@ -1566,3 +1566,7 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
## Step 17.4
|
||||
|
||||
- **No save migration** (CR-017 §3 asks for one): a tutorial save already stores only its completed-run count, and whether the deal is on offer is worked out from the current config, so an older save with four runs needs one more run with no change to its data.
|
||||
|
||||
## Step 17.5
|
||||
|
||||
- **The badge counts points, not pilots**: one pilot with three points to spend shows 3, which matches what the Step 17.6 pop-up lists.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import type { ScreenPath } from "../routes";
|
|||
|
||||
/**
|
||||
* Counts for the navigation's attention badges (decision 0.5.49): goals ready
|
||||
* to claim, ships ready for a job, and pilots with skill points to spend.
|
||||
* to claim, ships ready for a job, and unspent skill points across all pilots
|
||||
* (decision 0.5.109).
|
||||
*/
|
||||
export function tabAttention(state: PlayerState): Partial<Record<ScreenPath, number>> {
|
||||
const restingIds = new Set(state.pilots.filter((p) => p.resting).map((p) => p.id));
|
||||
|
|
@ -17,6 +18,6 @@ export function tabAttention(state: PlayerState): Partial<Record<ScreenPath, num
|
|||
return {
|
||||
"/": state.goals.filter((g) => g.status === "ready").length,
|
||||
"/contracts": readyShips,
|
||||
"/pilots": state.pilots.filter((p) => p.skillPointsAvailable > 0).length,
|
||||
"/pilots": state.pilots.reduce((sum, p) => sum + p.skillPointsAvailable, 0),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { PlayerState, Ship } from "@deadhead/schema";
|
||||
import { screen, within } from "@testing-library/react";
|
||||
import { screen, waitFor, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { tabAttention } from "../src/layout/attention";
|
||||
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
||||
|
|
@ -24,7 +25,7 @@ const owner = (overrides: Partial<PlayerState>) =>
|
|||
samplePlayerState({ tutorial: null, bays: [{ id: "b", locationId: "earth" }], ...overrides });
|
||||
|
||||
describe("navigation attention badges (Step 10E.3)", () => {
|
||||
it("counts ready goals, ships ready for a job and pilots with skill points", () => {
|
||||
it("counts ready goals, ships ready for a job and unspent skill points", () => {
|
||||
const state = owner({
|
||||
goals: [
|
||||
{ id: "a", status: "ready" },
|
||||
|
|
@ -51,7 +52,8 @@ describe("navigation attention badges (Step 10E.3)", () => {
|
|||
{ ...avatar, id: "p3", isAvatar: false, assignedShipId: "s4", skillPointsAvailable: 1 },
|
||||
],
|
||||
} as Partial<PlayerState>);
|
||||
expect(tabAttention(state)).toEqual({ "/": 2, "/contracts": 1, "/pilots": 2 });
|
||||
// Skill points: 2 + 0 + 1 across the three pilots (decision 0.5.109).
|
||||
expect(tabAttention(state)).toEqual({ "/": 2, "/contracts": 1, "/pilots": 3 });
|
||||
});
|
||||
|
||||
it("nudges toward Jobs during the tutorial until a run is in flight", () => {
|
||||
|
|
@ -68,3 +70,45 @@ describe("navigation attention badges (Step 10E.3)", () => {
|
|||
expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Pilots tab badge (Step 17.5, decision 0.5.109)", () => {
|
||||
it("shows the total unspent skill points and disappears once all are spent", async () => {
|
||||
// You with 2 points, Rosa with 1; each raise spends one.
|
||||
const points: Record<string, number> = { avatar: 2, rosa: 1 };
|
||||
const state = () =>
|
||||
owner({
|
||||
pilots: [
|
||||
{ ...avatar, skillPointsAvailable: points.avatar! },
|
||||
{
|
||||
...avatar,
|
||||
id: "rosa",
|
||||
name: "Rosa Vance",
|
||||
isAvatar: false,
|
||||
isVeteran: true,
|
||||
skillPointsAvailable: points.rosa!,
|
||||
},
|
||||
],
|
||||
});
|
||||
const api = fakeApi({
|
||||
getPlayerState: state,
|
||||
spendSkillPoint: ({ pilotId, skillId }) => {
|
||||
points[pilotId]! -= 1;
|
||||
const pilot = state().pilots.find((p) => p.id === pilotId)!;
|
||||
return { pilot: { ...pilot, skills: { ...pilot.skills, [skillId]: 1 } } };
|
||||
},
|
||||
});
|
||||
renderApp(api, "/pilots");
|
||||
const nav = await screen.findByRole("navigation", { name: "Main" });
|
||||
expect(await within(nav).findByTestId("badge-Pilots")).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 raise(avatar.name);
|
||||
await raise("Rosa Vance");
|
||||
await waitFor(() => expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -418,6 +418,6 @@ Spec: `memory-bank/change-requests/CR-017-tutorial-xp-skill-prompt.md` (received
|
|||
- [x] **Step 17.2 — Sim: tutorial XP and the level-1 guarantee.** `deliveryXpFor(record, tier, content)` in `resolution.ts`: a delivery's XP, × `tutorialXpMultiplier` (rounded) on tutorial runs; `resolveDispatch` uses it. The multiplier stays at 2: an Earth–Moon Standard run is 10 XP, so the cheapest five runs make exactly 100 XP, the level-1 threshold (a level gives 2 points). Tests (`packages/sim/test/tutorial-xp.test.ts`): a tutorial run pays exactly 2 × a normal run's XP for the same route and cargo; over 50 board periods, taking the lowest-XP job on every board for five runs (the scripted "Unmarked crates" board included) always reaches level 1 with at least one point.
|
||||
- [x] **Step 17.3 — Sim: tutorial timing.** The CR-001 Step 10A.3 timing test now covers five runs (Earth → Moon → Earth → Moon → Earth → Moon), each taking the slowest job on its board, and requires the total under 7 real minutes (it was four runs under 6), keeping "own a ship in under 10 minutes". Test: `packages/sim/test/tutorial.test.ts`.
|
||||
- [x] **Step 17.4 — Engine: deal availability and placement.** (The rule itself changed in 17.1.) No save migration and no save-version change: the save keeps the completed-run count and the deal is computed from it, so a save with four runs done now simply needs a fifth. Tests (`packages/engine/test/deal-five-runs.test.ts`): after four runs the deal isn't on offer (and accepting is refused), after the fifth, ending at the Moon, it is; accepting puts the avatar on the Courier and Rosa on the Clipper, both at Earth, with no loaner left; a save from before CR-017 with four runs done, opened on this build, still has four runs and no deal until one more run.
|
||||
- [ ] Step 17.5 — Client: Pilots tab badge
|
||||
- [x] **Step 17.5 — Client: Pilots tab badge.** The Pilots tab badge now shows the total unspent skill points across all pilots (was the number of pilots with points), hidden at zero (`tabAttention` in `apps/web/src/layout/attention.ts`, decision 0.5.109). Tests (`apps/web/test/attention.test.tsx`): pilots with 2, 0 and 1 points count 3; on the Pilots screen, the badge reads 3 with You on 2 and Rosa on 1, drops to 2 after one raise, and disappears once all three are spent.
|
||||
- [ ] Step 17.6 — Client: skill point pop-up
|
||||
- [ ] Step 17.7 — Plan amendments and regression
|
||||
|
|
|
|||
Loading…
Reference in New Issue