---
title: Improve the player profile header
type: task
status: completed
description: >-
  Carry the player profile header forward after the 2026-10-01 stat-group and ranking rework: close
  its known gaps and add the information that most helps a manager decide on a player.
base_uri: user:task/league/improve-player-profile-header.md
created_at: '2026-10-01T21:54:47.589Z'
entity_id: dd409c27-2238-4220-a33e-25a04bce9f1b
finished_at: '2026-10-01T23:04:29.896Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
started_at: '2026-10-01T21:55:26.005Z'
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-02T03:11:04.670Z'
visibility_analyzed_at: '2026-11-05T23:49:38.933Z'
---

# Improve the Player Profile Header

The header shared by the selected-player drawer and the standalone player page (`app/views/components/player-profile/`) is the fastest read of a player in the app. This task carries the header lane forward: close the gaps left by the 2026-10-01 rework, and add what still helps a manager decide on a player.

## Shipped (2026-10-01, league deploy ab721822d on both hosts)

- `6eabde655` — five stat groups (Points, Expected Points, Team Opportunity, Points Added, Projected), a Bye item, and the facts row above the groups. Group derivation lives in `player-header-stat-groups.js`, which has no JSX so a spec can load it (`test/app.player-header-stat-groups.spec.mjs`).
- `bba6e3851` — every cell is ranked and shaded against a per-position value list from `GET /api/seasonlogs/players/distributions` (`api/routes/seasonlogs.mjs`, spec `test/api.seasonlogs-player-distributions.spec.mjs`). Stored position ranks win where present. The population is players with a game, or a projection of at least one point.

## Plan

- [x] Show the Projected group on the standalone player page for a signed-out reader. Projections are computed in the browser by `calculateValues` (`app/core/players/sagas.js`), which that page never runs. The distributions route already reads the stored rest-of-season projections, so a server fallback for the player's own values is the likely route.
- [x] Make the per-game projection denominator agree. The route counts weekly projection rows from `current_season.fantasy_active_week`, while the client counts from the player's game-aware `rest_of_season_start_week`. They differ by one week for a player whose game this week has already been played.
- [x] Decide whether the Games tab keeps both "Pts+ Earned" and "Pts+ Net". Operator chose Net only (`92a9a1974`, live in deploy `1d8a176e2`); the season tables keep Earned, where its ranks live. Earned is net floored at zero per game, so the two columns are identical in every week a player clears the baseline (`app/views/components/player-selected-row/fantasy-stats.js`).
- [x] Layout: on the narrower standalone page a lone wrapped group stretches to full width (`.selected__player-season-stats-group` is `flex 1 1 auto`). Balance the wrap.
- [x] Check readability of full-opacity blue `StackedMetric` cells with dark text (`app/views/components/stacked-metric/stacked-metric.js`), since the header now shades every cell.
- [x] Further header information worth weighing: this week's opponent and kickoff, injury or practice status detail, and recent-weeks trend.

## Shipped (2026-10-01, league deploy 2a86495f4 on both hosts)

- `1d5a7ca24` — a player with no rest-of-season projection in the store is also fetched through `POST /players`, which serves the stored projections, so the Projected group renders on a cold standalone page. The distributions route counts per-game weeks from each player's own game-aware start week, floored at 1, as the client does.
- `f6bc3eebc` — stat groups keep their own width and a wrapped row centres.
- `1fa028b61` — `StackedMetric` value and rank text darkened to clear 4.5:1 on the darkest shade (was 3.43:1 and 3.40:1).
- `f7df3768d` — a Next item in the facts row: the first game not yet kicked off, opponent and local kickoff.
- Injury detail and recent trend were weighed and not added: Status already shows the game designation, and the Practice and Games tabs sit directly under the header.

## Verification

Use `yarn dev:live` per [[███████████████████████████████-█████████]]. Screenshot the drawer from `/leagues/1/players` and the page at `/players/<pid>` at desktop and phone widths, for a top player and a mid-tier one.
