---
title: Extract a Shared Standings Row Component
type: task
status: planned
description: >-
  Extract the standings row markup into one component consumed by both the standings page and the
  league home odds table, which currently duplicate it because the Material UI removal was live
  across standings.js when the dashboard shipped.
base_uri: user:task/league/extract-shared-standings-row-component.md
created_at: '2026-09-04T21:57:18.578Z'
entity_id: 6251be4d-8b4b-4acb-8472-818b5561751f
owner_identity_uri: █████████████████████████
public_read: true
relations:
  - relates [[user:task/league/make-league-dashboard-public-homepage.md]]
  - blocked_by [[█████████████████████-██████-███-███-████████-█████]]
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-04T21:57:46.692Z'
visibility_analyzed_at: '2026-11-05T23:49:16.106Z'
---

# Extract a Shared Standings Row Component

## Overview

`app/views/pages/league-home/league-home.js` renders its own compact odds table in plain `div.table__row` / `div.table__cell` markup that mirrors `app/views/pages/standings/standings.js`. The two are separate implementations of the same row, including the postseason gate that hides playoff and bye odds once the regular season ends.

That duplication is a timing decision, not an end state. When the dashboard shipped in `11d79c552`, [[█████████████████████-██████-███-███-████████-█████]] was in progress across `standings.js`, so extracting a component would have meant editing a file another task was rewriting and creating a component directory in the middle of that work.

## Plan

- [ ] **Wait for the Material UI removal to clear `standings.js`**
  - Verify: [[█████████████████████-██████-███-███-████████-█████]] no longer lists `app/views/pages/standings/standings.js` as in-flight.

- [ ] **Extract the row and the postseason gate into one component**
  - File: a new directory under `app/views/components/`.
  - Changes: move the `div.table__row` / `div.table__cell` markup and the `is_regular_season` column gate out of both call sites; the gate must apply to the header cells as well as the body cells, since porting only one misaligns the columns.
  - Verify: the extracted component renders the same DOM as each call site did before, compared against a capture of the current pages.

- [ ] **Consume it from both surfaces**
  - File: `app/views/pages/standings/standings.js` and `app/views/pages/league-home/league-home.js`.
  - Verify: `grep -n "table__cell metric" app/views/pages/standings/standings.js app/views/pages/league-home/league-home.js` returns nothing, and `/leagues/1` and `/leagues/1/standings` render unchanged.

- [ ] **Gates stay green**
  - Verify: `yarn test:isolated test/app.mui-import-ratchet.spec.mjs test/app.connected-component-prop-contract.spec.mjs`, then `yarn lint` and `yarn check:types`. If the extraction drops the last import of any `@mui/*` symbol, lower the ratchet budget in the same commit.

## Constraints

- The two surfaces show different columns: standings adds record, all-play record, all-play %, points and draft index; the dashboard shows odds only. The component has to take the column set rather than assume one.
- The dashboard omits `division_odds` because league 1's current season runs `division_berth_count: 0` with no divisions and never populates it.
- `@mui/*` imports are under an exact-match ratchet that fails on a removal as well as an addition, so any import the extraction drops needs the budget lowered in the same commit.
