Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1f0aed9
fix(viewport): implement mobile keyboard and touch target audit fixes
BigSimmo Jul 24, 2026
2e5943f
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
8788b51
fix(viewport): honour reduced motion and restore typecheck
cursoragent Jul 24, 2026
1528f69
Merge remote-tracking branch 'origin/main' into HEAD
cursoragent Jul 24, 2026
cf614ab
fix(viewport): resolve P1 defects with keyboard height detection and …
BigSimmo Jul 24, 2026
8fdfd20
Merge remote-tracking branch 'origin/main' into HEAD
cursoragent Jul 24, 2026
999835e
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
47ebd3d
Merge branch 'main' into implement-audit-viewport-fixes
BigSimmo Jul 24, 2026
c411604
docs: record Run PR behind-main sweep for #1140
BigSimmo Jul 24, 2026
47acd6a
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Jul 24, 2026
a541b75
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
BigSimmo Jul 24, 2026
fca3b67
docs: ledger re-sync sweep for PR #1140
BigSimmo Jul 24, 2026
3979115
Merge remote-tracking branch 'origin/main' into HEAD
BigSimmo Jul 24, 2026
a1a5995
Merge branch 'main' into implement-audit-viewport-fixes
cursoragent Jul 24, 2026
84e9309
fix(viewport): lift phone dock above keyboard
BigSimmo Jul 24, 2026
5f4cbc0
docs: record PR 1140 maintenance
BigSimmo Jul 24, 2026
6f53a18
Merge remote-tracking branch 'origin/implement-audit-viewport-fixes' …
BigSimmo Jul 24, 2026
793163d
Merge branch 'main' into implement-audit-viewport-fixes
cursoragent Jul 24, 2026
037946b
Merge branch 'main' into implement-audit-viewport-fixes
cursoragent Jul 24, 2026
283ad65
fix(ui): avoid double keyboard dock lift
BigSimmo Jul 24, 2026
d9efcab
docs: record PR 1140 keyboard guard
BigSimmo Jul 24, 2026
b1ff854
Merge remote-tracking branch 'origin/main' into HEAD
BigSimmo Jul 24, 2026
fa64fc7
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
43b598c
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
2f8f105
Merge cursor/pr-queue-hygiene-72ec for postcss audit + PR branch sync
cursoragent Jul 24, 2026
0f49684
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
c4a8917
Merge remote-tracking branch 'origin/main' into implement-audit-viewp…
cursoragent Jul 24, 2026
817785e
fix: resolve merge with main for PR #1140 babysit
cursoragent Jul 24, 2026
001e4d3
fix(test): align merge-artifact reserve pin with keyboard lift
cursoragent Jul 24, 2026
20eac47
fix(ui): gate keyboard lift on editable focus
cursoragent Jul 24, 2026
3084d04
merge origin/main into implement-audit-viewport-fixes
BigSimmo Jul 25, 2026
706188a
merge origin/main into implement-audit-viewport-fixes
BigSimmo Jul 25, 2026
80988a7
fix(viewport): preserve keyboard lift across orientation changes
cursoragent Jul 25, 2026
a30a0fb
fix(viewport): lift document-viewer composer above overlay keyboards
BigSimmo Jul 25, 2026
d3239b8
Merge branch 'main' into implement-audit-viewport-fixes
github-actions[bot] Jul 25, 2026
6eec292
fix(viewport): reset keyboard baselines without editable focus
BigSimmo Jul 25, 2026
761765a
fix(ui): harden Sources drawer focus restore after Escape
BigSimmo Jul 25, 2026
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
20 changes: 6 additions & 14 deletions docs/branch-review-ledger.md

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
--shadow-lux: inset 0 1px 0 rgb(255 255 255 / 64%), 0 12px 34px rgb(8 16 24 / 7%);
--shadow-inset: inset 0 1px 0 rgb(255 255 255 / 58%);
--shadow-card: var(--shadow-tight);
--shadow-focus: 0 0 0 3px color-mix(in srgb, var(--focus) 25%, transparent), var(--shadow-soft);
/* Active left-rail marker (nav/list "current" indicator). One token so the
accent rail is a consistent 2px everywhere instead of hand-inlined at 2px/3px. */
--shadow-rail-active: inset 2px 0 0 var(--clinical-accent);
Expand All @@ -283,6 +284,7 @@
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). */
--keyboard-height: 0px;
--phone-dock-hidden-pad: 0rem;
--phone-dock-differentials-compare-clearance: 12.5rem;
--phone-dock-differentials-compare-compact-clearance: 12.25rem;
Expand Down Expand Up @@ -428,6 +430,7 @@
--shadow-elevated: 0 2px 8px rgb(0 0 0 / 46%), 0 12px 30px rgb(0 0 0 / 48%), 0 28px 58px rgb(0 0 0 / 50%);
--shadow-lux: inset 0 1px 0 rgb(255 255 255 / 4%), 0 18px 44px rgb(0 0 0 / 64%);
--shadow-inset: inset 0 1px 0 rgb(255 255 255 / 4%);
--shadow-focus: 0 0 0 3px color-mix(in srgb, var(--focus) 25%, transparent), var(--shadow-soft);
color-scheme: dark;
}

