Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1248,3 +1248,42 @@ on small screens, not by trimming destinations.
the iOS home indicator. *Rejected:* a hamburger menu (hides the active state
the issue wants kept visible) and a 6-tab bar (too cramped with text labels on
a narrow phone).

### Mobile-first audit — phone form controls + issue-page field order

A rendered phone-viewport audit (360/390/430/768 + desktop regression, touch +
DPR emulation) of the highest-traffic views, following the bottom-tab-bar nav
(PROG-79). No blocking defects remained — no horizontal page scroll, correct
viewport meta, the new-issue modal fits at 360 px — but two systemic touch
problems and one layout-order problem surfaced and are fixed. (M/L findings —
small issue-page text-links, Structure `+add` buttons, keyboard-hint clutter —
are recorded in the audit report but deferred to keep this change tight.)

- **Form controls are sized for touch on phones, globally.** Every `text-xs`
control (board/agenda/search filters, the new-issue dialog, the issue sidebar)
rendered at 14.4 px, under the 16 px below which iOS Safari zooms-on-focus and
stays zoomed; many were also ~36 px tall, under the 44 px touch floor. Fixed
with one rule in `styles.css` — `@media (max-width:639px)` forcing
`input/select/textarea` to `font-size:16px` and `min-height:44px` (checkboxes/
radios excepted; `min-height` never shrinks a taller field like the comment
box). *Decisions within:* (1) **a single global rule, not per-component** — it
covers surfaces with bespoke `<select>` markup (Agenda, the dialog) that a
shared-component fix would miss, and prevents future drift. (2) **Unlayered on
purpose** — Tailwind's `text-xs` utility lives in a cascade layer, and
unlayered rules beat any layer regardless of specificity, so the element
selector wins without `!important`. (3) **Scoped to ≤639 px** (Tailwind's `sm`
floor) so the dense desktop sizing is untouched — mobile is primary, desktop
degrades to nothing here. Verified: zero sub-16 px and zero sub-44 px form
controls at 360/390/430 across all audited views.
- **The issue page surfaces its action fields above the activity log on
mobile.** The layout was `flex-col md:flex-row`, so on a phone the field
`<aside>` (Status/Priority/Estimate/Due/Container/Arc/Tags/Work-on-this) stacked
*after* the description **and** the full comments+activity timeline — on a
heavily-edited issue you scrolled past dozens of activity rows to change
status. Rebuilt as a CSS grid: mobile (single column) flows description →
field strip → timeline by source order; desktop pins content to the left
column across both rows and the fields to the right column via explicit
`col-start`/`row-start`/`row-span`, leaving the desktop view byte-identical.
*Rejected:* duplicating `<TimelineSection>` behind `hidden`/`md:hidden` (double
mount) and flex `order` hacks (can't express the desktop "right column spans
two left-stacked rows" cleanly).
17 changes: 13 additions & 4 deletions src/client/pages/IssuePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,14 @@ export default function IssuePage({
</h1>
</header>

<div className="mt-6 flex flex-col gap-8 md:flex-row">
<div className="min-w-0 flex-1">
{/* Mobile-first ordering (mobile audit): description → field strip →
timeline, so the primary actions (status/priority/due/…) aren't buried
under a potentially long activity history on a phone. A grid pins the
desktop layout (content in the left column across both rows, fields in
the right column) while the single-column mobile flow just follows
source order. */}
<div className="mt-6 grid grid-cols-1 gap-8 md:grid-cols-[minmax(0,1fr)_14rem]">
<div className="min-w-0 md:col-start-1 md:row-start-1">
<EditableMarkdown
value={issue.description}
placeholder="Add a description…"
Expand All @@ -132,10 +138,9 @@ export default function IssuePage({
updateIssue(issue.id, { description }, { toastOnError: false })
}
/>
<TimelineSection issue={issue} workspace={workspace} />
</div>

<aside className="w-full shrink-0 space-y-4 md:w-56">
<aside className="w-full space-y-4 md:col-start-2 md:row-start-1 md:row-span-2">
<Field label="Status">
<FieldSelect
value={issue.status}
Expand Down Expand Up @@ -240,6 +245,10 @@ export default function IssuePage({
{issue.completedAt && <p>Completed {fmtTime(issue.completedAt)}</p>}
</div>
</aside>

<div className="min-w-0 md:col-start-1 md:row-start-2">
<TimelineSection issue={issue} workspace={workspace} />
</div>
</div>
</div>
);
Expand Down
22 changes: 22 additions & 0 deletions src/client/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@
min-height: 100dvh;
}

/* iOS Safari auto-zooms when a focused form control has font-size < 16px, then
leaves the page zoomed — jarring and constant on a phone. Our dense filter
and dialog controls use `text-xs` (14.4px here), so on small screens force
every form control to 16px. Desktop keeps the compact sizing (≥sm). Mobile
audit. Unlayered on purpose: it must beat Tailwind's `text-xs` utility, and
cascade layers lose to unlayered rules regardless of selector specificity. */
@media (max-width: 639px) {
input,
select,
textarea {
font-size: 16px;
}
/* Meet the 44px touch-target floor on phones: the dense filter/sidebar/dialog
controls render ~36–40px from their compact `py-1`. min-height never shrinks
a taller field (e.g. the comment textarea), so it's safe to apply broadly. */
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
min-height: 44px;
}
}

/* Minimal Markdown typography (Tailwind preflight strips element defaults).
Used for issue descriptions and comments; intentionally lighter than the
official typography plugin — one more dependency we don't need yet. */
Expand Down