Reclaim mobile chrome: drop the editor's top banner, un-cramp the assistant #121

Merged
steve merged 2 commits from feat/mobile-space-polish into main 2026-07-23 01:32:59 +00:00
3 changed files with 23 additions and 16 deletions
Showing only changes of commit 256fa4f29f - Show all commits
+8 -7
View File
@@ -43,11 +43,10 @@ export function AppShell() {
// max-w-5xl reading measure the other pages use (#107).
const canvasRoute = inEditor || inPublicGarden
const showBottomNav = !!user && !canvasRoute
// On a phone the editor is a full-screen canvas the same reason the bottom
// bar hides there. The global top bar (brand + account) is then pure chrome
// above the garden, so hide it too and let the editor's own strip carry a back
// affordance; desktop keeps the header. Only the editor, not the public view,
// which has no strip of its own to fall back on.
// On a phone the editor is a full-screen canvas, so the global top bar is pure
// chrome above the garden — hide it and let the editor's own strip carry the
// back link AND the account menu (so sign-out isn't lost). Editor only, not the
// public view, which has no strip of its own to fall back on.
const hideHeaderOnMobile = inEditor
Outdated
Review

🟠 Hiding the global header on mobile in the editor removes the only sign-out affordance (AccountMenu), weakening session control on shared devices

correctness, error-handling, maintainability, security · flagged by 2 models

  • Loss of the only sign-out affordance on mobile in the editor (web/src/components/layout/AppShell.tsx:51,57-61). The global header — which contains AccountMenu, the app's only "Sign out" control (AppShell.tsx:92/130-197) — is now hidden md:block whenever inEditor is true. The editor's replacement mobile strip (GardenEditorPage.tsx:608-630) adds only a 🌱 Link to="/gardens" and garden/season/share controls — no account or sign-out control. showBottomNav is also false on th…

🪰 Gadfly · advisory

🟠 **Hiding the global header on mobile in the editor removes the only sign-out affordance (AccountMenu), weakening session control on shared devices** _correctness, error-handling, maintainability, security · flagged by 2 models_ - **Loss of the only sign-out affordance on mobile in the editor** (`web/src/components/layout/AppShell.tsx:51,57-61`). The global header — which contains `AccountMenu`, the app's only "Sign out" control (`AppShell.tsx:92`/`130-197`) — is now `hidden md:block` whenever `inEditor` is true. The editor's replacement mobile strip (`GardenEditorPage.tsx:608-630`) adds only a 🌱 `Link to="/gardens"` and garden/season/share controls — no account or sign-out control. `showBottomNav` is also `false` on th… <sub>🪰 Gadfly · advisory</sub>
const visibleSections = sections.filter((s) => !s.adminOnly || user?.isAdmin)
@@ -126,8 +125,10 @@ export function AppShell() {
}
/** Account control: a compact button that toggles a small sign-out popover. On
* desktop the display name shows inline; on mobile it lives inside the popover. */
function AccountMenu({ displayName }: { displayName: string }) {
* desktop the display name shows inline; on mobile it lives inside the popover.
* Exported so the editor's mobile strip can carry it — the global header that
* normally hosts it is hidden there (see hideHeaderOnMobile). */
export function AccountMenu({ displayName }: { displayName: string }) {
const logout = useLogout()
const navigate = useNavigate()
const [open, setOpen] = useState(false)
+4 -1
View File
@@ -62,7 +62,10 @@ export function EditorRail({
// reachable below. The canvas flexes to fill whatever's left. Desktop: a
// fixed-width column beside the canvas (the cap doesn't apply there).
'flex min-h-0 shrink-0 flex-col rounded-t-xl border-t border-border bg-surface shadow-lg',
tall ? 'max-h-[78vh]' : 'max-h-[50vh]',
// dvh, not vh: the enclosing editor column is dvh-bounded, and on mobile
Outdated
Review

🟠 Mobile peek height cap uses static vh instead of dvh, risking overflow past the visible viewport (hiding the always-visible mode bar) when panel content is tall — worsened by raising the cap from 50vh to 78vh

correctness, maintainability · flagged by 2 models

  • web/src/editor/EditorRail.tsx:65 — The mobile peek's height cap uses vh (max-h-[50vh] / new max-h-[78vh]) rather than dvh, while the container it's nested inside (GardenEditorPage.tsx:553, h-[calc(100dvh-4rem)] ... md:h-[calc(100dvh-8rem)]) was deliberately switched to dvh for the exact reason documented in the adjacent comment at GardenEditorPage.tsx:544-546 (#85): mobile Safari/Chrome's 100vh is the largest viewport (address bar hidden), so with the bar showing, any…

🪰 Gadfly · advisory

🟠 **Mobile peek height cap uses static vh instead of dvh, risking overflow past the visible viewport (hiding the always-visible mode bar) when panel content is tall — worsened by raising the cap from 50vh to 78vh** _correctness, maintainability · flagged by 2 models_ - **`web/src/editor/EditorRail.tsx:65`** — The mobile peek's height cap uses `vh` (`max-h-[50vh]` / new `max-h-[78vh]`) rather than `dvh`, while the container it's nested inside (`GardenEditorPage.tsx:553`, `h-[calc(100dvh-4rem)] ... md:h-[calc(100dvh-8rem)]`) was deliberately switched to `dvh` for the exact reason documented in the adjacent comment at `GardenEditorPage.tsx:544-546` (#85): mobile Safari/Chrome's `100vh` is the *largest* viewport (address bar hidden), so with the bar showing, any… <sub>🪰 Gadfly · advisory</sub>
// Safari/Chrome vh is the *largest* viewport, so a vh cap could overrun the
// visible area and shove the mode bar off-screen (same #85 reasoning).
tall ? 'max-h-[78dvh]' : 'max-h-[50dvh]',
'md:static md:max-h-none md:w-80 md:rounded-xl md:border md:shadow-sm',
)}
>
+11 -8
View File
@@ -23,6 +23,7 @@ import { isCoarsePointer } from '@/editor/shared'
import { cn } from '@/lib/cn'
import type { EditorGarden } from '@/editor/types'
import { ShareGardenModal } from '@/components/gardens/ShareGardenModal'
import { AccountMenu } from '@/components/layout/AppShell'
import { useMe } from '@/lib/auth'
import {
toEditorObject,
@@ -546,11 +547,11 @@ export function GardenEditorPage() {
// the canvas bottom + Fit button under the browser chrome (#85).
//
// The subtracted band differs by breakpoint because the chrome does. On mobile
// the global top bar is hidden here (AppShell), so only <main>'s padding is
// above us — 4rem reclaims the ~48px the header used to cost the canvas. On
// desktop the header is present, so keep the original 8rem.
// the global top bar is hidden here (AppShell), so the only thing outside the
// editor is <main>'s py-6 — 3rem, top + bottom. On desktop the header is
// present, so keep the original 8rem.
return (
Outdated
Review

🟡 Mobile height subtracts 4rem but only 3rem of chrome (main py-6) exists with header hidden; leaves ~16px dead space and comment math is wrong

correctness · flagged by 2 models

  • web/src/pages/GardenEditorPage.tsx:553 — Mobile height is h-[calc(100dvh-4rem)], but with the global header hidden on mobile (AppShell.tsx header is hidden md:block), the only chrome outside the editor's flex column is <main>'s px-4 py-6 — i.e. 3rem of vertical padding (1.5rem top + 1.5rem bottom), not 4rem. Subtracting 4rem leaves ~16px of dead space the viewport would otherwise allow. This contradicts the comment at GardenEditorPage.tsx:548-551 ("only <main>'s padding is…

🪰 Gadfly · advisory

🟡 **Mobile height subtracts 4rem but only 3rem of chrome (main py-6) exists with header hidden; leaves ~16px dead space and comment math is wrong** _correctness · flagged by 2 models_ - `web/src/pages/GardenEditorPage.tsx:553` — Mobile height is `h-[calc(100dvh-4rem)]`, but with the global header hidden on mobile (`AppShell.tsx` header is `hidden md:block`), the only chrome outside the editor's flex column is `<main>`'s `px-4 py-6` — i.e. **3rem** of vertical padding (1.5rem top + 1.5rem bottom), not 4rem. Subtracting 4rem leaves ~16px of dead space the viewport would otherwise allow. This contradicts the comment at `GardenEditorPage.tsx:548-551` ("only `<main>`'s padding is… <sub>🪰 Gadfly · advisory</sub>
<div className="flex h-[calc(100dvh-4rem)] flex-col gap-3 md:h-[calc(100dvh-8rem)] md:flex-row">
<div className="flex h-[calc(100dvh-3rem)] flex-col gap-3 md:h-[calc(100dvh-8rem)] md:flex-row">
{/* Desktop-only control column. On mobile these move to the bottom mode bar
+ a slim top strip so the canvas — the point of the screen — isn't shoved
into a corner by a stack of controls (#99). */}
1
@@ -627,6 +628,10 @@ export function GardenEditorPage() {
Share
</Button>
)}
{/* The global header (and its account menu) is hidden on mobile in the
editor, so carry sign-out here — otherwise it's unreachable without
leaving the garden. */}
{me.data && <AccountMenu displayName={me.data.displayName} />}
</div>
{seasonYear !== null && <SeasonBanner year={seasonYear} onExit={() => setSeasonYear(null)} />}
{/* Focus toolbar is desktop-only; on mobile its plant tools move to the
1
@@ -687,10 +692,8 @@ export function GardenEditorPage() {
tabs={railTabs}
activeId={railTab}
onActivate={setRailTab}
// The inspector is read alongside the canvas, so it stays a half-height
// peek. Journal/History/Assistant are the task themselves — reading and
// typing want room — so they take a taller slice on mobile (the canvas
// above just needs to stay glanceable as the assistant edits it).
// Panel modes want the room; the inspector stays a shorter peek (see the
// `tall` prop doc).
tall={railTab !== 'inspector'}
onClose={() => {
// Only the inspector is *about* the selection, so only closing it