Files
F-r-ldraledigplannerare/new-features/alignment-plan.md
T
2026-09-20 11:46:07 +02:00

5.4 KiB
Raw Blame History

Align the year-row timelines in the Overview tab

Context

In the Dashboard Overview tab each year is a <details class="disclosure year-row"> whose <summary> is its own grid: grid-template-columns: 3.5rem minmax(0, 1fr) auto var(--icon-sm) (frontend/src/styles/components.css:225). Because every row is an independent grid, the auto figures column is sized per row by that row's own text — "· expiring 5", a wider saved number, a longer parent name, a 3-digit vs 2-digit PL value. The minmax(0, 1fr) track column absorbs the difference, so the timeline bars end at a different x per year (see timeline-offset.png: 2029's track is clearly shortest, 2025 the longest). The figures columns are ragged for the same reason.

Goal: every year's timeline track starts and ends at the same x, and the figure columns line up down the list — without hardcoding a pixel width that breaks in Swedish (sv strings are longer than en) or when a number gains a digit.

Approach — one shared grid via subgrid

Promote the column definition from each summary to the .year-rows list and have the rows opt into it with grid-template-columns: subgrid. Tracks are then sized once, by the widest content in the whole list, and every row inherits identical column widths. Content still sizes the columns, so it stays bilingual-safe.

Split the figures cell into its three real columns (name | PL left | vacation group) at the list level too, so the numbers align vertically across years as well.

CSS only — no TSX changes. The existing markup already has the right elements (.year-rows → .year-row (details) → .year-row__summary / .year-row__body, .year-row__figures → .year-row__parent { display: contents }).

Files

  • frontend/src/styles/components.css — .year-rows (162), .year-row__summary (225), .year-row__summary::after (238), .year-row__year (244), .year-row__track (252), .year-row__figures (289), .year-row__body (322)
  • frontend/src/styles/responsive.css — mobile block at 93–120 (must reset subgrid)

Shape

.year-rows becomes the grid owner:

.year-rows {
  display: grid;
  /* year | track | name | PL left | vacation group | chevron */
  grid-template-columns: 3.5rem minmax(0, 1fr) auto auto auto var(--icon-sm);
}

Each row spans it and re-exposes it:

.year-row {           /* the <details> */
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
}
.year-row__summary {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  /* keep: align-items, column-gap, row-gap, padding, font-weight */
}
.year-row__body { grid-column: 1 / -1; }

Column placement inside the summary shifts to match the six tracks: year 1, track 2, .year-row__figures grid-column: 3 / 6 + grid-template-columns: subgrid (keep display: grid, align-items: baseline, white-space: nowrap; drop its own auto auto auto), chevron ::after grid-column: 6. .year-row__parent { display: contents } stays — each parent's three spans land in the three shared columns.

Notes:

  • --sp-4 column-gap must be declared on .year-rows as well, since a subgrid inherits the parent's gaps unless it sets its own; keep the summary's column-gap so the values agree.
  • .year-rows > .disclosure:last-child { border-bottom: none } and the .disclosure hairlines in primitives.css are unaffected (borders on the details still paint).
  • In the solo case .year-row__name isn't rendered; that column collapses to 0 for every row — still uniform.
  • Left-over grid-row: 1 declarations on year/track/figures stay valid.

Fallback

Wrap the promotion in a support check so older engines keep today's (merely ragged) layout rather than collapsing:

@supports (grid-template-columns: subgrid) { /* the rules above */ }

Keep the current .year-row__summary rule as the base; the @supports block overrides it.

Mobile

responsive.css already re-lays the summary as 3rem minmax(0, 1fr) var(--icon-sm) with the figures wrapped to row 2 as a flex column. Inside that media query, reset the new rules: .year-rows { display: flex; flex-direction: column } and .year-row { display: block } — so the mobile block's existing declarations apply unchanged. Place these resets after the @supports block in source order, or repeat them inside their own @supports guard, so specificity/order lets mobile win.

Verification

  1. cd frontend && npm run dev, open the Dashboard Overview tab with the demo workspace (demo@example.com) — delegate the browser pass to a Sonnet subagent (never drive Chrome from the coordinator).
  2. Check: every year's track left and right edge align to the pixel, including 2029 (shortest figures) vs 2025 (no expiring) vs a row with expiring; the "PL left" and "Vacation left" labels form straight columns.
  3. Repeat in both Swedish and English (longer sv strings are the real stress test), and in a solo workspace (no parent names) as well as a couple workspace.
  4. Narrow to the mobile breakpoint and confirm figures still wrap under the track, one parent per line (use the same-origin iframe trick — resize_window is a no-op in this setup).
  5. Gates (delegate to a Haiku subagent, raw output): from frontend/ npm run typecheck, npm run lint, npm run coverage. CSS-only change — YearRows.test.tsx and Dashboard.test.tsx should be untouched and green.