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
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,8 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-29 | codex/document-reader-condensed-view | 7cefb24e99f9745a61843c7e48c4889f7324ec42 | pr-1380-main-merge-coderabbit-density | merged origin/main; resolved source-panels conflict (kept condensed details + tracking-eyebrow); density in-memory fallback when storage blocked; summary keys + search/plain compact tests; local vitest/lint/typecheck/format/playwright condensed pass; awaiting hosted CI | vitest document suites 20/20; lint; typecheck; format:check; playwright condensed 4/4; merge-tree clean |
| 2026-07-29 | claude/test-coverage-analysis-2vcd8a | 0922d7f56624ef84be8abcb2bbc89205027cf9a6 | PR #1383 babysit | BLOCKER CLEARED: merged origin/main; renumbered coverage follow-ups #098/#099 -> #106/#107 (main claimed #098-#105). Before: CONFLICTING/DIRTY, 4 behind; CI green on prior tip; 0 review threads; 0 Bugbot findings. After: mergeable expected; verify:cheap 424 files/4371 passed; test:coverage exit 0; format:changed + check:rag:fixtures pass. | verify:cheap PASS (424 files, 4371 passed \| 4 skipped); test:coverage PASS (no threshold errors); format:changed PASS; check:rag:fixtures PASS (36 golden); Bugbot: no findings; no provider-backed checks |
| 2026-07-29 | claude/test-coverage-analysis-2vcd8a | 6f476b5f741627cb622af57d1b4665e3989789ca | PR #1383 babysit | CLOSEOUT at tip after ledger bookkeeping commit. Merge conflict cleared; coverage follow-ups live as #106/#107; local gates green; awaiting hosted CI on tip. | same as prior tip 0922d7f5 plus ledger append only; no product code change |
| 2026-07-29 | codex/document-results-responsive-polish | 94bef3d193f449e6396b1bf4688180281d260ca7 | document results responsive UI polish | No P0-P3 findings. Current-main three-action cards preserved; responsive typography, equal phone geometry, warning hierarchy, and no-overflow behavior verified. | focused ESLint; typecheck; 30 focused unit tests; isolated production Playwright 1 passed; verify:cheap static/design/governance/lint passed then lock-blocked at repeated typecheck |
| 2026-07-29 | codex/document-results-responsive-polish | 0f02d086e0716ed072df5fe73a7962b6c02891d1 | document results responsive UI polish | P2 tap-target review finding fixed with 48px actions and regression coverage | focused production Playwright 1 passed; eslint passed; format passed; diff-check passed |
| 2026-07-29 | claude/test-coverage-analysis-2vcd8a | 5145dc990af47fba8b4c68f2b4537f21406535b6 | PR #1383 babysit | Hosted CI green on prior tip bebc6c02 (PR required / Unit coverage / Static / Safety / Migration replay / CircleCI all PASS). Synced one more clean main commit (ledger-only). MERGEABLE; 0 unresolved threads; 0 Bugbot findings; no code defects. | hosted: PR required PASS, Unit coverage PASS 4m41s, Static PR PASS, Safety PASS, Migration replay PASS, CircleCI verify PASS; local prior: verify:cheap + test:coverage PASS; Bugbot none |
| 2026-07-29 | agent/document-topbar-actions (PR #1381) | 9da8ccfb19ff81b876a9bfff4e6b5870641e44d8 | PR #1381 CI triage | merged via squash auto-merge after main sync; all required checks green; no product code fix; no Bugbot/review threads | hosted CI pr-required pass; Production UI pass; CircleCI pass; lint; typecheck; document-viewer-shell.dom; Bugbot none |
| 2026-07-29 | codex/chat-document-header-overlay-document-header-overlay-20260729 | 48ed6cc95f886837f4ddbb369fdbc611a0958f17 | document phone header overlay | No high-confidence findings; physical iPhone acceptance remains | verify:pr-local unit 4373 pass; build PASS; focused Playwright 2 pass; phone gate contended |
Comment thread
BigSimmo marked this conversation as resolved.
Expand Down
18 changes: 11 additions & 7 deletions src/components/clinical-dashboard/answer-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,29 +88,33 @@ export function ScopeAndGovernanceNotice({
scope?.matchedDocumentCount === 0;
if (!showScope && groupedWarnings.length === 0) return null;
return (
<div className="space-y-1.5 rounded-md border border-[color:var(--warning)]/20 border-l-2 border-l-[color:var(--warning)] bg-[color:var(--warning-soft)]/30 px-2.5 py-2 text-xs text-[color:var(--text)]">
<div className="space-y-1.5 rounded-lg border border-[color:var(--warning)]/15 border-l-2 border-l-[color:var(--warning)]/75 bg-[color:var(--warning-soft)]/20 px-3 py-2.5 text-xs text-[color:var(--text-muted)]">
{showScope && scope ? (
<p className="font-semibold leading-5">
<p className="font-medium leading-5 text-[color:var(--text)]">
Scope: {scope.summary}
{scope.queryMode && scope.queryMode !== "auto" ? ` · ${scope.queryMode.replaceAll("_", " ")}` : ""}
</p>
) : null}
{scope?.warnings?.length ? (
<ul className="grid gap-0.5 text-2xs font-medium text-[color:var(--warning)]">
<ul className="grid gap-1 text-2xs font-medium leading-4 text-[color:var(--warning)]">
{scope.warnings.slice(0, 3).map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
) : null}
{groupedWarnings.length ? (
<ul className="grid gap-0.5 text-2xs font-medium text-[color:var(--warning)]">
<ul className="grid gap-1 text-2xs font-normal leading-4 text-[color:var(--text-muted)]">
{groupedWarnings.map((warning) => (
<li key={warning.code}>
{warning.message}
{warning.titles.length ? (
<details className="mt-0.5 font-normal text-[color:var(--text-muted)]">
<summary className="cursor-pointer">Sources affected</summary>
<span className="mt-0.5 block">{warning.titles.slice(0, 5).join(", ")}</span>
<details className="mt-0.5 text-[color:var(--text-soft)]">
<summary className="cursor-pointer font-medium hover:text-[color:var(--text-muted)]">
Sources affected
</summary>
<span className="mt-1 block leading-4 text-[color:var(--text-muted)]">
{warning.titles.slice(0, 5).join(", ")}
</span>
</details>
) : null}
</li>
Expand Down
37 changes: 20 additions & 17 deletions src/components/clinical-dashboard/document-search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ function DocumentSearchResultsPanelImpl({
(unavailableMessage && !shouldShowHome);

return (
<div data-testid="document-search-workspace" className="w-full space-y-3">
<div data-testid="document-search-workspace" className="w-full space-y-2.5 sm:space-y-3">
{showIdentityHeader ? (
<SearchResultsHeaderBand
modeId={showRecordMatches ? recordMode : "documents"}
Expand Down Expand Up @@ -898,8 +898,8 @@ function DocumentSearchResultsPanelImpl({
{sortedMatches.length} result{sortedMatches.length === 1 ? "" : "s"} after filters
</div>
) : null}
<div className="grid gap-4">
<div className="min-w-0 space-y-3">
<div className="grid gap-3 sm:gap-4">
<div className="min-w-0 space-y-2.5 sm:space-y-3">
{sortedMatches.length === 0 ? (
<div className={cn(panelSubtle, "p-4 text-sm font-semibold text-[color:var(--text-muted)]")}>
No document matches include all selected filters.
Expand All @@ -920,44 +920,44 @@ function DocumentSearchResultsPanelImpl({
"relative overflow-visible p-0 shadow-[var(--shadow-tight)] transition hover:border-[color:var(--clinical-accent-border)] hover:shadow-[var(--shadow-hover)]",
)}
>
<div className="grid grid-cols-[auto_minmax(0,1fr)] gap-3 px-3 py-3 sm:px-4">
<div className="grid grid-cols-[auto_minmax(0,1fr)] gap-2.5 px-3 py-2.5 sm:gap-3 sm:px-4 sm:py-3">
<DocumentFileTile kind={fileKind} tone={documentTileTone(fileKind)} compact />
<div className="min-w-0">
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="min-w-0">
<p className="flex flex-wrap items-center gap-1.5 text-2xs font-semibold uppercase tracking-[0.06em] text-[color:var(--text-muted)]">
<p className="flex flex-wrap items-center gap-1.5 text-2xs font-medium uppercase tracking-[0.06em] text-[color:var(--text-soft)]">
<span>{documentKindLabel(document)}</span>
{index === 0 ? (
<>
<span
className="h-1 w-1 rounded-full bg-[color:var(--border-strong)]"
aria-hidden="true"
/>
<span className="text-[color:var(--clinical-accent)]">Best match</span>
<span className="font-semibold text-[color:var(--clinical-accent)]">Best match</span>
</>
) : null}
</p>
<a
href={openHref}
className="mt-0.5 inline-flex min-h-tap items-center rounded-md text-base font-semibold leading-6 text-[color:var(--text-heading)] transition hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] sm:min-h-7"
className="mt-0.5 inline-flex min-h-tap items-center rounded-md text-base-minus font-semibold leading-5 text-[color:var(--text-heading)] transition hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] sm:min-h-7 sm:text-base sm:leading-6"
>
<span className="line-clamp-2">{documentDisplayTitle(document)}</span>
</a>
</div>
</div>
<div className="mt-1.5 flex flex-wrap gap-1.5">
<div className="mt-1 flex flex-wrap gap-1.5 sm:mt-1.5">
<DocumentBadge
variant={relevanceVariant}
icon={Target}
className="min-h-7 rounded-lg px-2.5 text-2xs"
className="min-h-6 rounded-md px-2 text-2xs [font-weight:500] sm:min-h-7 sm:rounded-lg sm:px-2.5"
>
{relevanceDisplay.short}
<span className="sr-only">, {relevanceDisplay.detail}</span>
</DocumentBadge>
<DocumentBadge
variant="neutral"
icon={BookOpen}
className="min-h-7 rounded-lg px-2.5 text-2xs"
className="min-h-6 rounded-md px-2 text-2xs [font-weight:500] sm:min-h-7 sm:rounded-lg sm:px-2.5"
>
{metadataBadgeLabel(document)}
</DocumentBadge>
Expand All @@ -966,44 +966,47 @@ function DocumentSearchResultsPanelImpl({
icon={
document.tableCount > 0 ? ListChecks : document.imageCount > 0 ? FileImage : ExternalLink
}
className="min-h-7 rounded-lg px-2.5 text-2xs"
className="min-h-6 rounded-md px-2 text-2xs [font-weight:500] sm:min-h-7 sm:rounded-lg sm:px-2.5"
>
{cautionBadgeLabel(document)}
</DocumentBadge>
</div>
<p className={cn("mt-1.5 line-clamp-2 text-sm leading-6", textMuted)}>
<p className={cn("mt-1.5 line-clamp-2 text-sm-minus font-normal leading-5", textMuted)}>
<SafeBoldText text={summaryText} />
</p>
<DocumentTagCloud
labels={document.labels}
query={query}
limit={2}
compact
className="mt-2"
className="mt-1.5 sm:mt-2"
onTagClick={onTagSearch}
/>
</div>
</div>
<div className="flex flex-wrap items-center gap-1 border-t border-[color:var(--border)] px-2 py-1.5 sm:px-3">
<div
data-testid="document-result-actions"
className="grid grid-cols-3 items-stretch border-t border-[color:var(--border)] px-1 sm:flex sm:flex-wrap sm:items-center sm:gap-1 sm:px-3 sm:py-1.5"
>
<DocumentActionLink
href={openHref}
className="min-h-tap rounded-lg px-2.5 text-xs text-[color:var(--text)]"
className="min-h-12 min-w-0 flex-col rounded-md px-1 text-2xs text-[color:var(--text-muted)] [font-size:var(--text-2xs)] [font-weight:500] [gap:0.125rem] [&_svg]:size-icon-sm sm:w-auto sm:flex-row sm:rounded-lg sm:px-2.5 sm:text-xs sm:[font-size:var(--text-xs)] sm:[gap:0.375rem]"
aria-label={`Open ${document.title}`}
>
{contextualOpenLabel(document)}
</DocumentActionLink>
<DocumentActionButton
onClick={() => onScopeDocument(document.document_id)}
icon={Filter}
className="min-h-tap rounded-lg px-2.5 text-xs text-[color:var(--text)]"
className="min-h-12 min-w-0 flex-col rounded-md px-1 text-2xs text-[color:var(--text-muted)] [font-size:var(--text-2xs)] [font-weight:500] [gap:0.125rem] [&_svg]:size-icon-sm sm:w-auto sm:flex-row sm:rounded-lg sm:px-2.5 sm:text-xs sm:[font-size:var(--text-xs)] sm:[gap:0.375rem]"
aria-label={`Scope search to ${document.title}`}
>
Scope
</DocumentActionButton>
<DocumentActionButton
onClick={() => onAnswerFromDocument(document.document_id)}
icon={Sparkles}
className="ml-auto min-h-tap rounded-lg px-2.5 text-xs text-[color:var(--clinical-accent)] hover:bg-[color:var(--clinical-accent-soft)]"
className="min-h-12 min-w-0 flex-col rounded-md px-1 text-2xs text-[color:var(--clinical-accent)] [font-size:var(--text-2xs)] [font-weight:500] [gap:0.125rem] hover:bg-[color:var(--clinical-accent-soft)] [&_svg]:size-icon-sm sm:ml-auto sm:w-auto sm:flex-row sm:rounded-lg sm:px-2.5 sm:text-xs sm:[font-size:var(--text-xs)] sm:[gap:0.375rem]"
aria-label={`Answer from ${document.title}`}
>
Answer
Expand Down
48 changes: 48 additions & 0 deletions tests/ui-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3279,6 +3279,54 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(documentResults).toContainText("Best match");
await expect(documentResults).toContainText("1 table");

// Phone actions share the card width evenly, then become a natural toolbar
// from tablet upward. Keep the production result card inside every viewport
// used by the design workflow and prove the quieter action typography.
for (const width of [320, 390, 639, 768, 1440, 1920]) {
await page.setViewportSize({ width, height: 900 });
await expectNoPageHorizontalOverflow(page);
const actionGeometry = await documentResults.getByTestId("document-result-actions").evaluate((rail) => {
const railStyle = getComputedStyle(rail);
const widths = Array.from(rail.children).map((child) => child.getBoundingClientRect().width);
const firstActionStyle = getComputedStyle(rail.children[0]);
const card = rail.closest("article")?.getBoundingClientRect();
return {
display: railStyle.display,
widths,
actionFontSize: firstActionStyle.fontSize,
actionFontWeight: firstActionStyle.fontWeight,
actionDirection: firstActionStyle.flexDirection,
cardLeft: card?.left ?? 0,
cardRight: card?.right ?? 0,
viewportWidth: window.innerWidth,
};
});
expect(actionGeometry.cardLeft).toBeGreaterThanOrEqual(0);
expect(actionGeometry.cardRight).toBeLessThanOrEqual(actionGeometry.viewportWidth + 1);
if (width < 640) {
expect(actionGeometry.display).toBe("grid");
expect(Math.max(...actionGeometry.widths) - Math.min(...actionGeometry.widths)).toBeLessThanOrEqual(1);
expect(actionGeometry.actionFontSize).toBe("11px");
expect(actionGeometry.actionDirection).toBe("column");
for (const action of await documentResults.getByTestId("document-result-actions").locator(":scope > *").all()) {
await expectMinTouchTarget(action, 48);
}
} else {
expect(actionGeometry.display).toBe("flex");
expect(actionGeometry.actionFontSize).toBe("12px");
expect(actionGeometry.actionDirection).toBe("row");
}
expect(actionGeometry.actionFontWeight).toBe("500");
}
await page.setViewportSize({ width: 390, height: 820 });
const openResultLink = documentResults.getByRole("link", { name: /Open Synthetic lithium monitoring protocol/i });
await openResultLink.focus();
await expect(openResultLink).toBeFocused();
await page.emulateMedia({ forcedColors: "active" });
await expect(documentResults.getByTestId("document-result-actions")).toBeVisible();
await expectNoPageHorizontalOverflow(page);
await page.emulateMedia({ forcedColors: "none" });

if ((await mobileTypeFilter.locator('option[value="tables"]').count()) > 0) {
await mobileTypeFilter.selectOption("tables");
await expect(mobileTypeFilter).toHaveValue("tables");
Expand Down
Loading