mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(insights): promote coach-rail labels to semantic h3 headings
The history and sources rails mount inline next to the message thread on lg+ / xl+ viewports, where no SheetTitle wrapper covers them. Both rails previously rendered their section labels as <span> with uppercase styling, so the drawer's semantic outline disappeared above the message thread on desktop. Screen-reader users navigating by heading skipped straight from the drawer's <h2> title to the message-thread region with no intermediate landmark. Promote both labels to <h3> while keeping the existing visual treatment intact. The mobile rail-tray (Sheet-wrapped) still surfaces its SheetTitle <h2>, so the rail's own <h3> simply nests below it on small screens. The desktop layout now carries a coherent h2 (drawer) > h3 (rail) hierarchy. Regression tests assert that each rail renders its label inside a real <h3> element so the semantic outline cannot silently drop again.
This commit is contained in:
1 parent
87e4cdcb1b
commit
bfb1b43af2
4 files changed
+52
-10
No files matched your search
@@ -66,6 +66,21 @@ describe("<HistoryRail>", () => {
|
||||
expect(html).toContain("Conversations");
|
||||
});
|
||||
|
||||
it("renders the rail label as a real `<h3>` heading (v1.4.33)", () => {
|
||||
// The rail mounts inline on `lg+` desktop where no `SheetTitle`
|
||||
// wrapper covers it. Without a real heading element the drawer
|
||||
// lost its semantic outline above the message thread — fixed in
|
||||
// v1.4.33 by promoting the rail label to `<h3>`.
|
||||
const client = makeClientWithConversations(samplePage);
|
||||
const html = render(
|
||||
<HistoryRail activeId={null} onSelect={() => {}} />,
|
||||
client,
|
||||
);
|
||||
expect(html).toMatch(
|
||||
/<h3[^>]*data-slot="coach-history-rail-heading"[^>]*>[\s\S]*Conversations[\s\S]*<\/h3>/,
|
||||
);
|
||||
});
|
||||
|
||||
it("renders one row per conversation", () => {
|
||||
const client = makeClientWithConversations(samplePage);
|
||||
const html = render(
|
||||
|
||||
@@ -17,6 +17,17 @@ describe("<SourcesRail>", () => {
|
||||
expect(html).toContain("What I can see");
|
||||
});
|
||||
|
||||
it("renders the rail label as a real `<h3>` heading (v1.4.33)", () => {
|
||||
// The rail mounts inline on `xl+` desktop where no `SheetTitle`
|
||||
// wrapper covers it. Promoting the label to `<h3>` restores the
|
||||
// drawer's semantic outline so screen-reader users can navigate
|
||||
// by heading.
|
||||
const html = render(<SourcesRail />);
|
||||
expect(html).toMatch(
|
||||
/<h3[^>]*data-slot="coach-sources-rail-heading"[^>]*>[\s\S]*What I can see[\s\S]*<\/h3>/,
|
||||
);
|
||||
});
|
||||
|
||||
it("lists exactly five rows (BP, weight, pulse, mood, compliance)", () => {
|
||||
const html = render(<SourcesRail />);
|
||||
const rows = html.match(/data-slot="coach-sources-row"/g) ?? [];
|
||||
|
||||
@@ -69,15 +69,23 @@ export function HistoryRail({
|
||||
data-slot="coach-history-rail"
|
||||
className={cn("flex h-full min-h-0 flex-col gap-2 p-3", className)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 px-1">
|
||||
{/* v1.4.33 — promote the rail label from a `<span>` to a real
|
||||
`<h3>` so the drawer carries a semantic outline on desktop
|
||||
where the rail is mounted inline (no `SheetTitle` wrapper).
|
||||
The mobile rail-tray still wraps the rail inside its own
|
||||
`SheetTitle`, so screen-reader users hear the section twice
|
||||
when the tray is open — that's intentional Radix behaviour
|
||||
(the rail still has to stand alone on desktop). */}
|
||||
<h3
|
||||
data-slot="coach-history-rail-heading"
|
||||
className="text-muted-foreground flex items-center gap-1.5 px-1 text-[11px] font-medium tracking-wide uppercase"
|
||||
>
|
||||
<MessagesSquare
|
||||
className="text-muted-foreground size-3.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="text-muted-foreground text-[11px] font-medium tracking-wide uppercase">
|
||||
{t("insights.coach.historyTitle")}
|
||||
</span>
|
||||
</div>
|
||||
{t("insights.coach.historyTitle")}
|
||||
</h3>
|
||||
<div className="relative">
|
||||
<Search
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -172,12 +172,20 @@ export function SourcesRail({
|
||||
data-slot="coach-sources-rail"
|
||||
className={cn("flex h-full min-h-0 flex-col gap-3 p-3", className)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 px-1">
|
||||
{/* v1.4.33 — promote the rail label from a `<span>` to a real
|
||||
`<h3>` so the rail carries a semantic outline on desktop
|
||||
where it mounts inline next to the message thread. The
|
||||
mobile rail-tray wraps the rail inside its own `SheetTitle`
|
||||
(an `<h2>`), so the rail's own `<h3>` simply nests beneath
|
||||
it — the heading hierarchy stays consistent across
|
||||
viewports. */}
|
||||
<h3
|
||||
data-slot="coach-sources-rail-heading"
|
||||
className="text-muted-foreground flex items-center gap-1.5 px-1 text-[11px] font-medium tracking-wide uppercase"
|
||||
>
|
||||
<Eye className="text-muted-foreground size-3.5" aria-hidden="true" />
|
||||
<span className="text-muted-foreground text-[11px] font-medium tracking-wide uppercase">
|
||||
{t("insights.coach.sourcesTitle")}
|
||||
</span>
|
||||
</div>
|
||||
{t("insights.coach.sourcesTitle")}
|
||||
</h3>
|
||||
|
||||
{/* Window selector — every row below is filtered to the picked
|
||||
window when the next Coach message goes out. 36px touch
|
||||
|
||||
Reference in new issue
Block a user