Expand Down Expand Up @@ -1161,6 +1164,7 @@ summary::-webkit-details-marker {

.chat-composer-shell-delta:focus-within {
border-color: var(--clinical-accent);
box-shadow: var(--shadow-focus);
}

.chat-composer-input {
Expand Down Expand Up @@ -1813,7 +1817,7 @@ summary::-webkit-details-marker {
bottom: 0;
width: 100%;
max-width: none;
transform: none;
transform: translateY(calc(-1 * var(--keyboard-height, 0px)));
Comment thread
BigSimmo marked this conversation as resolved.
Comment thread
BigSimmo marked this conversation as resolved.
padding-inline: max(0.75rem, var(--safe-area-left)) max(0.75rem, var(--safe-area-right));
padding-top: 0.5rem;
padding-bottom: max(0.5rem, var(--safe-area-bottom));
Expand Down Expand Up @@ -1847,6 +1851,13 @@ summary::-webkit-details-marker {
transform: translateY(calc(100% + 0.75rem + env(safe-area-inset-bottom)));
}

/* Document viewer owns its floating composer (not the shared dock). Lift it
with the same --keyboard-height signal so overlay/visual keyboards do not
cover the focused input on document detail/source routes. */
.document-viewer-composer.floating-composer-edge:not([data-scroll-hidden="true"]) {
transform: translateY(calc(-1 * var(--keyboard-height, 0px)));
}

/* Document viewer bottom composer mirrors the dock's hide-on-scroll. Unlike
the full-bleed dock it floats with a bottom gap (.floating-composer-edge,
bottom: max(0.75rem, safe-area)), so the exit transform must also clear
Expand Down
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PwaLifecycle } from "@/components/pwa-lifecycle";
import { WebVitalsReporter } from "@/components/web-vitals-reporter";
import { resolveMetadataBase } from "@/lib/metadata-base";
import { APP_THEME_COLORS, THEME_BOOTSTRAP_SCRIPT, THEME_COOKIE_NAME } from "@/lib/theme";
import { MobileKeyboardProvider } from "@/components/use-mobile-keyboard";
import "./globals.css";

const geistSans = localFont({
Expand Down Expand Up @@ -60,6 +61,7 @@ export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
interactiveWidget: "resizes-content",
colorScheme: "light dark",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: APP_THEME_COLORS.light },
Expand Down Expand Up @@ -114,7 +116,9 @@ export default async function RootLayout({
<WebVitalsReporter />
<PwaLifecycle />
<AuthProvider>
<AccountDataProvider>{children}</AccountDataProvider>
<AccountDataProvider>
<MobileKeyboardProvider>{children}</MobileKeyboardProvider>
</AccountDataProvider>
</AuthProvider>
</body>
</html>
Expand Down
6 changes: 6 additions & 0 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,12 @@ export function ClinicalDashboard({
).find((element) => element.isConnected && element.getClientRects().length > 0);
const focusTarget = returnTarget?.isConnected ? returnTarget : fallbackTarget;
focusTarget?.focus({ preventScroll: true });
// Sheet autofocus teardown and composer focus listeners can win the first
// frame after Escape; retry once if the opener did not keep focus.
window.setTimeout(() => {
if (!focusTarget?.isConnected || document.activeElement === focusTarget) return;
focusTarget.focus({ preventScroll: true });
}, 50);
});
});
const handleOpenSourcePdfBrowser = useEventCallback(openSourcePdfBrowser);
Expand Down
4 changes: 3 additions & 1 deletion src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,9 @@ export function DocumentViewer({
// The visible fixed composer needs endpoint clearance. Once hidden,
// remove all artificial clearance so Safari can paint document content
// beneath its translucent toolbar instead of showing a blank band.
composerScrollHidden ? "max-sm:pb-0" : "max-sm:pb-[calc(9rem+var(--safe-area-bottom))]",
composerScrollHidden
? "max-sm:pb-0"
: "max-sm:pb-[calc(9rem+var(--safe-area-bottom)+var(--keyboard-height,0px))]",
)}
>
{downloadError ? (
Expand Down
26 changes: 25 additions & 1 deletion src/components/clinical-dashboard/master-search-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
type AppModeId,
} from "@/lib/app-modes";
import { appModeIcons } from "@/lib/app-mode-icons";
import { resolveScrollBehavior } from "@/lib/scroll-behavior";
import type { ClinicalDocument, ClinicalQueryMode } from "@/lib/types";
import { type SearchScopeFilters } from "@/lib/search-scope";
import { tagSearchText } from "@/lib/document-tags";
Expand Down Expand Up @@ -391,6 +392,7 @@ export function MasterSearchHeader({
const scopePopoverRef = useRef<HTMLDivElement | null>(null);
const actionMenuTriggerRef = useRef<HTMLButtonElement | null>(null);
const scopeFilterInputRef = useRef<HTMLInputElement | null>(null);
const touchStartY = useRef<number | null>(null);
const selectedDocumentIdSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]);
const documentById = useMemo(() => new Map(documents.map((document) => [document.id, document])), [documents]);
const selectedDocuments = useMemo(
Expand Down Expand Up @@ -1380,6 +1382,25 @@ export function MasterSearchHeader({
return (
<form
onSubmit={submit}
onTouchStart={(e) => {
touchStartY.current = e.touches[0].clientY;
}}
onTouchMove={(e) => {
if (touchStartY.current === null) return;
// Ignore swipes that originate inside a scrollable container
if (e.target instanceof Element && e.target.closest(".overflow-y-auto, .overflow-auto, .overflow-x-auto")) {
touchStartY.current = null;
return;
}
const currentY = e.touches[0].clientY;
const diff = currentY - touchStartY.current;
if (diff > 50) {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
touchStartY.current = null;
}
}}
data-footer-variant={usesPhoneFooterDock ? (usesCompactMobileBottomStyle ? "compact" : "default") : undefined}
data-footer-addon={usesPhoneFooterDock && mobileBottomSearchAddonSlotId ? "differentials-compare" : undefined}
data-command-open={
Expand Down Expand Up @@ -1519,6 +1540,9 @@ export function MasterSearchHeader({
ref={bindQueryInputRef}
data-testid="global-search-input"
autoFocus={queryInputAutoFocus}
onFocus={(e) => {
e.target.scrollIntoView({ block: "nearest", behavior: resolveScrollBehavior() });
}}
Comment thread
cursor[bot] marked this conversation as resolved.
value={query}
enterKeyHint="search"
inputMode="search"
Expand All @@ -1543,7 +1567,7 @@ export function MasterSearchHeader({
<button
type="button"
onClick={onClearQuery}
className="grid h-tap w-tap shrink-0 place-items-center rounded-full text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)] sm:h-12 sm:w-12"
className="grid min-h-tap min-w-tap shrink-0 place-items-center rounded-full text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)] sm:h-12 sm:w-12"
aria-label="Clear search question"
>
<X aria-hidden="true" className="h-4 w-4" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,20 @@ export const mobileComposerHiddenReserve = "0rem";
export const mobileComposerIdleReserve = "2rem";

/** Differentials Compare selected bar + compact search pill. */
export const mobileComposerDifferentialsCompareReserve = "calc(12.5rem + var(--safe-area-bottom))";
export const mobileComposerDifferentialsCompareReserve =
"calc(12.5rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))";

// Every phone dock is the compact single-row pill (mode homes and result views
// alike); only the answer dock with a follow-up chip row is taller. The answer
// values are derived from the dock constants so the pairs cannot silently
// diverge — master-search-header's compact styling assumes they stay equal.
const shellCompactSingleRowReserve = "calc(5.5rem + var(--safe-area-bottom))";
const dashboardCompactSingleRowReserve = "calc(5rem + var(--safe-area-bottom))";
const shellCompactSingleRowReserve = "calc(5.5rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))";
const dashboardCompactSingleRowReserve = "calc(5rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))";

export const mobileComposerVisibleReserve = {
shellAnswer: shellCompactSingleRowReserve,
shellDock: shellCompactSingleRowReserve,
dashboardAnswerWithFollowUps: "calc(7.5rem + var(--safe-area-bottom))",
dashboardAnswerWithFollowUps: "calc(7.5rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))",
dashboardAnswer: dashboardCompactSingleRowReserve,
dashboardDock: dashboardCompactSingleRowReserve,
differentialsCompare: mobileComposerDifferentialsCompareReserve,
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 @@ -71,7 +71,7 @@ export const proseMeasure = "max-w-[68ch]";
// chunk numbers, guideline versions, document IDs. Pairs with tabular figures.
export const codeText = "font-mono tabular-nums tracking-tight";
export const commandInput =
"min-h-12 w-full rounded-lg border border-[color:var(--border)]/70 bg-[color:var(--surface)] pl-12 pr-12 text-sm font-semibold text-[color:var(--text)] shadow-[var(--shadow-soft),var(--shadow-inset)] outline-none transition placeholder:text-[color:var(--text-soft)] focus:border-[color:var(--focus)] motion-safe:transition sm:text-base";
"min-h-12 w-full rounded-lg border border-[color:var(--border)]/70 bg-[color:var(--surface)] pl-12 pr-12 text-sm font-semibold text-[color:var(--text)] shadow-[var(--shadow-soft),var(--shadow-inset)] outline-none transition placeholder:text-[color:var(--text-soft)] focus:border-[color:var(--focus)] focus:shadow-[var(--shadow-focus)] motion-safe:transition sm:text-base";

export const chatAnswerText =
"max-w-[68ch] text-base-minus font-medium leading-[1.56] text-[color:var(--text-heading)] sm:text-base sm:leading-[1.62]";
Expand Down
7 changes: 1 addition & 6 deletions src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,7 @@ export function Sheet({
restoreTimers.timeout = window.setTimeout(() => {
restoreTimers.timeout = null;
if (typeof document === "undefined") return;
if (
typeof document === "undefined" ||
!restoreTarget.isConnected ||
document.activeElement === restoreTarget ||
document.activeElement !== document.body
) {
if (!restoreTarget.isConnected || document.activeElement === restoreTarget) {
return;
}
restoreTarget.focus({ preventScroll: true });
Expand Down
Loading
Loading