Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
83d310f
Compare sheet: pin Done and Reset to the bottom of the phone sheet
claude Oct 13, 2026
6109d07
Clinical lists: colour only where a row differs (audit item 8)
claude Oct 13, 2026
a3eca89
Therapy record: move the body onto one shared record layout
claude Oct 13, 2026
ccd8bc1
DSM record: move the body onto the shared record layout
claude Oct 13, 2026
e7e0427
Clinical lists: phone A to Z strip and tidier Dictionary rows (audit …
claude Oct 13, 2026
a705563
Service record: hide raw catalogue tags and drop repeated phone numbers
claude Oct 13, 2026
e029e71
Therapy: show cautions in full and lift Safety & cautions under the s…
claude Oct 13, 2026
6a29250
Clinical lists: compact rows on Formulation, Therapy Recommend and Ca…
claude Oct 13, 2026
451df04
Differentials record: move the body onto the shared record layout
claude Oct 13, 2026
a126014
Psychiatry, Medicines and Tools: less overlap below the band (audit i…
claude Oct 13, 2026
d70535d
Documents: one calm message when search cannot run
claude Oct 13, 2026
f0ccb36
Specifiers record: shared record layout and a Clinical blue main button
claude Oct 13, 2026
601b603
Mode homes: Browse links under the box on an empty home (audit item 17)
claude Oct 13, 2026
f0bd61c
Formulation record: shared record layout and a Clinical blue main button
claude Oct 13, 2026
33e03cc
Medication record: shared record layout, no green side bar
claude Oct 13, 2026
548c1d8
Shorten bottom-box prompts so each fits at 390 px
claude Oct 13, 2026
5fa2ba6
Calculators: plain words in the About these tools notice
claude Oct 13, 2026
2657d75
Tests: follow the compact call button, single First Nations entry and…
claude Oct 13, 2026
38d530a
Forms record: shared record layout, facts shown in full
claude Oct 13, 2026
c824a8b
Tests: find the services phone button by its test id in the tap-floor…
claude Oct 13, 2026
1f30ba0
Services record: shared record layout, no blue side bar
claude Oct 13, 2026
241f375
Compare sheet and comparison buttons: Clinical blue main buttons
claude Oct 13, 2026
8728e5b
Phone composer: stop rem sizes growing past normal at large text
claude Oct 13, 2026
3a0688b
A to Z strip: use the leading-snug token instead of an arbitrary line…
claude Oct 13, 2026
a7093b0
Merge branch 'worktree-agent-adc98649389c888ad' into claude/project-t…
claude Oct 13, 2026
4b9fe3b
Merge branch 'worktree-agent-a97d640c98343bbdd' into claude/project-t…
claude Oct 13, 2026
6cf99f5
Merge branch 'worktree-agent-adc98649389c888ad' into worktree-agent-a…
claude Oct 13, 2026
aa94ea4
Merge remote-tracking branch 'origin/main' into worktree-agent-af5631…
claude Oct 13, 2026
e6b612b
Medication Access panel: keep decimal strengths in Formulation (was c…
claude Oct 13, 2026
6ff956e
Merge branch 'worktree-agent-af5631045485a3490' into claude/project-t…
claude Oct 13, 2026
5c07e91
Design-system contract: tokens for new status tag border, services ta…
claude Oct 13, 2026
f3a469c
A to Z strip: a skipped stray row no longer drops the next letter's f…
claude Oct 13, 2026
16942dc
A to Z strip: look past strays filed too early when checking a row's …
claude Oct 13, 2026
979966d
Fix browser failures: idle phone mode home fits one screen (two Brows…
claude Oct 13, 2026
3268f4a
Fix browser-suite failures: back to top from diagnosis foot actions, …
claude Oct 13, 2026
c2b6c8b
Wait for the At a glance grid to reflow before counting its rows
claude Oct 13, 2026
217e7be
Keep therapy session length in Format; keep a tab stop when the A to …
claude Oct 13, 2026
729c9be
Keep the search error visible over earlier results; show every formul…
claude Oct 13, 2026
25e404e
Retry the current search on a degraded zero result, not an older fail…
claude Oct 13, 2026
32ac289
fix(services): letter strip only for A to Z sort, tone-based chip sha…
BigSimmo Oct 13, 2026
763f9e0
fix(services): amber dot only for closed, paused, planned or low-conf…
claude Oct 13, 2026
7d320bb
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Oct 13, 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
4 changes: 2 additions & 2 deletions docs/design-system/COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ This generated snapshot is a local source-derived inventory. It does not assert
| `LoadingPanel` | feedback | yes | yes | inherited-global-root | yes | no | 10 |
| `MissingValue` | feedback | yes | yes | inherited-global-root | yes | no | 11 |
| `OverlayRoot` | layout | yes | yes | inherited-global-root | yes | no | 1 |
| `PageHeader` | layout | yes | yes | inherited-global-root | yes | no | 16 |
| `PageHeader` | layout | yes | yes | inherited-global-root | yes | no | 14 |
| `Pagination` | controls | yes | yes | inherited-global-root | yes | no | 1 |
| `PanelHeading` | layout | yes | yes | inherited-global-root | yes | no | 2 |
| `Progress` | feedback | yes | yes | inherited-global-root | yes | no | 1 |
Expand All @@ -1094,7 +1094,7 @@ This generated snapshot is a local source-derived inventory. It does not assert
| `SearchField` | controls | yes | yes | inherited-global-root | yes | no | 5 |
| `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 33 |
| `Select` | controls | yes | yes | inherited-global-root | yes | no | 16 |
| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 137 |
| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 136 |
| `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 9 |
| `SourceDesignationBadge` | source | yes | yes | inherited-global-root | yes | no | 4 |
| `SourceProvenance` | source | yes | yes | inherited-global-root | yes | no | 1 |
Expand Down
6 changes: 0 additions & 6 deletions docs/design-system/adoption-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1710,15 +1710,13 @@
"directImportFiles": [
"src/components/dictionary/dictionary-catalogue-pages.tsx",
"src/components/differentials/differential-compare-queue-page.tsx",
"src/components/differentials/differential-detail-page.tsx",
"src/components/dsm/dsm-page-header.tsx",
"src/components/factsheets/factsheets-topics-page.tsx",
"src/components/information-page-shell.tsx",
"src/components/specifiers/specifier-compare-page.tsx",
"src/components/specifiers/specifier-map-page.tsx",
"src/components/therapy-compass/screens/brief-screen.tsx",
"src/components/therapy-compass/screens/compare-screen.tsx",
"src/components/therapy-compass/screens/detail-screen.tsx",
"src/components/therapy-compass/screens/other-screen.tsx",
"src/components/therapy-compass/screens/pathways-screen.tsx",
"src/components/therapy-compass/screens/recommend-screen.tsx",
Expand All @@ -1728,15 +1726,13 @@
"productImportFiles": [
"src/components/dictionary/dictionary-catalogue-pages.tsx",
"src/components/differentials/differential-compare-queue-page.tsx",
"src/components/differentials/differential-detail-page.tsx",
"src/components/dsm/dsm-page-header.tsx",
"src/components/factsheets/factsheets-topics-page.tsx",
"src/components/information-page-shell.tsx",
"src/components/specifiers/specifier-compare-page.tsx",
"src/components/specifiers/specifier-map-page.tsx",
"src/components/therapy-compass/screens/brief-screen.tsx",
"src/components/therapy-compass/screens/compare-screen.tsx",
"src/components/therapy-compass/screens/detail-screen.tsx",
"src/components/therapy-compass/screens/other-screen.tsx",
"src/components/therapy-compass/screens/pathways-screen.tsx",
"src/components/therapy-compass/screens/recommend-screen.tsx",
Expand Down Expand Up @@ -2375,7 +2371,6 @@
"src/components/teaching/term-folder/term-folder-export-sheet.tsx",
"src/components/therapy-compass/pathway-picker-sheet.tsx",
"src/components/therapy-compass/recommend-scenario-control.tsx",
"src/components/therapy-compass/record/key-facts.tsx",
"src/components/tools/tools-search-results-page.tsx",
"src/components/ui/confirm-dialog.tsx",
"src/components/work-frame/two-pane-side-menu.tsx",
Expand Down Expand Up @@ -2516,7 +2511,6 @@
"src/components/teaching/term-folder/term-folder-export-sheet.tsx",
"src/components/therapy-compass/pathway-picker-sheet.tsx",
"src/components/therapy-compass/recommend-scenario-control.tsx",
"src/components/therapy-compass/record/key-facts.tsx",
"src/components/tools/tools-search-results-page.tsx",
"src/components/ui/confirm-dialog.tsx",
"src/components/work-frame/two-pane-side-menu.tsx",
Expand Down
55 changes: 46 additions & 9 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2937,16 +2937,25 @@ html[data-motion="reduced"] .shared-compare-tray-layer[data-scroll-hidden="true"
mode-action popover only renders ≥1024px, so this holds no .mode-action-*
overrides. */
@media (max-width: 430px) {
/* Large text (browser or OS text zoom raising the root size): every rem here
grew with it while the field's own 16px text did not, so at 320px with a
200% root three 88px buttons left the field 0-24px wide and the prompt read
"As". Each size in this block is its normal rem value capped in px. The caps
sit above what any density reaches (the largest root is 17px: 2.75rem is
46.75px against a 48px cap, 1.25rem 21.25px against 24px, 0.375rem 6.4px
against 8px, 0.35rem 5.95px against 6px, 3.25rem 55.25px against 56px), so
the composer is unchanged at 15, 16 and 17px roots and only stops growing
past them. 48px keeps the full tap floor at large text. */
.chat-composer-input {
min-height: 2.75rem;
min-height: min(2.75rem, 48px);
}

.chat-composer-icon-button,
.chat-send-button {
height: 2.75rem !important;
width: 2.75rem !important;
min-height: 2.75rem;
min-width: 2.75rem;
height: min(2.75rem, 48px) !important;
width: min(2.75rem, 48px) !important;
min-height: min(2.75rem, 48px);
min-width: min(2.75rem, 48px);
}

/* 1.1rem was 17.6px: lucide draws on a 24-unit grid with a 2px stroke, so a
Expand All @@ -2957,8 +2966,34 @@ html[data-motion="reduced"] .shared-compare-tray-layer[data-scroll-hidden="true"
height this block exists to protect untouched. */
.chat-composer-icon-button svg,
.chat-send-button svg {
height: var(--spacing-icon-lg);
width: var(--spacing-icon-lg);
height: min(var(--spacing-icon-lg), 24px);
width: min(var(--spacing-icon-lg), 24px);
}

/* Large-text caps for the pill and the field's inset. Unlayered (beats the
layered surface rule) and scoped one class deeper than the base
`.answer-footer-search-input` rule further down, which would otherwise win on
source order; at normal sizes both compute to the values those rules set. */
.answer-footer-search-pill {
min-height: min(3.25rem, 56px);
gap: min(0.375rem, 8px);
padding-inline: min(0.375rem, 8px);
}

.answer-footer-search-pill .answer-footer-search-input {
padding-inline: min(0.35rem, 6px);
}

/* The in-field Clear button sizes from the tap utilities (3rem, 51px at the
17px root) and a 1rem glyph; cap them past that the same way. */
.answer-footer-search-pill .min-w-tap {
min-height: min(var(--spacing-tap), 52px);
min-width: min(var(--spacing-tap), 52px);
}

.answer-footer-search-pill .min-w-tap svg {
height: min(var(--spacing-icon-md), 20px);
width: min(var(--spacing-icon-md), 20px);
}
}
/* Mirrors the `motion-safe` variant: animate when the OS allows it, and also when
Expand Down Expand Up @@ -3150,10 +3185,12 @@ html[data-motion="reduced"] .shared-compare-tray-layer[data-scroll-hidden="true"
padding-right: max(1rem, var(--safe-area-right));
}

/* The top hit is a plain card like the rest (Clinical audit, 13 Oct 2026,
item 8): no blue rail and no blue wash, because being first is not a
status. The rule stays so the verdict rules below still win by order. */
.medication-mobile-result[data-selected="true"] {
border-color: var(--border);
background: color-mix(in srgb, var(--clinical-accent-soft) 42%, var(--surface-raised));
box-shadow: inset 3px 0 0 var(--clinical-accent);
background: var(--surface-raised);
}

/* The patient verdict owns the card edge and surface, including on the selected row.
Expand Down
24 changes: 22 additions & 2 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2746,6 +2746,17 @@ function ClinicalDashboardContent({
],
);
const answerProgressCompleted = answerProgressEvents.at(-1)?.stage === "complete";
// A failed documents search with nothing listed. The documents results own that
// message (one calm "not a reliable nothing found" state with Retry and Browse), so the
// generic red "Search unavailable" box is not stacked on top. When earlier matches are
// still listed they belong to the previous query, so the red box stays and says so.
const documentsSearchFailed = searchMode === "documents" && Boolean(error) && errorKind !== "no-results";
const documentsResultsOwnError =
documentsSearchFailed &&
activeModeResultKind === "documents" &&
modeSearchSubmitted &&
Boolean(query.trim()) &&
documentMatches.length === 0;
const differentialsCompareHasSelection = useDifferentialsCompareHasSelection();
const {
showAuthPanel,
Expand Down Expand Up @@ -3285,7 +3296,7 @@ function ClinicalDashboardContent({
</>
}
/>
) : error ? (
) : error && !documentsResultsOwnError ? (
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
<EmptyState
icon={CircleAlert}
title={activeModeResultKind === "answer" ? "Answer unavailable" : "Search unavailable"}
Expand Down Expand Up @@ -3483,7 +3494,16 @@ function ClinicalDashboardContent({
onDocumentFiltersApply={searchMode === "documents" ? handleDocumentFiltersApply : undefined}
showHome={searchMode === "documents" && !modeSearchSubmitted}
desktopComposerSlotId={desktopHomeComposerSlotId}
searchFailed={searchMode === "documents" && Boolean(error) && errorKind !== "no-results"}
searchFailed={documentsSearchFailed}
searchFailureReason={documentsSearchFailed ? error : null}
onRetrySearch={
searchMode === "documents" && query.trim()
? () => {
setError(null);
void ask(documentsSearchFailed ? (lastFailedQuery ?? query) : query);
}
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
: undefined
}
/>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/calculators/search-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export function CalculatorSearchHome({

<p className="text-center text-2xs font-medium leading-4 text-[color:var(--text-muted)]">
Calculator answers remain in this browser session and are not intentionally submitted by this calculator
interface. Application telemetry and clinical-record documentation are governed separately.
interface. The app’s usage logs, and anything you document in the clinical record, are covered separately.
</p>
</main>
);
Expand Down
55 changes: 35 additions & 20 deletions src/components/calculators/search-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,20 @@ function CalculatorTile({
// finds the tile again by calculator id rather than holding a node ref.
data-calculator-open={calc.id}
aria-label={`Open ${calc.abbrev} — ${calc.name}`}
// A compact row of the list's one hairline card (Clinical audit, 13 Oct
// 2026, item 12), the same row Therapy search uses: name, one line,
// tags, and the whole row opens the calculator.
className={cn(
"group grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-left shadow-[var(--e2)] transition hover:-translate-y-0.5 hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--surface-raised)] hover:shadow-[var(--shadow-hover)] motion-reduce:transition-none motion-reduce:hover:translate-y-0",
"group grid min-h-tap w-full grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-[color:var(--surface-subtle)] motion-reduce:transition-none sm:px-5",
focusRing,
)}
>
<span
className={cn(
"grid shrink-0 place-items-center rounded-lg border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]",
compact ? "size-10" : "size-tap",
"mt-0.5 grid size-9 shrink-0 place-items-center rounded-lg bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]",
)}
>
<Icon className={compact ? "size-icon-lg" : "size-icon-xl"} aria-hidden="true" />
<Icon className="size-icon-md" aria-hidden="true" />
</span>

<span className="min-w-0">
Expand All @@ -167,11 +169,11 @@ function CalculatorTile({
{calc.name}
</span>
{compact ? null : (
<span className="mt-2 line-clamp-2 block text-sm-minus font-medium leading-5 text-[color:var(--text-muted)]">
<span className="mt-0.5 line-clamp-1 block text-sm-minus font-medium leading-5 text-[color:var(--text-muted)]">
{calc.indication}
</span>
)}
<span className="mt-2 flex flex-wrap items-center gap-1.5">
<span className="mt-1.5 flex flex-wrap items-center gap-1.5">
<MetaPill icon={ListChecks} label={`${calc.items.length} items`} />
<MetaPill icon={Clock3} label={calc.timeEstimate} />
{compact ? null : <MetaPill icon={Sigma} label={`${calc.minScore}–${calc.maxScore}`} />}
Expand Down Expand Up @@ -273,6 +275,16 @@ function ContinuePanel({
);
}

const countWords = ["No", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"];

/** "Three more calculators are waiting for …" — plain words for the calculators still in review. */
function pendingCalculatorsNotice(count: number) {
const amount = countWords[count] ?? String(count);
return count === 1
? `${amount} more calculator is waiting for version, rights and workflow checks before it appears.`
: `${amount} more calculators are waiting for version, rights and workflow checks before they appear.`;
}

function AboutPanel() {
return (
<section className="grid gap-2 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-subtle)] p-4">
Expand All @@ -282,12 +294,11 @@ function AboutPanel() {
</h2>
<p className="text-2xs font-medium leading-4 text-[color:var(--text-muted)]">
Scores support clinical judgement — they never replace a full assessment. Calculator answers remain in this
browser session and are not intentionally submitted by this calculator interface. Application telemetry and
clinical-record documentation are governed separately.
browser session and are not intentionally submitted by this calculator interface. The app’s usage logs, and
anything you document in the clinical record, are covered separately.
</p>
<p className="text-2xs font-semibold leading-4 text-[color:var(--text-muted)]">
{plannedCalculators.length} candidate calculators remain governance-gated pending version, rights and workflow
review.
{pendingCalculatorsNotice(plannedCalculators.length)}
</p>
</section>
);
Expand Down Expand Up @@ -587,18 +598,22 @@ export function CalculatorsSearchPage({
}
>
{results.length ? (
<div className={cn("grid gap-3", density === "compact" ? "sm:grid-cols-2 xl:grid-cols-3" : "md:grid-cols-2")}>
<ul
data-testid="calculator-result-list"
className="m-0 list-none divide-y divide-[color:var(--border)] overflow-hidden rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-0"
>
{results.map(({ calc, derived, context }) => (
<CalculatorTile
key={calc.id}
calc={calc}
derived={derived}
context={context}
compact={density === "compact"}
onOpen={() => openCalculator(calc.id)}
/>
<li key={calc.id}>
<CalculatorTile
calc={calc}
derived={derived}
context={context}
compact={density === "compact"}
onOpen={() => openCalculator(calc.id)}
/>
</li>
))}
</div>
</ul>
) : (
<SearchResultsEmptyState
modeId="calculators"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,8 @@ function DocumentSearchResultsPanelImpl({
showHome = false,
desktopComposerSlotId,
searchFailed = false,
searchFailureReason = null,
onRetrySearch,
}: {
matches: ClientDocumentMatch[];
recordMatches?: SearchRecordMatch[];
Expand All @@ -837,6 +839,10 @@ function DocumentSearchResultsPanelImpl({
setupWarning: string | null;
/** The last documents search request failed, so an empty list is not a real "no matches". */
searchFailed?: boolean;
/** The failure message for that request, kept as the reason line in the one degraded message. */
searchFailureReason?: string | null;
/** Re-runs the failed documents search (the dashboard's own search runner). */
onRetrySearch?: () => void | Promise<void>;
facets?: SearchFacets | null;
searchScope?: SearchScopeSummary | null;
onScopeDocument: (documentId: string) => void;
Expand Down Expand Up @@ -1571,6 +1577,8 @@ function DocumentSearchResultsPanelImpl({
onClearFilters={activeFilterCount > 0 ? clearAllFilters : undefined}
// A retrieval layer errored, so this zero is not evidence of absence.
degraded={retrievalDegraded}
degradedReason={searchFailed ? searchFailureReason : null}
onRetry={retrievalDegraded ? onRetrySearch : undefined}
onBrowseAll={onOpenLibrary}
browseAllLabel={
documentCount > 0 ? `Browse all ${documentCount.toLocaleString()} sources` : "Browse all sources"
Expand Down
Loading
Loading