Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
57ed737
fix(ci): clear post-merge annotations
BigSimmo Jul 3, 2026
2d5e029
Add generated sitemap audit
BigSimmo Jul 3, 2026
2d16f83
feat(search): universal footer chips + mode-identity icons on mobile
BigSimmo Jul 3, 2026
18f9ba6
fix(search): scope popover reachable via + menu on desktop widths
BigSimmo Jul 3, 2026
ca34ea9
fix(ui): prevent mode-home search composer overlap flash on services/…
BigSimmo Jul 3, 2026
2630d5f
Refine database search and answer rendering flows
BigSimmo Jul 4, 2026
080243c
Add public anonymous access implementation plan
BigSimmo Jul 4, 2026
51e4e51
Merge origin/main into codex/fix-ci-annotations
Copilot Jul 4, 2026
e922119
chore: fix Prettier formatting in 4 files to pass CI format:check
Copilot Jul 4, 2026
bfb2b07
Fix jsx-a11y/label-has-associated-control lint errors in ClinicalDash…
Copilot Jul 4, 2026
6215b9a
fix(pr-254): address Codex review findings and prettier CI gate
BigSimmo Jul 4, 2026
32cf2ce
fix: unblock CI verify gate after main merge (exports, sitemap, test)
BigSimmo Jul 4, 2026
c991340
chore(test): recalibrate function-coverage regression floor to 43%
BigSimmo Jul 4, 2026
028293b
Fix ui-smoke selectors for updated launcher and favourites UI
Copilot Jul 4, 2026
dde6964
Update Playwright smoke tests for current UI semantics
Copilot Jul 4, 2026
2bd65f4
Changes before error encountered
Copilot Jul 4, 2026
1af550f
fix(pr-254): address remaining review findings
BigSimmo Jul 4, 2026
44e757f
feat(ui): compact phone bottom search on search/result views for max …
BigSimmo Jul 4, 2026
bbbfec2
feat(mobile): hide universal header on scroll to maximise phone scree…
BigSimmo Jul 4, 2026
71f7cd8
fix(ui): fit mode home pages to phone viewports without scrolling (#256)
BigSimmo Jul 4, 2026
8fb254c
Fix header new chat button to open answer mode
BigSimmo Jul 4, 2026
e5c82d4
Redesign Clinical Guide sidebar for responsive navigation and fix UI …
BigSimmo Jul 4, 2026
55c9f17
feat(answer): add follow-up suggestions, thread storage, and collapse…
BigSimmo Jul 4, 2026
db082af
feat(search): open bottom-docked command surface upward above the pill.
BigSimmo Jul 4, 2026
98d2def
fix(ui): fit mode home and detail pages to phone viewports without ov…
BigSimmo Jul 4, 2026
ceedbea
fix(answer): bootstrap thread persistence safely and share mode icons.
BigSimmo Jul 4, 2026
c9a3dea
fix(ui): use sheets for clinical notes and evidence on all breakpoints.
BigSimmo Jul 4, 2026
caa5f95
fix(ui): tighten scope and source-only disclosure styling.
BigSimmo Jul 4, 2026
b0ae58c
fix(ui): compact evidence gap cards in mobile sheets.
BigSimmo Jul 4, 2026
46c1afa
test(ui): lock tablet rail tools and active-route affordance.
BigSimmo Jul 4, 2026
ac7aa9e
fix: export maxStoredAnswerTurns and drop invalid compact header prop.
BigSimmo Jul 4, 2026
dc9b693
fix(answer): harden thread restore on reload and extract result surface.
BigSimmo Jul 4, 2026
61b5d29
refactor(answer): re-export StagedAnswerResultSurface from shared mod…
BigSimmo Jul 4, 2026
9aeeaba
fix(answer): polish follow-up chips, quote smoke, and sign-out thread…
BigSimmo Jul 4, 2026
e20c66d
test(ui): align Playwright specs with launcher cards and answer follo…
BigSimmo Jul 4, 2026
31543a9
Align worker env defaults and refresh onboarding/verification docs.
BigSimmo Jul 4, 2026
5f4eb1e
fix(answer): derive concise topics for first-turn suggestion chips
BigSimmo Jul 4, 2026
0b1b99b
Merge origin/main into codex/fix-ci-annotations
BigSimmo Jul 4, 2026
1c5d767
Add codebase index and Cursor semantic search configuration.
BigSimmo Jul 4, 2026
93e7841
refactor(answer): complete answer-review hygiene pass
BigSimmo Jul 4, 2026
c0fa9f7
Refine sources capsule into compact label + count badge control
cursoragent Jul 8, 2026
88ef154
Merge origin/main and resolve conflicts
Copilot Jul 8, 2026
3e978bb
Merge main and resolve PR conflicts
Copilot Jul 8, 2026
da21535
chore: fix Prettier formatting to pass CI format:check
cursoragent Jul 9, 2026
f890f32
fix(ui): keep sources capsule at min-h-tap for WCAG touch targets
cursoragent Jul 9, 2026
01765a1
chore: fix Prettier formatting in ui-primitives recipes doc
cursoragent Jul 9, 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
14 changes: 6 additions & 8 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1956,26 +1956,24 @@ summary::-webkit-details-marker {

/* Premium hover transitions for source capsules */
.source-capsule-hover {
box-shadow: var(--glow-soft), var(--shadow-inset);
transition:
transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 180ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 150ms ease,
background-color 150ms ease !important;
box-shadow 150ms ease,
transform 150ms ease !important;
}

.source-capsule-hover:hover {
transform: translateY(-1px) scale(1.01);
box-shadow: var(--shadow-tight), var(--shadow-inset);
transform: translateY(-1px) !important;
box-shadow: var(--shadow-tight) !important;
}

.source-capsule-hover[aria-expanded="true"] {
border-color: var(--clinical-accent);
box-shadow: var(--glow-soft), var(--shadow-inset);
box-shadow: var(--shadow-tight), var(--shadow-inset);
}

.source-capsule-hover[aria-expanded="true"]:hover {
transform: translateY(-1px) scale(1.01);
transform: translateY(-1px) !important;
box-shadow: var(--shadow-tight), var(--shadow-inset);
}

Expand Down
118 changes: 57 additions & 61 deletions src/components/clinical-dashboard/answer-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ChevronDown,
Copy,
ExternalLink,
Layers,
Loader2,
ShieldCheck,
Sparkles,
Expand All @@ -23,12 +24,12 @@ import {
chatMicroAction,
cn,
sourceCapsule,
sourceCapsuleCountBadge,
statusDotMuted,
statusDotReady,
statusDotReview,
subtleStatusPill,
textMuted,
toneWarningQuiet,
} from "@/components/ui-primitives";
import { sourceResultHref } from "@/components/clinical-dashboard/source-actions";
import {
Expand Down Expand Up @@ -257,19 +258,19 @@ function primaryAnswerDisplayText(value: string) {
.replace(/[;,:-]\s*$/, "")}...`;
}

function sourceCapsuleText({
function sourceCapsuleDisplay({
sourceCount,
weakEvidence,
grounded,
}: {
sourceCount: number;
weakEvidence: boolean;
grounded: boolean;
}) {
if (sourceCount <= 0) return "No direct source found";
if (!grounded) return "Review nearby sources";
if (weakEvidence) return "Review sources";
return `${sourceCount} source${sourceCount === 1 ? "" : "s"}`;
}): { label: string; showCountBadge: boolean } {
if (sourceCount <= 0) return { label: "No direct source found", showCountBadge: false };
if (!grounded) return { label: "Review nearby sources", showCountBadge: false };
if (weakEvidence) return { label: "Review sources", showCountBadge: false };
return { label: "Sources", showCountBadge: true };
}

export function sourceStatusDotClass(metadata: ReturnType<typeof normalizeSourceMetadata> | null | undefined) {
Expand Down Expand Up @@ -572,7 +573,7 @@ export function NaturalLanguageAnswer({
}, []);
const cleaned = primaryAnswerDisplayText(text);
if (!cleaned) return null;
const capsuleText = sourceCapsuleText({ sourceCount, weakEvidence, grounded });
const capsuleDisplay = sourceCapsuleDisplay({ sourceCount, weakEvidence, grounded });
const previewSources = capsulePreviewSources(bestSource, sources, sourceLinks);
const quoteText = sourceLinks.find((source) => source.snippet)?.snippet || bestSource?.quote || bestSource?.snippet;
const canOpenSourcePreview = previewSources.length > 0;
Expand All @@ -587,31 +588,23 @@ export function NaturalLanguageAnswer({
setCopiedSourceQuote(false);
}
}
const cautionCapsule = weakEvidence || !grounded;
const sourceCapsuleButton = (
<button
type="button"
ref={sourceCapsuleRef}
className={cn(sourceCapsule, "w-fit", cautionCapsule && toneWarningQuiet)}
className={cn(sourceCapsule, "w-fit")}
aria-label="Open answer sources"
aria-expanded={sourcePreviewOpen}
onClick={() => {
if (canOpenSourcePreview) setSourcePreviewOpen((current) => !current);
}}
>
{sourceCount > 0 ? (
<>
<span className="leading-none sm:hidden">
{sourceCount} source{sourceCount === 1 ? "" : "s"}
</span>
<span className="hidden leading-none sm:inline">{capsuleText}</span>
</>
) : (
<span className="leading-none">{capsuleText}</span>
)}
<Layers className="h-3.5 w-3.5 shrink-0" aria-hidden />
<span className="min-w-0 truncate">{capsuleDisplay.label}</span>
{capsuleDisplay.showCountBadge ? <span className={sourceCapsuleCountBadge}>{sourceCount}</span> : null}
{canOpenSourcePreview ? (
<ChevronDown
className={cn("h-4 w-4 shrink-0 transition-transform", sourcePreviewOpen && "rotate-180")}
className={cn("h-3.5 w-3.5 shrink-0 transition-transform", sourcePreviewOpen && "rotate-180")}
strokeWidth={2.25}
aria-hidden
/>
Expand All @@ -632,53 +625,56 @@ export function NaturalLanguageAnswer({
>
<ShieldCheck className="h-[18px] w-[18px]" />
</span>
<div className="min-w-0 space-y-2.5">
<div className="min-w-0 space-y-1.5">
<p className={chatAnswerText}>
<span data-testid="plain-answer-prose">
<SafeBoldText text={cleaned} />
</span>
</p>
{sourceOnly ? (
<section
data-testid="source-only-disclosure"
role="note"
className={cn(
"w-fit max-w-full overflow-hidden rounded-md border border-[color:var(--warning)]/20 border-l-2 border-l-[color:var(--warning)] bg-[color:var(--warning-soft)]/30 text-xs",
textMuted,
)}
>
<button
type="button"
onClick={() => setSourceOnlyNoticeOpen((current) => !current)}
className="inline-flex min-h-7 w-full max-w-[68ch] items-center gap-1.5 px-2 py-1 text-left transition hover:bg-[color:var(--warning-soft)]/55 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]"
aria-expanded={sourceOnlyNoticeOpen}
aria-controls="source-only-disclosure-detail"
<div className="space-y-1">
{sourceOnly ? (
<section
data-testid="source-only-disclosure"
role="note"
className={cn(
"w-fit max-w-full overflow-hidden rounded-md border border-[color:var(--warning)]/20 border-l-2 border-l-[color:var(--warning)] bg-[color:var(--warning-soft)]/30 text-xs",
textMuted,
)}
>
<AlertCircle className="h-3.5 w-3.5 shrink-0 text-[color:var(--warning)]" aria-hidden />
<span className="min-w-0 truncate font-semibold text-[color:var(--text-heading)]">Source-only</span>
<span className="shrink-0 text-2xs text-[color:var(--text-muted)]">· verify passages</span>
<ChevronDown
className={cn(
"ml-auto h-3.5 w-3.5 shrink-0 text-[color:var(--text-muted)] transition-transform",
sourceOnlyNoticeOpen && "rotate-180",
)}
aria-hidden
/>
</button>
{sourceOnlyNoticeOpen ? (
<div
id="source-only-disclosure-detail"
className="border-t border-[color:var(--warning)]/15 px-2.5 py-1.5 text-2xs leading-4 text-[color:var(--text-muted)] motion-safe:animate-fade-up"
<button
type="button"
onClick={() => setSourceOnlyNoticeOpen((current) => !current)}
className="inline-flex min-h-7 w-full max-w-[68ch] items-center gap-1.5 px-2 py-1 text-left transition hover:bg-[color:var(--warning-soft)]/55 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]"
aria-expanded={sourceOnlyNoticeOpen}
aria-controls="source-only-disclosure-detail"
>
<p>
This answer was assembled from your documents without the AI model, so it may be less complete. Verify
dose, threshold, route, timing, monitoring, and risk details against the cited passages below.
</p>
</div>
) : null}
</section>
) : null}
{sourceCapsuleButton}
<AlertCircle className="h-3.5 w-3.5 shrink-0 text-[color:var(--warning)]" aria-hidden />
<span className="min-w-0 truncate font-semibold text-[color:var(--text-heading)]">Source-only</span>
<span className="shrink-0 text-2xs text-[color:var(--text-muted)]">· verify passages</span>
<ChevronDown
className={cn(
"ml-auto h-3.5 w-3.5 shrink-0 text-[color:var(--text-muted)] transition-transform",
sourceOnlyNoticeOpen && "rotate-180",
)}
aria-hidden
/>
</button>
{sourceOnlyNoticeOpen ? (
<div
id="source-only-disclosure-detail"
className="border-t border-[color:var(--warning)]/15 px-2.5 py-1.5 text-2xs leading-4 text-[color:var(--text-muted)] motion-safe:animate-fade-up"
>
<p>
This answer was assembled from your documents without the AI model, so it may be less complete.
Verify dose, threshold, route, timing, monitoring, and risk details against the cited passages
below.
</p>
</div>
) : null}
</section>
) : null}
{sourceCapsuleButton}
</div>
{canOpenSourcePreview && !usePreviewSheet ? (
<SourcePreviewPopover
open={sourcePreviewOpen}
Expand Down
4 changes: 3 additions & 1 deletion src/components/ui-primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ export const chatActionRow =
export const chatMicroAction =
"inline-flex min-h-tap min-w-tap items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold text-[color:var(--text-muted)] transition hover:bg-[color:var(--clinical-accent-soft)] hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] disabled:cursor-not-allowed disabled:opacity-50";
export const sourceCapsule =
"source-capsule-hover focus-ring-premium inline-flex min-h-tap min-w-tap items-center gap-2 rounded-full border border-[color:var(--clinical-accent-border)]/90 bg-[color-mix(in_srgb,var(--clinical-accent-soft)_78%,var(--surface-lux))] px-3.5 py-2 text-xs font-semibold tabular-nums tracking-tight leading-none text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--clinical-accent)]";
"source-capsule-hover focus-ring-premium inline-flex min-h-tap items-center gap-1 rounded-md border border-[color:var(--border)] bg-[color-mix(in_srgb,var(--clinical-accent-soft)_55%,var(--surface))] px-2 text-[11px] font-medium text-[color:var(--clinical-accent)] transition hover:border-[color:var(--clinical-accent-border)] sm:px-2.5";
export const sourceCapsuleCountBadge =
"nums inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-[color:var(--surface-raised)] px-1.5 text-2xs font-semibold text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]";
export const evidenceRow =
"flex min-h-12 w-full items-center justify-between gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-left shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]";
export const clinicalNotesRow =
Expand Down
Loading