Commit 028900d0 authored by edy's avatar edy

fix(ui): add renderer keyboard navigation affordances

parent 9fd7024f
......@@ -1578,6 +1578,7 @@ export default function App() {
return (
<div className={"shell openclaw-theme" + (isConversationView ? " conversation-shell" : "") + (viewMode === "experts" ? " conversation-shell-experts" : "") + (isSidebarCollapsed ? " sidebar-collapsed" : "")}>
<a className="skip-link" href="#main-content">跳到主内容</a>
<TopBar onMinimize={minimizeWindow} onMaximize={maximizeWindow} onClose={closeWindow} />
<AppSidebar
viewMode={viewMode}
......@@ -1610,7 +1611,7 @@ export default function App() {
<div className={"main-shell" + (isConversationView ? " conversation-main-layout" : "") + (viewMode === "settings" ? " settings-main-shell" : "")}>
{infoText ? <div className="notice toast-notice" role="status" aria-live="polite">{infoText}</div> : null}
{errorText ? <div className="notice error" role="alert">{errorText}</div> : null}
<main className={"content-area" + (isConversationView ? " conversation-content-area" : "")}>
<main id="main-content" tabIndex={-1} className={"content-area" + (isConversationView ? " conversation-content-area" : "")}>
{isConversationView ? (
<ConversationWorkspaceView {...conversationWorkspaceProps} />
) : null}
......
......@@ -11,6 +11,28 @@
background: #f0f7ff;
}
.skip-link {
position: fixed;
top: 10px;
left: 10px;
z-index: 10000;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid rgba(37, 99, 235, 0.38);
background: #ffffff;
color: #1d4ed8;
font-size: 13px;
font-weight: 700;
text-decoration: none;
transform: translateY(-140%);
}
.skip-link:focus-visible {
transform: translateY(0);
outline: 2px solid rgba(37, 99, 235, 0.56);
outline-offset: 2px;
}
.sidebar {
height: 100vh;
padding: var(--space-5) var(--space-4);
......
......@@ -139,7 +139,7 @@ test("read-only config values expose full values by title and copy on click or k
assert.match(settingsPanelsSource, /setCopiedConfigValue\(value\)/)
assert.match(settingsPanelsSource, /settings-readonly-config-copy-feedback/)
assert.match(settingsPanelsSource, />已复制</)
assert.doesNotMatch(settingsPanelsSource, /已复制/)
assert.doesNotMatch(settingsPanelsSource, /\u2705已复制/)
assert.match(settingsPanelsSource, /navigator\.clipboard\.writeText\(value\)/)
assert.match(settingsPanelsSource, /const handleReadonlyConfigKeyDown = \(event: KeyboardEvent<HTMLDivElement>, value\?: string\) =>/)
assert.match(settingsPanelsSource, /event\.key !== "Enter" && event\.key !== " "/)
......
......@@ -482,7 +482,7 @@
- Modify: `apps/ui/src/styles/shell.css`
- Modify: `apps/ui/src/styles/theme-openclaw.css`
- [ ] **Step 1: Add skip link**
- [x] **Step 1: Add skip link**
Add a visually hidden but focus-visible skip link near the top of the shell:
......@@ -501,7 +501,11 @@
- First Tab focuses skip link.
- Pressing Enter moves focus target to main content.
- [ ] **Step 2: Add skip link styles**
Progress note:
- Completed on 2026-05-25: added `跳到主内容` skip link and `main#main-content` with `tabIndex={-1}`.
- [x] **Step 2: Add skip link styles**
Add CSS:
......@@ -531,7 +535,11 @@
- Hidden by default.
- Visible only when focused.
- [ ] **Step 3: Confirm nav tab order**
Progress note:
- Completed on 2026-05-25 in `shell.css`.
- [x] **Step 3: Confirm nav tab order**
Manual validation:
......@@ -546,7 +554,11 @@
- No keyboard trap.
- No hidden active control receives focus unless intentionally screen-reader-only.
- [ ] **Step 4: Define desktop responsive contract**
Progress note:
- Confirmed on 2026-05-25 with browser snapshot: first Tab focuses skip link, Enter moves focus to `main#main-content`, then navigation/content controls remain reachable.
- [x] **Step 4: Define desktop responsive contract**
Current shell uses `min-width: 960px`. Keep that if the product is desktop-only, but document it in CSS comments and acceptance notes.
......@@ -555,7 +567,11 @@
- At widths below 960px, behavior is intentional: either horizontal app viewport constraint or a later mobile-specific plan.
- Do not pretend mobile is supported unless the shell actually supports it.
- [ ] **Step 5: Validate accessibility basics**
Progress note:
- Documented in `shell.css`; current contract remains desktop-first with `min-width: 960px`.
- [x] **Step 5: Validate accessibility basics**
Manual checks:
......@@ -568,18 +584,22 @@
- No obvious keyboard-only blocker in chat, settings, task panel, automation tasks.
Progress note:
- Checked on 2026-05-25: error notices retain `role="alert"`, icon-only controls retain accessible labels, decorative startup image keeps `alt=""`, and settings Tabs preserve existing aria semantics.
## 8. Final Acceptance Checklist
- [ ] `corepack pnpm --filter @qjclaw/ui typecheck` passes.
- [ ] `corepack pnpm build` passes.
- [ ] Relevant source tests pass for touched views.
- [ ] `App.tsx` has no UTF-8 BOM.
- [ ] `git diff -- apps/ui/src/App.tsx` shows no Chinese mojibake.
- [ ] Chat, experts, tasks, automation, knowledge, settings render with correct height and scrolling.
- [ ] Left sidebar remains left column; right workspace remains full work area.
- [ ] No core smoke selector or `__QJC_SMOKE_ACTIONS__` contract is removed.
- [ ] No visible emoji remains in primary tool controls after Phase 4.
- [ ] Focus-visible states are visible in sidebar, composer, tabs, settings controls, and message actions.
- [x] `corepack pnpm --filter @qjclaw/ui typecheck` passes.
- [x] `corepack pnpm build` passes.
- [x] Relevant source tests pass for touched views.
- [x] `App.tsx` has no UTF-8 BOM.
- [x] `git diff -- apps/ui/src/App.tsx` shows no Chinese mojibake.
- [x] Chat, experts, tasks, automation, knowledge, settings render with correct height and scrolling.
- [x] Left sidebar remains left column; right workspace remains full work area.
- [x] No core smoke selector or `__QJC_SMOKE_ACTIONS__` contract is removed.
- [x] No visible emoji remains in primary tool controls after Phase 4.
- [x] Focus-visible states are visible in sidebar, composer, tabs, settings controls, and message actions.
## 9. Suggested Commit Sequence
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment