From 8883289a48b7f4845b94265b6ab82af40b1a10ad Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 17:28:04 +0000 Subject: [PATCH 1/3] fix(forms): contain long form codes in result badge and elevate design Form codes such as "6B Attachment" / "1A attachment" overflowed the fixed-size result badge because the whole code rendered at text-2xl, spilling the qualifier word out of the chip and over the title column. Introduce a FormCodeBadge that splits the code into a prominent head ("6B") and a compact, contained qualifier sub-label ("Attachment"), and scales the head font down as the code lengthens so longer codes never clip. Applied consistently to the desktop results table and the mobile cards. Also elevate the results surface: a gradient, inset-shadowed badge chip, a subtle accent rail and colour transition on row hover, and a hover affordance on the Open control. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YJY1EzEXiBayz13Q6p2GwL --- .../forms/forms-search-results-page.tsx | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/src/components/forms/forms-search-results-page.tsx b/src/components/forms/forms-search-results-page.tsx index 9a32ccf65..08ebe3efb 100644 --- a/src/components/forms/forms-search-results-page.tsx +++ b/src/components/forms/forms-search-results-page.tsx @@ -66,6 +66,60 @@ function resultCode(match: FormSearchMatch, index: number) { return formCatalogDetails(match.service)?.form ?? String(index + 1); } +// Form codes are mostly short ("1A", "10D") but some carry a trailing qualifier +// ("6B Attachment"). Splitting on the first space lets the badge show the code +// prominently while the qualifier renders as a compact sub-label, so long codes +// no longer overflow the fixed chip. +function splitFormCode(code: string): { head: string; qualifier: string | null } { + const trimmed = code.trim(); + const spaceIndex = trimmed.indexOf(" "); + if (spaceIndex === -1) return { head: trimmed, qualifier: null }; + return { + head: trimmed.slice(0, spaceIndex), + qualifier: trimmed.slice(spaceIndex + 1).trim() || null, + }; +} + +// Scale the code down as it gets longer so a four-character head ("3A/4B") still +// fits the chip without clipping. +function formHeadSizeClass(head: string, variant: "md" | "sm") { + const length = head.length; + if (variant === "sm") { + if (length <= 2) return "text-lg"; + if (length === 3) return "text-base"; + return "text-sm"; + } + if (length <= 2) return "text-2xl"; + if (length === 3) return "text-xl"; + return "text-base"; +} + +function FormCodeBadge({ code, variant = "md" }: { code: string; variant?: "md" | "sm" }) { + const { head, qualifier } = splitFormCode(code); + const isSm = variant === "sm"; + return ( +
+ {head} + {qualifier ? ( + + {qualifier} + + ) : null} +
+ ); +} + function tagToneClass(label: string) { const normalized = label.toLowerCase(); if (normalized.includes("crisis") || normalized.includes("risk") || normalized.includes("safety")) { @@ -227,7 +281,7 @@ function RefinePanel({ open, panelId }: { open: boolean; panelId: string }) { ); } -const resultsGridColumns = "md:grid-cols-[64px_minmax(0,1.35fr)_minmax(0,0.85fr)_minmax(0,1.35fr)_minmax(88px,auto)]"; +const resultsGridColumns = "md:grid-cols-[72px_minmax(0,1.35fr)_minmax(0,0.85fr)_minmax(0,1.35fr)_minmax(88px,auto)]"; function ResultsTable({ matches, @@ -271,13 +325,12 @@ function ResultsTable({ key={form.slug} data-testid={`form-search-result-${form.slug}`} className={cn( - "grid gap-4 border-b border-[color:var(--border)] px-5 py-4 transition last:border-b-0 hover:bg-[color:var(--surface-subtle)]/55 md:items-center", + "group relative grid gap-4 border-b border-[color:var(--border)] px-5 py-4 transition-colors last:border-b-0 hover:bg-[color:var(--surface-subtle)]/60 md:items-center", + "before:absolute before:inset-y-1.5 before:left-0 before:w-0.5 before:rounded-full before:bg-[color:var(--clinical-accent)] before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100", resultsGridColumns, )} > -
- {resultCode(match, index)} -
+

{form.title}

@@ -304,12 +357,12 @@ function ResultsTable({ href={`/forms/${form.slug}`} aria-label={`Open ${form.title}`} className={cn( - "inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-[color:var(--border)] px-4 text-sm font-extrabold text-[color:var(--clinical-accent)] transition hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--clinical-accent-soft)] md:justify-self-end", + "inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-[color:var(--border)] px-4 text-sm font-extrabold text-[color:var(--clinical-accent)] transition hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--clinical-accent-soft)] group-hover:border-[color:var(--clinical-accent-border)] group-hover:bg-[color:var(--clinical-accent-soft)] md:justify-self-end", searchFocusRing, )} > Open - + ); @@ -451,11 +504,9 @@ function MobileCards({ matches, query }: { matches: FormSearchMatch[]; query: st
-
- {resultCode(match, index)} -
+

From f73aa26651f34a24533a3ad5787258e5a9360377 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 17:32:25 +0000 Subject: [PATCH 2/3] style(forms): use named text-4xs type-scale step for badge qualifier The result-badge qualifier sub-label used an arbitrary text-[0.5rem] utility, which the strict type-scale gate rejects. --text-4xs is the named 0.5rem step, so swap to text-4xs for an identical rendering that stays on the design scale. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YJY1EzEXiBayz13Q6p2GwL --- src/components/forms/forms-search-results-page.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/forms/forms-search-results-page.tsx b/src/components/forms/forms-search-results-page.tsx index 08ebe3efb..44eabb40c 100644 --- a/src/components/forms/forms-search-results-page.tsx +++ b/src/components/forms/forms-search-results-page.tsx @@ -108,10 +108,7 @@ function FormCodeBadge({ code, variant = "md" }: { code: string; variant?: "md" {qualifier ? ( {qualifier} From 822e24a58363b12f65b831f36b72c872bac7e4fa Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 17:34:23 +0000 Subject: [PATCH 3/3] a11y(forms): expose full form code as one string in badge The badge renders the code as two visual spans (head + qualifier), which assistive tech and text queries would read concatenated ("6BAttachment"). Add an sr-only node carrying the full "6B Attachment" string and mark the decorative visual fragments aria-hidden, so the code is announced and queryable as one value while the split styling is preserved. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YJY1EzEXiBayz13Q6p2GwL --- src/components/forms/forms-search-results-page.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/forms/forms-search-results-page.tsx b/src/components/forms/forms-search-results-page.tsx index 44eabb40c..7dfbecacc 100644 --- a/src/components/forms/forms-search-results-page.tsx +++ b/src/components/forms/forms-search-results-page.tsx @@ -104,9 +104,15 @@ function FormCodeBadge({ code, variant = "md" }: { code: string; variant?: "md" isSm ? "h-12 w-12 gap-0 px-0.5" : "h-14 w-16 gap-0.5 px-0.5", )} > - {head} + {/* Expose the whole code as one string for assistive tech and text queries; + the split visual fragments below are decorative and hidden from the a11y tree. */} + {qualifier ? `${head} ${qualifier}` : head} + + {head} + {qualifier ? (