Schedules screen: "Active schedule" selector stretches full-width and overlaps the header #463

Closed
opened 2026-07-19 11:54:39 +02:00 by timothy · 3 comments
Owner

Symptom

On /app/schedules, the "Active schedule" dropdown at the top of the header is far too wide — it stretches across the row and overlaps / distorts the title block and the Add/Edit/Delete buttons in that header box.

Root cause

web/src/screens/SchedulesScreen.tsx:480 renders the selector as <Select label="Active schedule" ... /> without fullWidth={false}. Select defaults fullWidth = true (web/src/components/forms.tsx), which applies .ctv-field-full { width: 100% } (components.css). .ctv-schedule-header is display: flex, so a width: 100% flex item demands the whole row, crowding the sibling <div> (title/counts, flex: 1) and .ctv-schedule-header-actions.

Other inline selects in the app render compact because they size to content (.ctv-field { width: auto }); this one was left at the full-width default.

Suggested fix (pure-SPA)

  • Pass fullWidth={false} to the Select at SchedulesScreen.tsx:480 so it sizes to content.
  • Add a sensible max-width (and/or min-width) via a scoped class or style so a very long schedule name can't re-widen it and crowd the actions; truncate with ellipsis if needed.
  • Verify at narrow widths (the @media block already sets the header to flex-wrap: wrap at shell.css:3872).

No API/DB change. Update spa-conventions.md only if a reusable "compact header select" pattern is introduced.

Done-when

  • "Active schedule" selector sized to content, no overlap with the title block or the action buttons
  • Long schedule names don't distort the header (bounded width / ellipsis)
  • Checked at narrow / wrapped widths
  • Adversarial review passed
## Symptom On `/app/schedules`, the **"Active schedule"** dropdown at the top of the header is far too wide — it stretches across the row and overlaps / distorts the title block and the Add/Edit/Delete buttons in that header box. ## Root cause `web/src/screens/SchedulesScreen.tsx:480` renders the selector as `<Select label="Active schedule" ... />` **without `fullWidth={false}`**. `Select` defaults `fullWidth = true` (`web/src/components/forms.tsx`), which applies `.ctv-field-full { width: 100% }` (`components.css`). `.ctv-schedule-header` is `display: flex`, so a `width: 100%` flex item demands the whole row, crowding the sibling `<div>` (title/counts, `flex: 1`) and `.ctv-schedule-header-actions`. Other inline selects in the app render compact because they size to content (`.ctv-field { width: auto }`); this one was left at the full-width default. ## Suggested fix (pure-SPA) - Pass `fullWidth={false}` to the `Select` at `SchedulesScreen.tsx:480` so it sizes to content. - Add a sensible `max-width` (and/or min-width) via a scoped class or `style` so a very long schedule name can't re-widen it and crowd the actions; truncate with ellipsis if needed. - Verify at narrow widths (the `@media` block already sets the header to `flex-wrap: wrap` at `shell.css:3872`). No API/DB change. Update `spa-conventions.md` only if a reusable "compact header select" pattern is introduced. ## Done-when - [x] "Active schedule" selector sized to content, no overlap with the title block or the action buttons - [x] Long schedule names don't distort the header (bounded width / ellipsis) - [x] Checked at narrow / wrapped widths - [x] Adversarial review passed
timothy added the bugpriority: mediumin-progress labels 2026-07-19 11:55:33 +02:00
Author
Owner

Claiming (same session that filed it). Trivial pure-SPA fix per the root cause above — fullWidth={false} on the header Select + a bounded width. Will PR with a live-E2E screenshot.

Claiming (same session that filed it). Trivial pure-SPA fix per the root cause above — `fullWidth={false}` on the header `Select` + a bounded width. Will PR with a live-E2E screenshot.
Author
Owner

Live visual verification (Playwright headless, real local instance)

