- WEB MODE GETS THE TOP BAR BACK. The corner ☰ with no header was rejected on a real phone. Both shell modes
now show the one top bar on a phone, the ☰ at its start and the language at its end; web mode's bar takes no
camera inset (the shell is not at the top of the screen in a website). The corner circle and the drawer-foot
language are gone. check:shell-header renders both modes (13 planted defects).
- THE FLOOR NEVER EXCEEDS A SIZED HOST. ProfileLoader's 100svh floor is for a host sized by its content (the
scroll-page leaf). Inside a host with a definite height (DX Life's map slot, overflow hidden) it made the map
taller than the host, which clipped the map's bottom and its legend footer, and tied the map's height to the
window's. ProfileLoader now MEASURES the host (the floor at 0 and at a huge value: a sized host keeps its
height) and drops the floor inside a sized one, re-measuring when the host resizes (a ResizeObserver on the
host, not a window resize).
- LegendDesktop caps itself at the map's height (100% - 80px), not the window's (100vh - 80px).
- On a phone the drawer started OPEN (navOpen = initialNavOpen) and mounted() closed it, so every load drew it
on screen and slid it away (measured: up to ~1.6 s, both shells, app and web mode). data() now reads the
same (max-width: 760px) query, so the first render is already the phone's closed state; mounted() still
tracks breakpoint crossings.
- In web mode the language at the drawer's foot sat right under the last row: margin-top:auto needs the nav's
flex column (.has-rail-end), which only the desktop rail-end set. The phone drawer's foot sets it too.
- check:shell-header asserts the flex column in web mode (14 planted defects).
ONE HEADER ON A WEBSITE. Inside a host site that has its own header, the shell's 44px phone bar stacked a
second white bar under it. VerticalTabsLayout gains shellMode (prop, or the host element's shell-mode
attribute): web | app | auto.
- app (a Capacitor or standalone app, or a DX Maps leaf page): the phone bar, unchanged.
- web: no bar. The rail opens from a 40px corner ☰ at the pane's bottom-start, which publishes its footprint
(--map-fab-clearance / --map-fab-beside) so the legend sheet, the pin hint and back-to-top step aside; the
language menu moves to the drawer's foot.
- auto: the host's shell-mode attribute, else app for Capacitor, display-mode standalone or a leaf
(window.MAP_OWNS_PAGE), else web.
Desktop is unchanged. check:shell-header renders both modes (13 planted defects); check:nav-reachable-mobile
accepts the corner ☰.
MAPS HOLD THEIR SIZE. ProfileLoader floored every map at min-height 100dvh, and the leaf pages' slot did the
same. dvh follows the mobile URL bar, so scrolling grew every map mid-scroll and moved its legend sheet. Both
now use 100svh (the viewport with the bar showing, which never changes on scroll), with 100vh where svh is
unsupported.
VerticalTabsLayout, header off: on a phone a 44px bar (a real grid row) replaces the floating corner
circle, with the rail toggle at the inline start and a #phone-end slot at the end; on a desktop a
#rail-end slot sits at the bottom of the rail (it receives `collapsed`). Neither renders with a header
on, so header-on consumers and desktop layouts without a filler are unchanged. The --map-fab-* footprint
is no longer published; its readers fall back to their plain insets.
ShellLanguageMenu fills either slot: the switcher's languages plus the current one, a native select
under a globe face. A pick dispatches one cancelable, composed, bubbling doxa-language-request on the
host element (library/utils/languageRequest.js). A host that cancels keeps the language; otherwise the
menu emits 'switch' and writes ?lang= only on a page the tree generated.
check:shell-header (gates:pre, Vite SSR) asserts where the bar and the rail slot render, the event's
shape and count, cancel and no-cancel, the ?lang= rule and the language list; its self-test (gates:post)
refuses 11 planted defects. check:nav-reachable now presses the bar's toggle.