feat: roomier BO2 companion with +/- rank stepping
All checks were successful
Test and deploy staging / staging (push) Successful in 2m29s

- Scale the COD page to the screen: larger gun art, text and progress
  ribbon, sized from one viewport unit so it fills a MacBook Pro 16"
  and big monitors and still fits 1280x720 without scrolling.
- Five columns on wide screens and four below, picked by CSS, so the
  server-rendered page already has the final layout.
- Replace the tabs/filters/overview rows with one toolbar of matching
  controls.
- Player and weapon ranks step with + and -: level 55 + 1 is the next
  prestige at level 1, up to max prestige. Presses show at once, batch
  while a save is running, and stepRank does the arithmetic in SQL.
- Clicking a rank still opens exact entry for big jumps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 21:50:19 +00:00
parent 8eec207f2d
commit 0ee9ed0abb
10 changed files with 1128 additions and 588 deletions

View File

@@ -222,7 +222,7 @@ test('weapon/player ranks, challenge progress and osu goals/scores/training pers
await sql`select c.id,c.target from tracker_challenges c join tracker_games g on c.game_id=g.id where g.slug='black-ops-3' and c.target>1 limit 1`;
const cod = '/projects/game-tracker/call-of-duty/black-ops-3';
await page.goto(cod);
await page.getByRole('button', { name: 'Update my rank' }).click();
await page.getByRole('button', { name: 'Player level and prestige' }).click();
await page.getByLabel('Current level', { exact: true }).fill('42');
await page.getByLabel('Prestige', { exact: true }).fill('3');
await page.getByRole('button', { name: 'Save rank' }).click();
@@ -537,7 +537,12 @@ test('live weapon controls save immediately, infer max level from prestige, and
await expect(level).toHaveText(/LEVEL2/);
await card.getByRole('button', { name: `Level down ${weapon.name}` }).click();
await expect(level).toHaveText(/LEVEL1/);
await card.getByRole('button', { name: `Prestige up ${weapon.name}` }).click();
// Without known caps + / − only move the level; prestige is typed into the exact-rank dialog.
await level.click();
await page.getByLabel('Prestige', { exact: true }).fill('1');
await page.getByRole('button', { name: 'Save rank' }).click();
await expect(page.getByRole('dialog')).toHaveCount(0);
await page.getByRole('button', { name: `${weapon.name} camos and milestones` }).click();
await expect(card).toContainText('Max level reached');
await expect(card.getByRole('checkbox', { name: /max level/ })).toHaveCount(0);
expect(
@@ -547,38 +552,28 @@ test('live weapon controls save immediately, infer max level from prestige, and
).toMatchObject({ level: 1, prestige: 1 });
const concurrent = await Promise.all(
Array.from({ length: 4 }, () =>
action(page.request, cod + '?/adjustWeaponRank', {
itemId: String(weapon.id),
field: 'level',
delta: '1'
})
action(page.request, cod + '?/stepRank', { itemId: String(weapon.id), delta: '1' })
)
);
expect(concurrent.every((r) => r.status === 200)).toBe(true);
await page.reload();
await page.getByRole('button', { name: `${weapon.name} camos and milestones` }).click();
await expect(level).toHaveText(/LEVEL5/);
await page.route('**/*adjustWeaponRank', (route) => route.abort());
await page.route('**/*stepRank', (route) => route.abort());
await card.getByRole('button', { name: `Level up ${weapon.name}` }).click();
await expect(card.getByRole('status')).toContainText(/fetch|save|network/i);
await expect(level).toHaveText(/LEVEL5/);
await page.unroute('**/*adjustWeaponRank');
await page.unroute('**/*stepRank');
await card.getByRole('button', { name: `Level up ${weapon.name}` }).click();
await expect(level).toHaveText(/LEVEL6/);
for (const delta of ['999', '0', '1.5', ''])
expect(
(await action(page.request, cod + '?/stepRank', { itemId: String(weapon.id), delta })).status
).toBe(400);
expect(
(
await action(page.request, cod + '?/adjustWeaponRank', {
await action(page.request, '/projects/game-tracker/call-of-duty/wwii?/stepRank', {
itemId: String(weapon.id),
field: 'level',
delta: '999'
})
).status
).toBe(400);
expect(
(
await action(page.request, '/projects/game-tracker/call-of-duty/wwii?/adjustWeaponRank', {
itemId: String(weapon.id),
field: 'level',
delta: '1'
})
).status
@@ -586,9 +581,8 @@ test('live weapon controls save immediately, infer max level from prestige, and
const anonymous = await playwright.request.newContext({ baseURL: origin });
expect(
(
await action(anonymous, cod + '?/adjustWeaponRank', {
await action(anonymous, cod + '?/stepRank', {
itemId: String(weapon.id),
field: 'level',
delta: '1'
})
).status
@@ -911,9 +905,8 @@ test('BO2 armoury fits every gun, enforces caps and advances all rank progress w
const overall = page.getByRole('progressbar', { name: 'Game completion', exact: true });
await expect(levels).toHaveAttribute('aria-valuenow', '0');
await card.getByRole('button', { name: 'Level up M27', exact: true }).click();
await expect(card.getByRole('button', { name: 'Edit M27 level and prestige' })).toHaveText(
'LEVEL2/17'
);
const m27Rank = card.getByRole('button', { name: 'Edit M27 level and prestige' });
await expect(m27Rank).toHaveText('P0 2/17');
const first = Number(await levels.getAttribute('aria-valuenow'));
expect(first).toBeGreaterThan(0);
const before = Number(await overall.getAttribute('aria-valuenow'));
@@ -942,37 +935,51 @@ test('BO2 armoury fits every gun, enforces caps and advances all rank progress w
})
).status
).toBe(400);
expect(
(
await action(page.request, cod + '?/adjustWeaponRank', {
itemId: String(m27.id),
field: 'prestige',
delta: '1'
})
).status
).toBe(400);
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '17',
prestige: '0'
});
// Presses from two devices at max level both count: level 17 → prestige 1 level 2.
const simultaneous = await Promise.all(
[1, 2].map(() =>
action(page.request, cod + '?/adjustWeaponRank', {
itemId: String(m27.id),
field: 'prestige',
delta: '1'
})
action(page.request, cod + '?/stepRank', { itemId: String(m27.id), delta: '1' })
)
);
expect(simultaneous.filter((r) => r.status === 200)).toHaveLength(1);
expect(simultaneous.every((r) => r.status === 200)).toBe(true);
expect(
(
await sql`select level,prestige from tracker_weapon_ranks where user_id=${userId} and item_id=${m27.id}`
)[0]
).toMatchObject({ level: 1, prestige: 1 });
).toMatchObject({ level: 2, prestige: 1 });
await page.reload();
await expect(card).toContainText('18/51');
await expect(m27Rank).toHaveText('P1 2/17');
await card.getByRole('button', { name: 'Level down M27', exact: true }).click();
await card.getByRole('button', { name: 'Level down M27', exact: true }).click();
await expect(m27Rank).toHaveText('P0 17/17');
await expect
.poll(
async () =>
(
await sql`select level,prestige from tracker_weapon_ranks where user_id=${userId} and item_id=${m27.id}`
)[0]
)
.toMatchObject({ level: 17, prestige: 0 });
await card.getByRole('button', { name: 'Level up M27', exact: true }).click();
await expect(m27Rank).toHaveText('P1 1/17');
await action(page.request, cod + '?/saveRank', { level: '55', prestige: '0' });
await page.reload();
const badge = page.getByLabel('Player level and prestige');
await page.getByRole('button', { name: 'Level up player rank', exact: true }).click();
await expect(badge).toContainText('PRESTIGE1/10');
await expect(badge).toContainText('LEVEL1/55');
await page.getByRole('button', { name: 'Level down player rank', exact: true }).click();
await expect(badge).toContainText('LEVEL55/55');
await expect
.poll(
async () => (await sql`select level,prestige from tracker_ranks where user_id=${userId}`)[0]
)
.toMatchObject({ level: 55, prestige: 0 });
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '17',
@@ -980,7 +987,7 @@ test('BO2 armoury fits every gun, enforces caps and advances all rank progress w
});
await action(page.request, cod + '?/saveRank', { level: '42', prestige: '3' });
await page.reload();
await expect(card).toContainText('51/51');
await expect(m27Rank).toHaveText('P2 17/17');
await expect(card.getByRole('button', { name: 'Level up M27' })).toBeDisabled();
await expect(page.getByRole('img', { name: 'Prestige 3 emblem' })).toBeVisible();
const progress = await overall.getAttribute('aria-valuenow');