From b2a4e66675a0ee0442d0ee327cfb0437578097b8 Mon Sep 17 00:00:00 2001 From: dev_agent Date: Sat, 10 Oct 2026 23:10:16 -0400 Subject: [PATCH] Reflow tablet portrait layouts: Plan week 4 + 3, Settings one column At 768-1199 px held upright the shell marks itself portrait (new useOrientation hook over the existing tablet breakpoint) and trims its side padding; the Plan week lays out four days over three instead of scrolling sideways, and Settings (mock 2e) stacks its two columns into one. Vitest covers every primary route at 820x1180 and 1180x820. Task: 529c743e-4464-479b-b750-7ec41a0c9772 Co-Authored-By: Claude Opus 5.5 --- web/src/plan/PlanPage.module.css | 6 + web/src/plan/PlanPage.tsx | 10 +- web/src/settings/SettingsTabs.module.css | 6 + web/src/settings/SettingsTabs.tsx | 9 +- web/src/shell/AppShell.module.css | 6 + web/src/shell/AppShell.tsx | 8 +- web/src/shell/tabletPortrait.test.tsx | 225 +++++++++++++++++++++++ web/src/shell/useBreakpoint.ts | 19 ++ web/src/test/matchMedia.ts | 21 ++- 9 files changed, 297 insertions(+), 13 deletions(-) create mode 100644 web/src/shell/tabletPortrait.test.tsx diff --git a/web/src/plan/PlanPage.module.css b/web/src/plan/PlanPage.module.css index 03fcf98..74ea096 100644 --- a/web/src/plan/PlanPage.module.css +++ b/web/src/plan/PlanPage.module.css @@ -410,6 +410,12 @@ list-style: none; } +/* An upright tablet's week: four days over three, every column wide enough + that nothing scrolls sideways. */ +.portraitPage .week { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + .column { min-width: 0; display: flex; diff --git a/web/src/plan/PlanPage.tsx b/web/src/plan/PlanPage.tsx index d15f80f..471268e 100644 --- a/web/src/plan/PlanPage.tsx +++ b/web/src/plan/PlanPage.tsx @@ -8,7 +8,7 @@ import { Confirm } from '../settings/LeaveGuard.tsx' import { methodLabels } from '../settings/RegularsSettingsPage.tsx' import { formatClockTime, useHouseholdSettings } from '../settings/api.ts' import { supplyRunScreen, useGenerateSupplyRun, useSupplyRun, WEEK_PARAM } from '../shopping/api.ts' -import { useBreakpoint, type Breakpoint } from '../shell/useBreakpoint.ts' +import { useBreakpoint, useOrientation, type Breakpoint } from '../shell/useBreakpoint.ts' import { Button } from '../ui/Button.tsx' import buttonStyles from '../ui/Button.module.css' import { EmptyState } from '../ui/EmptyState.tsx' @@ -673,8 +673,10 @@ function PushFailure({ error, cached, onRetry }: { error: Error; cached: boolean // week; the week buttons keep ?week= on the week shown. The board keeps its place in the tree across layouts, so crossing a // breakpoint keeps a draft being followed. From tablet up a dinner is // dragged from the strip or the panel onto a day of the grid to swap it in. +// An upright tablet lays the week out four days over three. export function PlanPage() { const layout = useBreakpoint() + const orientation = useOrientation() const settings = useHouseholdSettings() const [params, setParams] = useSearchParams() const chosen = params.get(WEEK_PARAM) @@ -699,7 +701,11 @@ export function PlanPage() { } return ( -
+
{content} {layout === 'tablet' && } diff --git a/web/src/settings/SettingsTabs.module.css b/web/src/settings/SettingsTabs.module.css index 180b236..16d6567 100644 --- a/web/src/settings/SettingsTabs.module.css +++ b/web/src/settings/SettingsTabs.module.css @@ -83,6 +83,12 @@ gap: 20px; } +/* An upright tablet stacks the columns: Notifications above the AI + assistant and Recipe search. */ +.portrait .columns { + grid-template-columns: minmax(0, 1fr); +} + .column { min-width: 0; display: flex; diff --git a/web/src/settings/SettingsTabs.tsx b/web/src/settings/SettingsTabs.tsx index 2aaf975..ab706be 100644 --- a/web/src/settings/SettingsTabs.tsx +++ b/web/src/settings/SettingsTabs.tsx @@ -1,4 +1,5 @@ import { useId, useRef, useState, type KeyboardEvent, type ReactNode } from 'react' +import { useBreakpoint, useOrientation } from '../shell/useBreakpoint.ts' import { Card } from '../ui/Card.tsx' import { AIAssistantCard } from './AISettingsPage.tsx' import { HouseholdSettingsSections } from './HouseholdSettingsPage.tsx' @@ -21,7 +22,10 @@ const step: Record = { ArrowRight: 1, ArrowLeft: -1 } // sub-tabs instead of the mobile list. Every tab stays mounted while hidden, // so a draft survives switching tabs, and one leave guard covers them all. // hidden hides the page but keeps it mounted, drafts and leave guard included. +// On an upright tablet the two columns stack into one. export function SettingsTabs({ hidden = false }: { hidden?: boolean }) { + const layout = useBreakpoint() + const orientation = useOrientation() const [tab, setTab] = useState('notifications-ai') const [householdDirty, setHouseholdDirty] = useState(false) const [aiDirty, setAIDirty] = useState(false) @@ -64,7 +68,10 @@ export function SettingsTabs({ hidden = false }: { hidden?: boolean }) { } return ( -