Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@
the layered .edge-glass-header base and the unlayered max-width:639px
guard so the values cannot drift apart. lg overrides to 1.5rem. */
--header-edge-pad: 1rem;
/* Borderless app-shell/header height (the min-h-14 bar). Page shells fill the
viewport below it via calc(100dvh - var(--shell-header-h)); one token so the
header height and those page-fill floors cannot silently drift apart. */
--shell-header-h: 4rem;
/* Keep in sync with mobile-composer-reserve.ts (phone dock clearance). */
--phone-dock-hidden-pad: 0.75rem;
--phone-dock-differentials-compare-clearance: 12.5rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ export function FavouritesCommandLibraryPage({ query = "", demoMode }: { query?:
return (
<main
data-testid="favourites-hub"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:pb-32 md:pb-0"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:pb-32 md:pb-0"
>
<span data-testid="favourites-command-library" className="sr-only">
Favourites command library
Expand Down Expand Up @@ -1128,14 +1128,14 @@ export function FavouritesCommandLibraryPage({ query = "", demoMode }: { query?:
return (
<main
data-testid="favourites-hub"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:pb-32 md:pb-0"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:pb-32 md:pb-0"
>
<span data-testid="favourites-command-library" className="sr-only">
Favourites command library
</span>
<div
className={cn(
"grid min-h-0 min-w-0 overflow-x-clip sm:min-h-[calc(100dvh-4rem)]",
"grid min-h-0 min-w-0 overflow-x-clip sm:min-h-[calc(100dvh-var(--shell-header-h))]",
navCollapsed ? "lg:grid-cols-[5.25rem_minmax(0,1fr)]" : "lg:grid-cols-[17.5rem_minmax(0,1fr)]",
selectedItem &&
(navCollapsed
Expand Down
4 changes: 2 additions & 2 deletions src/components/clinical-dashboard/global-search-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ function GlobalStandaloneSearchShellClient({
// Phone: keep a block formatting scrollport (not a column flex). A
// flex-1 child overflowed past a sibling spacer without extending
// scrollHeight, which parked long pages under the visible dock.
"min-w-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)] max-sm:min-h-0 max-sm:flex-1 max-sm:overflow-x-hidden max-sm:overflow-y-auto max-sm:overscroll-contain max-sm:[-webkit-overflow-scrolling:touch] sm:min-h-[calc(100dvh-4rem)] sm:overflow-x-clip",
"min-w-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)] max-sm:min-h-0 max-sm:flex-1 max-sm:overflow-x-hidden max-sm:overflow-y-auto max-sm:overscroll-contain max-sm:[-webkit-overflow-scrolling:touch] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:overflow-x-clip",
// sm+: static desktop clearance; use var(--safe-area-bottom) so tests
// can simulate insets without depending on env() in Chromium.
!reservesFloatingComposer
Expand All @@ -773,7 +773,7 @@ function GlobalStandaloneSearchShellClient({
*/}
<div data-testid="mobile-composer-reserve-pad" className="max-sm:pb-[var(--mobile-composer-reserve)]">
<ClientHydrationBoundary
fallback={<div className="min-h-[calc(100dvh-4rem)] overflow-x-hidden" aria-hidden />}
fallback={<div className="min-h-[calc(100dvh-var(--shell-header-h))] overflow-x-hidden" aria-hidden />}
>
<SearchCommandProvider value={searchCommandContextValue}>{children}</SearchCommandProvider>
</ClientHydrationBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,10 @@ export function MedicationRecordPage({
const governance = data?.governance ?? (error ? undefined : fallbackGovernance);

return (
<main className="min-h-[calc(100dvh-4rem)] text-[color:var(--text)]" data-testid={`medication-page-${slug}`}>
<main
className="min-h-[calc(100dvh-var(--shell-header-h))] text-[color:var(--text)]"
data-testid={`medication-page-${slug}`}
>
<div className="mx-auto max-w-7xl px-3 pt-3 sm:px-6 lg:px-8">
<Link
href={`/?mode=prescribing&q=${encodeURIComponent(slug)}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ export function DifferentialPresentationWorkflowPage({
return (
<main
data-testid="differential-presentation-page"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] px-3 pb-[calc(6.25rem+env(safe-area-inset-bottom))] pt-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-5 md:pb-8 xl:px-7 xl:pt-6"
className="min-h-0 overflow-x-clip bg-[color:var(--background)] px-3 pb-[calc(6.25rem+env(safe-area-inset-bottom))] pt-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-5 md:pb-8 xl:px-7 xl:pt-6"
>
<div className="mx-auto grid w-full max-w-[94rem] gap-5 xl:grid-cols-[minmax(0,1fr)_23.5rem]">
<div className="min-w-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const streamCopy: Record<
export function DifferentialStreamPage({ stream, query = "" }: DifferentialStreamPageProps) {
const copy = streamCopy[stream];
return (
<main className="min-h-[calc(100dvh-4rem)] bg-[color:var(--background)] px-4 py-10 text-[color:var(--text)] sm:px-6 lg:px-8">
<main className="min-h-[calc(100dvh-var(--shell-header-h))] bg-[color:var(--background)] px-4 py-10 text-[color:var(--text)] sm:px-6 lg:px-8">
<div className="mx-auto grid w-full max-w-6xl gap-6">
<section className="rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface-lux)] p-4 shadow-[var(--shadow-soft)] sm:p-6">
<p className="text-xs font-bold uppercase tracking-[0.08em] text-[color:var(--clinical-accent)]">
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/form-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ export function FormDetailPage({ form }: { form: FormRecord }) {
return (
<main
data-testid="form-detail-page"
className="max-sm:min-h-0 bg-[color:var(--background)] px-3 pb-4 pt-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-5 sm:pb-10 sm:pt-6 lg:px-8"
className="max-sm:min-h-0 bg-[color:var(--background)] px-3 pb-4 pt-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-5 sm:pb-10 sm:pt-6 lg:px-8"
>
<div className={pageContainer}>
{notice ? (
Expand Down
2 changes: 1 addition & 1 deletion src/components/formulation/formulation-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function FormulationPageShell({ children, className }: { children: ReactN
className={cn(
// Phone shell owns dock clearance; avoid 100dvh min-height overflowing the
// inset scrollport the way service/form detail pages used to.
"max-sm:min-h-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-5 sm:py-6 sm:pb-10 lg:px-7",
"max-sm:min-h-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-5 sm:py-6 sm:pb-10 lg:px-7",
className,
)}
>
Expand Down
8 changes: 6 additions & 2 deletions src/components/mode-home-page-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ModeHomePageSkeleton() {

export function ModeHomeRouteLoading() {
return (
<div className="grid min-h-[calc(100dvh-13.5rem-env(safe-area-inset-top)-env(safe-area-inset-bottom))] items-center justify-items-center bg-[color:var(--background)] sm:min-h-[calc(100dvh-4rem)]">
<div className="grid min-h-[calc(100dvh-13.5rem-env(safe-area-inset-top)-env(safe-area-inset-bottom))] items-center justify-items-center bg-[color:var(--background)] sm:min-h-[calc(100dvh-var(--shell-header-h))]">
<ModeHomePageSkeleton />
</div>
);
Expand All @@ -59,7 +59,11 @@ export function DocumentSearchPageSkeleton() {

export function DocumentViewerPageSkeleton() {
return (
<div className="flex h-[calc(100dvh-4rem)] flex-col gap-4 px-4 py-4" role="status" aria-label="Loading document">
<div
className="flex h-[calc(100dvh-var(--shell-header-h))] flex-col gap-4 px-4 py-4"
role="status"
aria-label="Loading document"
>
<SkeletonBlock className="h-10 w-full max-w-lg" />
<SkeletonBlock className="min-h-0 flex-1 rounded-lg" />
<span className="sr-only">Loading document</span>
Expand Down
2 changes: 1 addition & 1 deletion src/components/mode-home-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export function ModeHomeMain({
<main
data-testid={testId}
className={cn(
"flex min-h-0 w-full flex-1 flex-col items-center bg-[color:var(--background)] px-0 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-6 sm:pb-[clamp(1.75rem,5vh,3.25rem)] lg:px-8",
"flex min-h-0 w-full flex-1 flex-col items-center bg-[color:var(--background)] px-0 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-6 sm:pb-[clamp(1.75rem,5vh,3.25rem)] lg:px-8",
withoutJustifyUtilities(className),
MODE_HOME_MAIN_ALIGN_CLASS[contentAlign],
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/services/service-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ export function ServiceDetailPage({ service }: { service: ServiceRecord }) {
// Phone shell already owns dock clearance via --mobile-composer-reserve.
// A 100dvh min-height here overflows the inset scrollport and parks the
// page footer under the visible dock even when the shell pad is correct.
className="max-sm:min-h-0 max-sm:shrink-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-5 sm:py-6 sm:pb-10 lg:px-8"
className="max-sm:min-h-0 max-sm:shrink-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-5 sm:py-6 sm:pb-10 lg:px-8"
>
<div className={pageContainer}>
{notice ? (
Expand Down
2 changes: 1 addition & 1 deletion src/components/specifiers/specifier-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SpecifierPageShell({ children, className }: { children: ReactNod
className={cn(
// Phone shell owns dock clearance; avoid 100dvh min-height overflowing the
// inset scrollport the way service/form detail pages used to.
"max-sm:min-h-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-4rem)] sm:px-5 sm:py-6 sm:pb-10 lg:px-7",
"max-sm:min-h-0 bg-[color:var(--background)] px-3 py-4 pb-4 text-[color:var(--text)] sm:min-h-[calc(100dvh-var(--shell-header-h))] sm:px-5 sm:py-6 sm:pb-10 lg:px-7",
className,
)}
>
Expand Down
4 changes: 2 additions & 2 deletions src/components/therapy-compass/therapy-compass.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.tc-root {
min-height: calc(100dvh - 4rem);
min-height: calc(100dvh - var(--shell-header-h));
background: var(--surface-chrome);
color: var(--text);
}
Expand Down Expand Up @@ -1293,7 +1293,7 @@
padding: 32px 40px 40px;
}
.tc-root .tc-workspace-006 {
min-height: calc(100dvh - 4rem);
min-height: calc(100dvh - var(--shell-header-h));
background: var(--surface-chrome);
color: var(--text);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui-primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const searchPageCanvas = "bg-[color:var(--background)] text-[color:var(--
// --mobile-composer-reserve so it can collapse when the dock hides. Do not bake
// a second dock-sized safe-area pad into page shells.
export const searchPageShell =
"min-h-[calc(100dvh-4rem)] overflow-x-hidden px-3 py-3 pb-4 sm:px-5 sm:py-5 sm:pb-8 lg:px-6";
"min-h-[calc(100dvh-var(--shell-header-h))] overflow-x-hidden px-3 py-3 pb-4 sm:px-5 sm:py-5 sm:pb-8 lg:px-6";
export const searchPageContainer = "mx-auto w-full max-w-[1500px]";
// Canonical content-page width. Detail pages (service / form / differential),
// medication record + prescribing workspace, and the forms results view converge
Expand Down
8 changes: 5 additions & 3 deletions tests/mobile-interaction-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ describe("mobile interaction regressions", () => {
const differentialsHomeSource = source("src/components/clinical-dashboard/differentials-home.tsx");

expect(presentationSource).toMatch(
/data-testid="differential-presentation-page"\s+className="[^"]*min-h-0[^"]*overflow-x-clip[^"]*sm:min-h-\[calc\(100dvh-4rem\)\]/,
/data-testid="differential-presentation-page"\s+className="[^"]*min-h-0[^"]*overflow-x-clip[^"]*sm:min-h-\[calc\(100dvh-var\(--shell-header-h\)\)\]/,
);
expect(favouritesSource).toMatch(
/data-testid="favourites-hub"\s+className="[^"]*min-h-0[^"]*overflow-x-clip[^"]*sm:min-h-\[calc\(100dvh-4rem\)\]/,
/data-testid="favourites-hub"\s+className="[^"]*min-h-0[^"]*overflow-x-clip[^"]*sm:min-h-\[calc\(100dvh-var\(--shell-header-h\)\)\]/,
);
expect(favouritesSource).toContain(
'"grid min-h-0 min-w-0 overflow-x-clip sm:min-h-[calc(100dvh-var(--shell-header-h))]"',
);
expect(favouritesSource).toContain('"grid min-h-0 min-w-0 overflow-x-clip sm:min-h-[calc(100dvh-4rem)]"');
// overflow-x-hidden would force overflow-y:auto and nest a scrollport under #main-content.
expect(differentialsHomeSource).toMatch(
/data-testid="differentials-search-results"[\s\S]*?className="[^"]*overflow-x-clip[^"]*"/,
Expand Down
Loading