Stood up a local build, seeded 3 schedules incl. a max-length (50-char) name, logged in, and screenshotted the header at two widths:

  • 1280px: the "Active schedule" selector is bounded (~260px, the long name truncates with the chevron visible) and sits cleanly between the title/counts block and the Add schedule / Edit / Delete buttons — no overlap. (Pre-fix, .ctv-field-full { width:100% } made it demand the whole row.)
  • 560px: the header wraps (@media flex-wrap: wrap) and the selector stays bounded rather than overlapping — the fix removes the overlap the wrap rule was partially papering over.

Also confirmed schedule names are capped at 50 chars server-side (422 beyond), so the worst case is bounded and maxWidth:260 handles it.

All four Done-when boxes ticked. Cold review + verdict already posted.

## Live visual verification (Playwright headless, real local instance) Stood up a local build, seeded 3 schedules incl. a **max-length (50-char)** name, logged in, and screenshotted the header at two widths: - **1280px**: the "Active schedule" selector is bounded (~260px, the long name truncates with the chevron visible) and sits cleanly between the title/counts block and the Add schedule / Edit / Delete buttons — **no overlap**. (Pre-fix, `.ctv-field-full { width:100% }` made it demand the whole row.) - **560px**: the header wraps (`@media` `flex-wrap: wrap`) and the selector stays bounded rather than overlapping — the fix removes the overlap the wrap rule was partially papering over. Also confirmed schedule names are capped at 50 chars server-side (422 beyond), so the worst case is bounded and `maxWidth:260` handles it. All four Done-when boxes ticked. Cold review + verdict already posted.
timothy removed the in-progress label 2026-07-19 12:21:20 +02:00
Author
Owner

Closed — merged in PR #465

Root cause: the "Active schedule" <Select> (SchedulesScreen.tsx:480) rendered at the component default fullWidth=true, applying .ctv-field-full { width:100% }. In the flex .ctv-schedule-header that made the selector demand the whole row, overlapping the title/counts block and the Add/Edit/Delete buttons.

Fix (pure-SPA/CSS): fullWidth={false} (sizes to content) + style={{maxWidth:260,minWidth:150}} so a long name cannot re-widen it; the native <select> value truncates within the frame (.ctv-select { min-width:0 }). Regression test asserts the wrapper is not .ctv-field-full.

Files: web/src/screens/SchedulesScreen.tsx (+ SchedulesScreen.test.tsx). No API/DB/docs change (one-off style prop, not a convention).

Verification: 25 SchedulesScreen tests + lint + typecheck + prod build green; cold review MERGEABLE; Playwright-headless screenshots at 1280px + 560px (incl. a 50-char max-length name) confirm the selector is bounded and no longer overlaps. Schedule names are capped at 50 chars server-side, so the worst case is bounded.

No follow-ups.

## Closed — merged in PR #465 **Root cause:** the "Active schedule" `<Select>` (`SchedulesScreen.tsx:480`) rendered at the component default `fullWidth=true`, applying `.ctv-field-full { width:100% }`. In the flex `.ctv-schedule-header` that made the selector demand the whole row, overlapping the title/counts block and the Add/Edit/Delete buttons. **Fix (pure-SPA/CSS):** `fullWidth={false}` (sizes to content) + `style={{maxWidth:260,minWidth:150}}` so a long name cannot re-widen it; the native `<select>` value truncates within the frame (`.ctv-select { min-width:0 }`). Regression test asserts the wrapper is not `.ctv-field-full`. **Files:** `web/src/screens/SchedulesScreen.tsx` (+ `SchedulesScreen.test.tsx`). No API/DB/docs change (one-off style prop, not a convention). **Verification:** 25 SchedulesScreen tests + lint + typecheck + prod build green; cold review MERGEABLE; Playwright-headless screenshots at 1280px + 560px (incl. a 50-char max-length name) confirm the selector is bounded and no longer overlaps. Schedule names are capped at 50 chars server-side, so the worst case is bounded. No follow-ups.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#463