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({
MaxRemove