Skip to content

Commit 458c7f9

Browse files
Improve header accessibility hints (#551)
## Summary - add missing hover/title hints and accessible labels to compact title-bar and modal controls - add a sticky Analysis pane title bar with an explicit close button - clarify the session analysis toggle label based on open/closed state ## Validation - npm run check - npm test -- AppHeader.test.ts SessionList.test.ts - npm test -- SessionVitals.test.ts AppHeader.test.ts - npm test - git diff --check --------- Co-authored-by: Codex <codex@openai.com>
1 parent a953013 commit 458c7f9

16 files changed

Lines changed: 229 additions & 7 deletions

frontend/src/lib/components/content/SessionVitals.svelte

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,24 @@
225225
</script>
226226

227227
<div class="vital">
228+
<header class="vital-titlebar">
229+
<div>
230+
<div class="vital-title">Analysis</div>
231+
<div class="vital-subtitle">Session vital signs</div>
232+
</div>
233+
<button
234+
type="button"
235+
class="vital-close"
236+
title="Close session analysis"
237+
aria-label="Close session analysis"
238+
onclick={() => ui.closeVitals()}
239+
>
240+
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
241+
<path d="M3.22 3.22a.75.75 0 011.06 0L8 6.94l3.72-3.72a.75.75 0 111.06 1.06L9.06 8l3.72 3.72a.75.75 0 11-1.06 1.06L8 9.06l-3.72 3.72a.75.75 0 11-1.06-1.06L6.94 8 3.22 4.28a.75.75 0 010-1.06z"/>
242+
</svg>
243+
</button>
244+
</header>
245+
228246
{#if timing}
229247
<section class="v-section">
230248
<header class="v-h">
@@ -497,6 +515,56 @@
497515
min-height: 0;
498516
}
499517
518+
.vital-titlebar {
519+
position: sticky;
520+
top: 0;
521+
z-index: 2;
522+
min-height: 42px;
523+
padding: 7px 10px 7px 14px;
524+
display: flex;
525+
align-items: center;
526+
justify-content: space-between;
527+
gap: 12px;
528+
background: var(--bg-surface);
529+
border-bottom: 1px solid var(--border-default);
530+
}
531+
532+
.vital-title {
533+
color: var(--text-primary);
534+
font-size: 12px;
535+
font-weight: 650;
536+
line-height: 1.2;
537+
}
538+
539+
.vital-subtitle {
540+
color: var(--text-muted);
541+
font-size: 10px;
542+
line-height: 1.2;
543+
margin-top: 1px;
544+
}
545+
546+
.vital-close {
547+
width: 26px;
548+
height: 26px;
549+
display: inline-flex;
550+
align-items: center;
551+
justify-content: center;
552+
flex-shrink: 0;
553+
border-radius: var(--radius-sm);
554+
color: var(--text-muted);
555+
transition: background 0.12s, color 0.12s;
556+
}
557+
558+
.vital-close:hover {
559+
background: var(--bg-surface-hover);
560+
color: var(--text-primary);
561+
}
562+
563+
.vital-close:focus-visible {
564+
outline: 2px solid var(--accent-blue);
565+
outline-offset: 2px;
566+
}
567+
500568
.v-section {
501569
padding: 12px 14px 14px;
502570
border-bottom: 1px solid var(--border-muted);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// @vitest-environment jsdom
2+
import {
3+
afterEach,
4+
beforeEach,
5+
describe,
6+
expect,
7+
it,
8+
vi,
9+
} from "vitest";
10+
import { mount, tick, unmount } from "svelte";
11+
import type { SessionTiming } from "../../api/types/timing.js";
12+
13+
const mocks = vi.hoisted(() => {
14+
const timing: SessionTiming = {
15+
session_id: "sess-1",
16+
total_duration_ms: 1200,
17+
tool_duration_ms: 0,
18+
turn_count: 1,
19+
tool_call_count: 0,
20+
subagent_count: 0,
21+
slowest_call: null,
22+
by_category: [],
23+
turns: [],
24+
running: false,
25+
};
26+
27+
return {
28+
fetchSessionTiming: vi.fn().mockResolvedValue(timing),
29+
};
30+
});
31+
32+
vi.mock("../../api/timing.js", () => ({
33+
fetchSessionTiming: mocks.fetchSessionTiming,
34+
}));
35+
36+
import { ui } from "../../stores/ui.svelte.js";
37+
import { sessionTiming } from "../../stores/sessionTiming.svelte.js";
38+
// @ts-ignore
39+
import SessionVitals from "./SessionVitals.svelte";
40+
41+
describe("SessionVitals", () => {
42+
let component: ReturnType<typeof mount> | undefined;
43+
44+
beforeEach(() => {
45+
sessionTiming.reset();
46+
ui.vitalsOpen = true;
47+
});
48+
49+
afterEach(() => {
50+
if (component) {
51+
unmount(component);
52+
component = undefined;
53+
}
54+
sessionTiming.reset();
55+
ui.vitalsOpen = false;
56+
document.body.innerHTML = "";
57+
});
58+
59+
it("has an obvious close control inside the analysis pane", async () => {
60+
component = mount(SessionVitals, {
61+
target: document.body,
62+
props: { sessionId: "sess-1" },
63+
});
64+
await tick();
65+
await tick();
66+
67+
const closeButton = document.querySelector<HTMLButtonElement>(
68+
'button[aria-label="Close session analysis"]',
69+
);
70+
71+
expect(closeButton).not.toBeNull();
72+
expect(closeButton?.title).toBe("Close session analysis");
73+
74+
closeButton!.click();
75+
await tick();
76+
77+
expect(ui.vitalsOpen).toBe(false);
78+
});
79+
});

frontend/src/lib/components/filters/SessionFilterControl.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
class="filter-btn"
126126
bind:this={filterBtnRef}
127127
onclick={() => (open = !open)}
128+
title="Filter sessions"
128129
aria-label="Filters"
129130
aria-expanded={open}
130131
>

frontend/src/lib/components/import/ImportModal.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
class="modal-close"
218218
onclick={handleClose}
219219
disabled={importing}
220+
title="Close import dialog"
220221
aria-label="Close"
221222
>&times;</button>
222223
</div>

frontend/src/lib/components/layout/AppHeader.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@
255255
class:active={router.route === "trends" || router.route === "pinned" || router.route === "insights" || router.route === "trash" || moreOpen}
256256
bind:this={moreBtnRef}
257257
onclick={() => { moreOpen = !moreOpen; }}
258+
title="More navigation"
258259
aria-label="More navigation"
259260
aria-expanded={moreOpen}
260261
>
@@ -637,6 +638,7 @@
637638
class="header-btn"
638639
onclick={() => (ui.activeModal = "shortcuts")}
639640
title="Keyboard shortcuts (?)"
641+
aria-label="Keyboard shortcuts"
640642
>
641643
?
642644
</button>

frontend/src/lib/components/layout/AppHeader.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,21 @@ describe("AppHeader export actions", () => {
9999
expect(ui.followLatest).toBe(false);
100100
expect(followButton!.classList.contains("active")).toBe(false);
101101
});
102+
103+
it("labels compact title-bar actions with hover hints", async () => {
104+
component = mount(AppHeader, { target: document.body });
105+
await tick();
106+
107+
const moreButton = document.querySelector<HTMLButtonElement>(
108+
'button[aria-label="More navigation"]',
109+
);
110+
const shortcutsButton = document.querySelector<HTMLButtonElement>(
111+
'button[aria-label="Keyboard shortcuts"]',
112+
);
113+
114+
expect(moreButton).not.toBeNull();
115+
expect(moreButton?.title).toBe("More navigation");
116+
expect(shortcutsButton).not.toBeNull();
117+
expect(shortcutsButton?.title).toBe("Keyboard shortcuts (?)");
118+
});
102119
});

