Address #121 review: keep sign-out reachable, dvh peek, exact height
Build image / build-and-push (push) Successful in 12s

- Fold the account menu into the editor's mobile strip. Hiding the global
  header removed the only sign-out on mobile in the editor; the strip now
  carries it, so the space win stays but sign-out is one tap away.
- EditorRail peek cap vh → dvh, matching the dvh-bounded editor column, so
  it can't overrun the visible viewport and push the mode bar off-screen.
- Mobile editor height 4rem → 3rem: with the header hidden, only <main>'s
  py-6 (3rem) is outside the editor, so 4rem left ~16px dead. Comment
  corrected.
- Trim two comments that duplicated nearby docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
2026-07-22 21:31:35 -04:00
co-authored by Claude Opus 4.8
parent ace696467b
commit 256fa4f29f
3 changed files with 23 additions and 16 deletions
+8 -7
View File
@@ -43,11 +43,10 @@ export function AppShell() {
// max-w-5xl reading measure the other pages use (#107). // max-w-5xl reading measure the other pages use (#107).
const canvasRoute = inEditor || inPublicGarden const canvasRoute = inEditor || inPublicGarden
const showBottomNav = !!user && !canvasRoute const showBottomNav = !!user && !canvasRoute
// On a phone the editor is a full-screen canvas the same reason the bottom // On a phone the editor is a full-screen canvas, so the global top bar is pure
// bar hides there. The global top bar (brand + account) is then pure chrome // chrome above the garden — hide it and let the editor's own strip carry the
// above the garden, so hide it too and let the editor's own strip carry a back // back link AND the account menu (so sign-out isn't lost). Editor only, not the
// affordance; desktop keeps the header. Only the editor, not the public view, // public view, which has no strip of its own to fall back on.
// which has no strip of its own to fall back on.
const hideHeaderOnMobile = inEditor const hideHeaderOnMobile = inEditor
const visibleSections = sections.filter((s) => !s.adminOnly || user?.isAdmin) 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 /** 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. */ * desktop the display name shows inline; on mobile it lives inside the popover.
function AccountMenu({ displayName }: { displayName: string }) { * 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 logout = useLogout()
const navigate = useNavigate() const navigate = useNavigate()
const [open, setOpen] = useState(false) 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 // reachable below. The canvas flexes to fill whatever's left. Desktop: a
// fixed-width column beside the canvas (the cap doesn't apply there). // 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', '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
// 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', '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 { cn } from '@/lib/cn'
import type { EditorGarden } from '@/editor/types' import type { EditorGarden } from '@/editor/types'
import { ShareGardenModal } from '@/components/gardens/ShareGardenModal' import { ShareGardenModal } from '@/components/gardens/ShareGardenModal'
import { AccountMenu } from '@/components/layout/AppShell'
import { useMe } from '@/lib/auth' import { useMe } from '@/lib/auth'
import { import {
toEditorObject, toEditorObject,
@@ -546,11 +547,11 @@ export function GardenEditorPage() {
// the canvas bottom + Fit button under the browser chrome (#85). // the canvas bottom + Fit button under the browser chrome (#85).
// //
// The subtracted band differs by breakpoint because the chrome does. On mobile // 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 // the global top bar is hidden here (AppShell), so the only thing outside the
// above us — 4rem reclaims the ~48px the header used to cost the canvas. On // editor is <main>'s py-6 — 3rem, top + bottom. On desktop the header is
// desktop the header is present, so keep the original 8rem. // present, so keep the original 8rem.
return ( return (
<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 {/* 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 + a slim top strip so the canvas — the point of the screen — isn't shoved
into a corner by a stack of controls (#99). */} into a corner by a stack of controls (#99). */}
@@ -627,6 +628,10 @@ export function GardenEditorPage() {
Share Share
</Button> </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> </div>
{seasonYear !== null && <SeasonBanner year={seasonYear} onExit={() => setSeasonYear(null)} />} {seasonYear !== null && <SeasonBanner year={seasonYear} onExit={() => setSeasonYear(null)} />}
{/* Focus toolbar is desktop-only; on mobile its plant tools move to the {/* Focus toolbar is desktop-only; on mobile its plant tools move to the
@@ -687,10 +692,8 @@ export function GardenEditorPage() {
tabs={railTabs} tabs={railTabs}
activeId={railTab} activeId={railTab}
onActivate={setRailTab} onActivate={setRailTab}
// The inspector is read alongside the canvas, so it stays a half-height // Panel modes want the room; the inspector stays a shorter peek (see the
// peek. Journal/History/Assistant are the task themselves — reading and // `tall` prop doc).
// typing want room — so they take a taller slice on mobile (the canvas
// above just needs to stay glanceable as the assistant edits it).
tall={railTab !== 'inspector'} tall={railTab !== 'inspector'}
onClose={() => { onClose={() => {
// Only the inspector is *about* the selection, so only closing it // Only the inspector is *about* the selection, so only closing it