feat(web): harden shell keyboard navigation

This commit is contained in:
saravanakumardb1 2026-03-10 09:14:33 -07:00
parent 756714e67c
commit c3831176a6
3 changed files with 32 additions and 9 deletions

View File

@ -35,7 +35,7 @@ Stack: Next.js 16 + React 19 + TypeScript
- [x] Artifact/attachment UI
- [x] Task extraction/review UI
- [x] Workspace filters and saved views
- [ ] Keyboard navigation improvements
- [x] Keyboard navigation improvements
- [ ] Dense knowledge UX polish
# Phase W3 — Agent UX
@ -103,6 +103,11 @@ Stack: Next.js 16 + React 19 + TypeScript
- denser search result rows with status/owner/workspace metadata
- operator workflow summary cards on dashboard and reviews
- workspace owner visibility for denser knowledge navigation
- Hardened shared accessibility/keyboard affordances with:
- skip-to-content support in the shared shell
- stronger focus-visible treatment for interactive controls
- clearer active-nav semantics via `aria-current`
- keyboard/accessibility guidance surfaced in navigation/settings
# Open Questions
@ -133,7 +138,7 @@ Stack: Next.js 16 + React 19 + TypeScript
- Artifact upload/download UX
- Extraction-backed task review flows
- Backend-backed agent activity timeline, approval queue, proposal diff review, and audit filtering
- Dense keyboard navigation, accessibility hardening, and performance polish
- Remaining dense/accessibility polish and performance hardening
- Remaining verification:
- run `npm test`

View File

@ -14,15 +14,26 @@ export function AppShell({
}) {
return (
<div className="app-shell">
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<Sidebar />
<main className="main-panel">
<main id="main-content" className="main-panel" tabIndex={-1} aria-labelledby="page-title">
<div className="page-grid">
<header className="surface-card" style={{ padding: "var(--ml-space-6)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-4)", alignItems: "start", flexWrap: "wrap" }}>
<header
className="surface-card"
style={{ padding: "var(--ml-space-6)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-4)", alignItems: "start", flexWrap: "wrap" }}
>
<div style={{ display: "grid", gap: "var(--ml-space-2)" }}>
<div style={{ fontFamily: "var(--ml-font-display)", fontSize: "var(--ml-fs-2xl)", fontWeight: 700 }}>{title}</div>
<h1
id="page-title"
style={{ margin: 0, fontFamily: "var(--ml-font-display)", fontSize: "var(--ml-fs-2xl)", fontWeight: 700 }}
>
{title}
</h1>
<div style={{ color: "var(--ml-text-secondary)", maxWidth: 720 }}>{description}</div>
</div>
{actions ? <div>{actions}</div> : null}
{actions ? <div aria-label="Page actions">{actions}</div> : null}
</header>
{children}
</div>

View File

@ -18,7 +18,7 @@ export function Sidebar() {
const pathname = usePathname();
return (
<aside className="sidebar" style={{ padding: "var(--ml-space-6)" }}>
<aside className="sidebar" style={{ padding: "var(--ml-space-6)" }} aria-label="Primary">
<div style={{ display: "grid", gap: "var(--ml-space-6)" }}>
<div className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-3)" }}>
<div className="badge" style={{ width: "fit-content" }}>
@ -33,7 +33,7 @@ export function Sidebar() {
</div>
</div>
<nav style={{ display: "grid", gap: "var(--ml-space-2)" }}>
<nav aria-label="Primary navigation" style={{ display: "grid", gap: "var(--ml-space-2)" }}>
{navItems.map((item) => {
const Icon = item.icon;
const active = pathname === item.href || pathname.startsWith(`${item.href}/`);
@ -41,7 +41,8 @@ export function Sidebar() {
<Link
key={item.href}
href={item.href}
className="surface-muted"
className="surface-muted nav-link"
aria-current={active ? "page" : undefined}
style={{
display: "flex",
alignItems: "center",
@ -57,6 +58,12 @@ export function Sidebar() {
);
})}
</nav>
<div className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-2)" }}>
<strong>Keyboard flow</strong>
<span style={{ color: "var(--ml-text-secondary)" }}>Use Tab to move between navigation, filters, and dense result surfaces.</span>
<span style={{ color: "var(--ml-text-secondary)" }}>Use the skip link to jump directly into page content.</span>
</div>
</div>
</aside>
);