From f5d0128657a1ad2808ab3a9e51b20cddf0655998 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" <70015+atomantic@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:17:21 +0000 Subject: [PATCH 1/2] fix(ui): lay out the task form by container width, not viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Quick Task dashboard widget squeezed its description textarea down to one character per line. TaskAddForm's compact row keyed its layout off viewport breakpoints, but a dashboard tile is ~250px wide on a 2560px screen — so every `sm:` was unconditionally true and pinned the wide side-by-side row into the narrowest column. The expanded options and the reviewer table (a ~32rem grid) overflowed the tile the same way. Both components now declare `@container` on their own root and key their layout off container queries. The container lives on the component rather than the grid cell because ReviewerPicker also renders in drawers, the settings page and the schedule panel, none of which provide a container ancestor — a cell-level one would leave those stuck in the stacked form. Also guards the flex/grid items that hold selects with `min-w-0` so they shrink instead of overflowing, and wraps the rows whose contents can't. The hover-vs-touch `md:` affordances stay viewport-based; they really are about the device. Records the convention in the dashboard AGENTS.md so the next page component reused as a widget doesn't reintroduce it. --- client/src/components/cos/ReviewerPicker.jsx | 43 ++++++++------- client/src/components/cos/TaskAddForm.jsx | 56 +++++++++++--------- client/src/components/dashboard/AGENTS.md | 2 + 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/client/src/components/cos/ReviewerPicker.jsx b/client/src/components/cos/ReviewerPicker.jsx index 3c05b6d964..80d254ce7e 100644 --- a/client/src/components/cos/ReviewerPicker.jsx +++ b/client/src/components/cos/ReviewerPicker.jsx @@ -26,6 +26,21 @@ const normalizeReviewerValue = (value) => normalizeReviewerSlug(value); // older build either. const CUSTOM_MODEL_OPTION = '[custom]'; +// Shared row grid, one template for the header and every row so their columns +// cannot drift apart. The wide form keeps minimum tracks for order, provider, +// model, effort, optional, max, and remove — together ~32rem, so the collapse is +// keyed to a CONTAINER query (`@xl`), not a viewport one: this picker also +// renders inside a narrow dashboard tile on a wide screen, where a viewport +// `sm:` was unconditionally true and forced the wide grid into a ~250px column. +// Below `@xl` a row collapses to a stacked 2-column label/value block, so a +// narrow container never scrolls horizontally. The header is wide-only — in the +// stacked form each cell carries its own inline label, since a header far above +// a stacked row doesn't associate. +const WIDE_TRACKS = '@xl:grid-cols-[2.5rem_minmax(5rem,1fr)_minmax(8rem,2fr)_minmax(7rem,1fr)_auto_3.25rem_auto]'; +const ROW_CLASS = `grid grid-cols-[auto_1fr] ${WIDE_TRACKS} items-center gap-x-2 gap-y-1 px-1.5 py-1.5 rounded border border-port-border bg-port-bg @xl:border-transparent @xl:bg-transparent @xl:py-0.5 @xl:rounded-none`; +const CELL_LABEL_CLASS = '@xl:hidden text-[10px] uppercase tracking-wide text-gray-600'; +const HEADER_CLASS = `hidden @xl:grid ${WIDE_TRACKS} items-center gap-x-2 px-1.5 text-[10px] uppercase tracking-wide text-gray-600`; + /** * Ordered multi-reviewer picker, rendered as one row per reviewer with the five * per-reviewer controls as columns: **Provider | Model | Effort | Optional | Max @@ -647,18 +662,8 @@ export default function ReviewerPicker({ emit({ reviewers: next }); }; - // Shared row grid. Desktop keeps minimum tracks for order, provider, model, - // effort, optional, max, and remove; under `sm` it collapses to a stacked - // 2-column label/value block so a narrow screen never needs horizontal - // scrolling. The header row is desktop-only — on mobile each cell carries - // its own inline label, since a header far above a stacked row doesn't - // associate. - const ROW_CLASS = 'grid grid-cols-[auto_1fr] sm:grid-cols-[2.5rem_minmax(5rem,1fr)_minmax(8rem,2fr)_minmax(7rem,1fr)_auto_3.25rem_auto] items-center gap-x-2 gap-y-1 px-1.5 py-1.5 rounded border border-port-border bg-port-bg sm:border-transparent sm:bg-transparent sm:py-0.5 sm:rounded-none'; - const CELL_LABEL_CLASS = 'sm:hidden text-[10px] uppercase tracking-wide text-gray-600'; - const HEADER_CLASS = 'hidden sm:grid sm:grid-cols-[2.5rem_minmax(5rem,1fr)_minmax(8rem,2fr)_minmax(7rem,1fr)_auto_3.25rem_auto] items-center gap-x-2 px-1.5 text-[10px] uppercase tracking-wide text-gray-600'; - return ( -
+
Reviewers (in order): {selected.length > 0 && ( @@ -672,14 +677,14 @@ export default function ReviewerPicker({ Max Remove
-
+
{selected.map((value, index) => (
o.value === value)?.description} > -
+
{index + 1}.
- + {reviewerLabel(value)} {renderUnavailableBadge(value)} @@ -716,7 +721,7 @@ export default function ReviewerPicker({
Max iterations
{renderMaxRounds(value, reviewerLabel(value))}
-
+