frontend/src/lib/components/layout/SessionBreadcrumb.svelte

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,11 @@
388388

389389

390390
<div class="session-breadcrumb">
391-
<button class="breadcrumb-link" onclick={onBack}>
391+
<button
392+
class="breadcrumb-link"
393+
onclick={onBack}
394+
title="Back to sessions"
395+
>
392396
Sessions
393397
</button>
394398
<span class="breadcrumb-sep">/</span>
@@ -552,8 +556,9 @@
552556
{@const rawId = sessionDisplayId(session.id)}
553557
<button
554558
class="session-id"
555-
title={rawId}
559+
title="Copy session ID: {rawId}"
556560
onclick={() => copySessionId(rawId, session.id)}
561+
aria-label="Copy session ID"
557562
>
558563
{copiedSessionId === session.id
559564
? "Copied!"
@@ -596,9 +601,13 @@
596601
<button
597602
class="minimap-btn"
598603
class:minimap-btn--active={ui.vitalsOpen}
599-
title="Session vital signs"
604+
title={ui.vitalsOpen
605+
? "Hide session analysis"
606+
: "Show session analysis"}
600607
onclick={() => ui.toggleVitals()}
601-
aria-label="Toggle session vital signs"
608+
aria-label={ui.vitalsOpen
609+
? "Hide session analysis"
610+
: "Show session analysis"}
602611
>
603612
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor">
604613
<path d="M1 14V8h2v6H1zm4 0V2h2v12H5zm4 0V5h2v9H9zm4 0V9h2v5h-2z"/>
@@ -618,6 +627,7 @@
618627
<button
619628
class="actions-btn"
620629
title="Session actions"
630+
aria-label="Session actions"
621631
bind:this={menuBtnEl}
622632
onclick={toggleMenu}
623633
>

frontend/src/lib/components/modals/AboutModal.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
<button
5050
class="close-btn"
5151
onclick={() => ui.activeModal = null}
52+
title="Close about dialog"
53+
aria-label="Close about dialog"
5254
>
5355
&times;
5456
</button>

frontend/src/lib/components/modals/ConfirmDeleteModal.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@
6969
<div class="confirm-modal">
7070
<div class="confirm-header">
7171
<h3 class="confirm-title">Delete Session</h3>
72-
<button class="close-btn" onclick={close}>&times;</button>
72+
<button
73+
class="close-btn"
74+
onclick={close}
75+
title="Close delete confirmation"
76+
aria-label="Close delete confirmation"
77+
>&times;</button>
7378
</div>
7479

7580
<div class="confirm-body">

frontend/src/lib/components/modals/PublishModal.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
<button
9494
class="modal-close"
9595
onclick={() => ui.activeModal = null}
96+
title="Close publish dialog"
97+
aria-label="Close publish dialog"
9698
>
9799
&times;
98100
</button>

0 commit comments

Comments
 (0)