Skip to content

Make the UI usable on mobile - #21

Open
dgruhin-hrizn wants to merge 1 commit into
L1AD:mainfrom
dgruhin-hrizn:pr/mobile-responsive
Open

Make the UI usable on mobile#21
dgruhin-hrizn wants to merge 1 commit into
L1AD:mainfrom
dgruhin-hrizn:pr/mobile-responsive

Conversation

@dgruhin-hrizn

Copy link
Copy Markdown

What

Makes the viewer usable on a phone. Today the layout is desktop-only: three fixed flex columns (280px sidebar + 3×320px kanban + 400px detail panel) needing ~1740px, no @media queries anywhere, hover-only affordances, 32px touch targets and a lot of 9–11px type. On a 375px screen the sidebar alone takes 75% of the viewport and the board is scrolled off-canvas.

Approach

Desktop-first overrides, appended as one contiguous block before </style>. The existing ~1450 lines are all correct at width, so rewriting mobile-first would mean touching everything — this way mobile lives in one place, and is one place to delete if you don't want it.

Three breakpoints:

Query Purpose
max-width: 1023px sidebar → drawer, detail panel → sheet
max-width: 767px status tabs, type scale, 44px targets
hover: none kills sticky :hover, adds :active

That last one is capability-based rather than width-based, so it also fixes touch laptops and iPads.

Notable decisions

  • The hamburger lives in .main, not .view-header. The empty state (#no-session) has no header at all, so a header-mounted button would strand you with no way back to the sessions list.
  • The status tabs are pure CSS. State is one <body data-kanban-tab> attribute whose effect is scoped inside the media query, so crossing the breakpoint needs no resize listener and no state reconciliation — desktop simply ignores the attribute.
  • .kanban's display is never overridden, because switchView() writes it as an inline style and inline beats any media query. The mobile single-column view hides columns instead.
  • The service worker is deliberately inert. It has a fetch handler purely for Android installability but caches nothing, so the SSE stream can never be served stale.
  • Icons are derived from the existing .logo-mark checkmark via qlmanage + sips. No new dependencies.

Three bugs found while building

  1. Closing the drawer inside fetchTasks/showAllTasks would have slammed it shut on every SSE refresh, since refreshCurrentView() and the event handler also call them. Moved to selectSession()/selectAllTasks() wrappers on the user-initiated paths only.
  2. renderTimeline read --tl-gutter from :root while the override lands on body, misaligning the axis by 34px.
  3. min-height on .task-card acted as a shrink target, not a floor — .column-tasks is a flex column, so cards collapsed to 56px and spilled their text. Fixed with flex-shrink: 0.

Verification

Walked drawer → session select → detail sheet → Esc → timeline → tap tooltip → all three status tabs → theme toggle, at 375/768/1440 in both themes, plus a real iPhone.

Desktop is unchanged — measured identical at 1440px: 280px static sidebar, 320px columns, 400px detail panel, 13px body, 32px icon buttons, 16px 24px header padding, --tl-gutter: 160px, and every mobile rule inert.

Two things emulation can't cover and I could only check by eye on device: iOS input focus-zoom (the 16px input rule is in place) and env(safe-area-inset-*) on a notched device.

Independent of the other PR

I've opened a second PR for dark-mode contrast. They're independent and touch disjoint regions of the file — I verified both merge cleanly onto main in either order, and that merging both reproduces exactly the tree I tested. Take either, both, or neither.

The layout is desktop-only: three fixed flex columns (280px sidebar +
3x320px kanban + 400px detail panel) needing roughly 1740px, no media
queries at all, hover-only affordances, 32px touch targets and a lot of
9-11px type. On a 375px phone the sidebar alone takes 75% of the screen
and the board is scrolled off-canvas.

Adds a desktop-first override block appended before </style>. The
existing 1450 lines are all correct at width, so mobile lives in one
contiguous place rather than being threaded through the file. Three
breakpoints: <=1023px (drawer + sheet), <=767px (tabs, type scale, 44px
targets) and (hover: none), which is capability-based so it also covers
touch laptops and iPads.

- The sidebar becomes an off-canvas drawer. The hamburger lives in
  .main rather than .view-header, because the empty state has no header
  at all and would otherwise have no way to reach the sessions list.
- The detail panel becomes a full-screen sheet, riding on the existing
  .visible toggle -- Esc and the close button needed no changes.
- The kanban collapses to one column behind a sticky status-tab bar.
  State is a single <body data-kanban-tab> attribute whose effect is
  scoped inside the media query, so crossing the breakpoint needs no
  resize listener and no state reconciliation.
- Timeline: --tl-gutter replaces a 160px constant that was hardcoded in
  both CSS and JS, so bars and axis stay aligned at either size. Hover
  tooltips become tap-to-open, and the axis drops from 7 ticks to 3
  where the labels would otherwise overlap solid.
- PWA: manifest, plus a deliberately inert service worker -- it has a
  fetch handler for Android installability but caches nothing, so the
  SSE stream can never be served stale. Icons are derived from the
  existing checkmark logo with qlmanage and sips; no new dependencies.

Three bugs surfaced while building this:

- Closing the drawer inside fetchTasks/showAllTasks would have slammed
  it shut on every SSE refresh, since refreshCurrentView() and the
  event handler also call them. Moved to selectSession()/
  selectAllTasks() wrappers on the user-initiated paths.
- renderTimeline read --tl-gutter from :root while the override lands
  on body, misaligning the axis by 34px.
- min-height on .task-card acted as a shrink *target*, not a floor:
  .column-tasks is a flex column, so cards collapsed to 56px and spilled
  their text. Fixed with flex-shrink: 0.

theme-color is set to the accent so iOS tints the status bar and browser
controls to match the app rather than painting a plain white band.

Desktop is unchanged -- verified identical at 1440px: 280px static
sidebar, 320px columns, 400px detail panel, 13px body, 32px icon
buttons, 16px/24px header padding, --tl-gutter 160px, and every mobile
rule inert.

Not verified, needs real hardware beyond what emulation covers: iOS
input focus-zoom and env(safe-area-inset-*) on a notched device.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant