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
36 changes: 36 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,22 @@ html {
scroll-padding-top: calc(4rem + env(safe-area-inset-top));
}

/*
* Interface density scales the rem baseline so every rem-based size shifts
* together. "Comfortable" is the browser default (16px) and sets no attribute,
* so the untouched default experience is unchanged. The attribute is applied on
* <html> before first paint (layout.tsx inline script) and kept in sync by
* useAppPreferences. The unlayered 16px mobile input floor above still wins for
* form controls so iOS never zooms.
*/
html[data-density="compact"] {
font-size: 15px;
}

html[data-density="spacious"] {
font-size: 17px;
}

body {
min-height: 100%;
min-height: 100svh;
Expand Down Expand Up @@ -2204,6 +2220,26 @@ summary::-webkit-details-marker {
}
}

/*
* Explicit "Reduce motion" preference from the settings surface. Mirrors the
* prefers-reduced-motion suppression above so users can opt in regardless of
* their OS setting. The attribute is set on <html> before first paint by the
* inline script in layout.tsx and kept in sync by useAppPreferences.
*/
html[data-motion="reduced"] *,
html[data-motion="reduced"] *::before,
html[data-motion="reduced"] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}

html[data-motion="reduced"] .source-capsule-hit:hover .source-capsule-face,
html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .source-capsule-face {
transform: none !important;
}

@media (forced-colors: active) {
:root,
.dark {
Expand Down
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ export default async function RootLayout({
{/* Applies the resolved theme before first paint on every route (standalone
pages don't mount useTheme, and hydration-time toggling flashes light).
Mirrors resolveThemePreference in src/lib/theme.ts: stored choice wins,
otherwise the OS preference. Key must match use-theme.ts. */}
otherwise the OS preference. Key must match use-theme.ts. The second
block applies the density/motion preferences (keys must match
use-app-preferences.ts) so an opted-in choice never flashes in. */}
<script
nonce={nonce}
// Next.js strips the nonce from the client payload (so scripts can't
// read it), which reads as a hydration mismatch on this attribute.
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `(function(){try{var t=localStorage.getItem("clinical-kb-theme");var d=t==="dark"||(t!=="light"&&window.matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);if(t==="dark"||t==="light"){var c=d?"${APP_THEME_COLORS.dark}":"${APP_THEME_COLORS.light}";document.querySelectorAll('meta[name="theme-color"]').forEach(function(m){m.setAttribute("content",c);});}}catch(e){}})();`,
__html: `(function(){try{var t=localStorage.getItem("clinical-kb-theme");var d=t==="dark"||(t!=="light"&&window.matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);if(t==="dark"||t==="light"){var c=d?"${APP_THEME_COLORS.dark}":"${APP_THEME_COLORS.light}";document.querySelectorAll('meta[name="theme-color"]').forEach(function(m){m.setAttribute("content",c);});}}catch(e){}try{var p=JSON.parse(localStorage.getItem("clinical-kb-preferences")||"{}");if(p&&typeof p==="object"){if(p.density==="compact"||p.density==="spacious"){document.documentElement.setAttribute("data-density",p.density);}if(p.motion==="reduced"){document.documentElement.setAttribute("data-motion","reduced");}}}catch(e){}})();`,
}}
/>
<a
Expand Down
2 changes: 0 additions & 2 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4124,8 +4124,6 @@ export function ClinicalDashboard({
open={settingsOpen}
onClose={closeSettings}
identity={sidebarIdentity}
theme={theme}
onToggleTheme={toggleTheme}
onSignOut={auth.signOut}
onOpenGuide={openGuide}
/>
Expand Down
2 changes: 0 additions & 2 deletions src/components/clinical-dashboard/global-search-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,6 @@ function GlobalStandaloneSearchShellClient({
open={settingsOpen}
onClose={() => setSettingsOpen(false)}
identity={sidebarIdentity}
theme={theme}
onToggleTheme={toggleTheme}
onSignOut={auth.signOut}
onOpenGuide={openGuide}
/>
Expand Down
28 changes: 28 additions & 0 deletions src/components/clinical-dashboard/recent-query-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ export function loadRecentQueries(ownerId: string | null): string[] {
}
}

// Total recent queries retained across every owner-scoped session key. Used by
// the settings privacy controls to show how much is stored and to disable the
// "Clear" affordance when there is nothing to remove.
export function countRecentQueries(): number {
if (typeof window === "undefined") return 0;
try {
let total = 0;
for (let index = 0; index < window.sessionStorage.length; index += 1) {
const key = window.sessionStorage.key(index);
if (!key?.startsWith(`${recentQueryStorageKey}:`)) continue;
let stored: unknown;
try {
stored = JSON.parse(window.sessionStorage.getItem(key) ?? "[]");
} catch {
// Isolate a single corrupt entry: skip just this key rather than let one
// malformed value zero the whole total and wrongly disable "Clear".
continue;
}
if (Array.isArray(stored)) {
total += stored.filter((item) => typeof item === "string" && Boolean(item.trim())).length;
}
}
return total;
} catch {
return 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

// The pre-owner-scoping build persisted recent clinical queries under the
// unscoped key, which survived logout, account switching, and demo/
// authenticated transitions on shared workstations (2026-07-13 audit,
Expand Down
Loading