fix(a11y): EntityRow and MetricCard onClick — add role=button, tabIndex, onKeyDown (QUA-169)#1523
Conversation
… and add missing models (#1357) Cursor CLI now rejects "auto" as a model selection with "Cannot use this model: auto". Change the default to "composer-2" and add "composer-2" + "composer-2-fast" to the fallback model list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…awbot.vn Add allowedHosts: "all" to vite server config so that requests from custom domain behind Cloudflare are not blocked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ers from agent worktree modifications
… production deployments
Break the monolithic AgentDetail page into focused, maintainable modules: - AgentDetail.tsx: slim orchestrator (593 LOC, down from 3988) - agent-detail/OverviewTab.tsx: dashboard with charts, issues, costs - agent-detail/InstructionsTab.tsx: instructions bundle editor - agent-detail/ConfigurationTab.tsx: agent config form + permissions + keys - agent-detail/SkillsTab.tsx: skill management - agent-detail/RunsTab.tsx: run list + run detail (lazy loaded via React.lazy) - agent-detail/LogViewer.tsx: live transcript/log viewer - agent-detail/KeysTab.tsx: API key management - agent-detail/WorkspaceOperations.tsx: workspace operation UI - agent-detail/utils.ts: shared utilities and types RunsTab is lazy-loaded for better initial page performance. No functional changes — pure extraction refactor. QUA-123 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…calls When /agents/:id/wakeup is called directly (not via the checkout route), the contextSnapshot lacks issueId/projectId/workspaceId, causing the run to fall back to agent_home instead of the project workspace. Now enqueueWakeup auto-detects the agent's in-progress checked-out issue and enriches the context with issueId, projectId, and projectWorkspaceId. Also persists resolved projectId to contextSnapshot when only issueId was provided. Fixes #1387 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… locks (QUA-12) release() now clears executionRunId, executionAgentNameKey, and executionLockedAt alongside checkoutRunId. Previously these fields persisted after release, causing permanent stale locks that blocked re-checkout by other agents. Adds unit tests verifying all execution fields are nulled on release. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nsert (#QUA-91) Gemini adapter output sometimes contains null bytes (0x00) which PostgreSQL rejects in TEXT and JSONB columns. Add stripNullBytes() sanitization at the DB write layer (setRunStatus + appendRunEvent) to protect against any adapter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Windows with WIN1252 system locale, child process stdout/stderr
default to the system encoding, causing crashes when adapter output
contains Unicode characters. Add setEncoding("utf8") on streams
immediately after spawn, matching the pattern used in codex-local.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#1465) Users running adapters like codex_local with non-standard models (e.g. grok-4.20-beta) were blocked by the hardcoded model dropdown. Now: - ModelDropdown shows a "Use <custom>" option when search doesn't match any known model - InlineEntitySelector gains allowCustomValue prop for the same behavior - NewIssueDialog model override selector enables custom values - Custom model values display correctly in trigger buttons Closes #1465 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ers from agent worktree modifications
… production deployments
…nsert (#QUA-91) Gemini adapter output sometimes contains null bytes (0x00) which PostgreSQL rejects in TEXT and JSONB columns. Add stripNullBytes() sanitization at the DB write layer (setRunStatus + appendRunEvent) to protect against any adapter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Windows with WIN1252 system locale, child process stdout/stderr
default to the system encoding, causing crashes when adapter output
contains Unicode characters. Add setEncoding("utf8") on streams
immediately after spawn, matching the pattern used in codex-local.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and add missing models (#1357) Cursor CLI now rejects "auto" as a model selection with "Cannot use this model: auto". Change the default to "composer-2" and add "composer-2" + "composer-2-fast" to the fallback model list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…awbot.vn Add allowedHosts: "all" to vite server config so that requests from custom domain behind Cloudflare are not blocked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cancelActiveForAgentInternal now matches cancelRunInternal's escalation pattern: SIGTERM → grace period → SIGKILL. The runningProcesses entry is deferred until after the grace window to prevent orphaned processes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…1461) The codex adapter's getQuotaWindows spawns the codex CLI in its class constructor. If codex is not installed, spawn emits ENOENT which can crash the server when the Costs → Providers page is loaded. Wrap each adapter.getQuotaWindows() call in try-catch to handle both synchronous throws and async rejections gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#1165) enqueueWakeup can throw (budget blocked, agent state conflict, etc). Without a try-catch inside the tickTimers loop, one failing agent aborts the entire tick — silently stopping all scheduled heartbeats. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… local_trusted mode
When a bearer token is present but fails validation (expired JWT, invalid key),
the default local_trusted board actor was not cleared, allowing unauthenticated
requests to inherit full board-level access. This enabled agents with expired
auth to perform destructive operations like deleting other agents.
Now clears the actor to { type: "none" } when a bearer token is present before
validation, so only successful verification restores the proper actor type.
Fixes #1314
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jwtConfig() only read PAPERCLIP_AGENT_JWT_SECRET, but server startup (index.ts) accepts BETTER_AUTH_SECRET as a fallback. When only BETTER_AUTH_SECRET is set (common in local dev), all agent runs failed to get PAPERCLIP_API_KEY injected. Align the fallback chain so local agents authenticate correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…th EPERM
On Windows without Developer Mode, symlink creation fails with EPERM.
The new symlinkOrCopy helper catches EPERM and falls back to
fs.cp(source, target, {recursive: true}), allowing skills to be
installed without requiring elevated privileges.
Closes #1464
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Filter tabs: All | Active | Planned | Achieved - Default filter: active (most useful view) - Persist preference to localStorage key goals:statusFilter - Empty state when filtered results = 0 - Pattern matches Routines page filter bar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt Tasks (QUA-162) Section headers for Recent Activity and Recent Tasks lacked navigation links, creating dead-ends for users who want to see the full list. Added subtle "View all →" links pointing to /activity and /issues respectively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…expanded to Org tree nodes (QUA-163)
assertAdapterConfigConstraints now checks that the configured model exists in the adapter's known model list for all adapter types, not just opencode_local. Returns a clear 422 error with available models when a mismatch is detected. Also expanded the PATCH handler guard to call validation for all adapter types, preventing silent misconfiguration that causes agents to fail at runtime. Closes #1388 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
buildWakeText hardcoded ~/.openclaw/workspace/ as the claimed API key location. When an agent has a different workspace (e.g. workspace-nora), the path was wrong and the agent couldn't find its token. Now accepts an optional workspacePath from agent config or paperclip workspace context, falling back to the original default only when neither is available. Closes #1371 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # server/src/__tests__/heartbeat-context-content-trust.test.ts
Greptile SummaryThis PR adds keyboard accessibility ( Key findings:
Confidence Score: 3/5
Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: ui/src/components/EntityRow.tsx
Line: 70
Comment:
**`onKeyDown` fires on events bubbled from focusable children**
The handler doesn't verify that the event originated on the root `<div>` itself. If `trailing` contains a focusable child element — such as an `<a>`, a `<button>`, or another `role="button"` node — and the user presses Space while focused on that child:
1. The keydown event bubbles up to this div.
2. The handler calls `e.preventDefault()`, which **suppresses the child button's subsequent `keyup`-triggered click** (browsers fire button clicks on Space `keyup`, and `preventDefault` on `keydown` cancels it).
3. The outer `onClick` fires instead of the intended inner action.
Add a target guard to restrict the handler to self-focus only. Replace the handler expression with one that checks `e.target === e.currentTarget` before acting.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/components/MetricCard.tsx
Line: 51
Comment:
**`onKeyDown` fires on events bubbled from child elements**
Same event-bubbling issue as in `EntityRow`: if any child inside `inner` becomes focusable (e.g. an icon wrapped in a link), a Space or Enter keydown bubbles up here. `e.preventDefault()` is called before the child's `keyup` click fires, hijacking the interaction and triggering the outer `onClick` instead.
Add a `e.target === e.currentTarget` check before the key guard to scope the handler strictly to the card's own focus.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/components/EntityRow.tsx
Line: 65-74
Comment:
**Missing focus-visible ring — inconsistent with design system**
All other interactive elements in this project (see `ui/src/components/ui/button.tsx`) pair `outline-none` with an explicit `focus-visible:ring-[3px]` class so the focus indicator matches the design system. Without it, this `<div>` falls back to the browser's default outline, which is visually inconsistent and can appear differently (or be suppressed) across browsers.
Consider adding the same `focus-visible:outline-none focus-visible:ring-[3px]` classes used by `<Button>` — conditioned on `isClickable` — alongside the `classes` variable.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/components/MetricCard.tsx
Line: 46-55
Comment:
**Missing focus-visible ring — inconsistent with design system**
Like the `EntityRow` change, this wrapper div will show the raw browser outline on focus rather than the project's design-system ring style (see `ui/src/components/ui/button.tsx` for the pattern). For a PR claiming WCAG 2.1 AA compliance, having a non-standard, browser-default focus ring is worth addressing to ensure consistent, predictable visual feedback across the UI.
Add `focus-visible:outline-none focus-visible:ring-[3px]` (plus an appropriate `rounded-lg` so the ring follows the card shape) to the wrapper `className`.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix(a11y): add role=..." |
| onClick={onClick} | ||
| role={isClickable ? "button" : undefined} | ||
| tabIndex={isClickable ? 0 : undefined} | ||
| onKeyDown={isClickable ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick?.(); } } : undefined} |
There was a problem hiding this comment.
onKeyDown fires on events bubbled from focusable children
The handler doesn't verify that the event originated on the root <div> itself. If trailing contains a focusable child element — such as an <a>, a <button>, or another role="button" node — and the user presses Space while focused on that child:
- The keydown event bubbles up to this div.
- The handler calls
e.preventDefault(), which suppresses the child button's subsequentkeyup-triggered click (browsers fire button clicks on Spacekeyup, andpreventDefaultonkeydowncancels it). - The outer
onClickfires instead of the intended inner action.
Add a target guard to restrict the handler to self-focus only. Replace the handler expression with one that checks e.target === e.currentTarget before acting.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/EntityRow.tsx
Line: 70
Comment:
**`onKeyDown` fires on events bubbled from focusable children**
The handler doesn't verify that the event originated on the root `<div>` itself. If `trailing` contains a focusable child element — such as an `<a>`, a `<button>`, or another `role="button"` node — and the user presses Space while focused on that child:
1. The keydown event bubbles up to this div.
2. The handler calls `e.preventDefault()`, which **suppresses the child button's subsequent `keyup`-triggered click** (browsers fire button clicks on Space `keyup`, and `preventDefault` on `keydown` cancels it).
3. The outer `onClick` fires instead of the intended inner action.
Add a target guard to restrict the handler to self-focus only. Replace the handler expression with one that checks `e.target === e.currentTarget` before acting.
How can I resolve this? If you propose a fix, please make it concise.| role="button" | ||
| tabIndex={0} | ||
| onClick={onClick} | ||
| onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } }} |
There was a problem hiding this comment.
onKeyDown fires on events bubbled from child elements
Same event-bubbling issue as in EntityRow: if any child inside inner becomes focusable (e.g. an icon wrapped in a link), a Space or Enter keydown bubbles up here. e.preventDefault() is called before the child's keyup click fires, hijacking the interaction and triggering the outer onClick instead.
Add a e.target === e.currentTarget check before the key guard to scope the handler strictly to the card's own focus.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/MetricCard.tsx
Line: 51
Comment:
**`onKeyDown` fires on events bubbled from child elements**
Same event-bubbling issue as in `EntityRow`: if any child inside `inner` becomes focusable (e.g. an icon wrapped in a link), a Space or Enter keydown bubbles up here. `e.preventDefault()` is called before the child's `keyup` click fires, hijacking the interaction and triggering the outer `onClick` instead.
Add a `e.target === e.currentTarget` check before the key guard to scope the handler strictly to the card's own focus.
How can I resolve this? If you propose a fix, please make it concise.| <div | ||
| className={classes} | ||
| onClick={onClick} | ||
| role={isClickable ? "button" : undefined} | ||
| tabIndex={isClickable ? 0 : undefined} | ||
| onKeyDown={isClickable ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick?.(); } } : undefined} | ||
| > | ||
| {content} | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Missing focus-visible ring — inconsistent with design system
All other interactive elements in this project (see ui/src/components/ui/button.tsx) pair outline-none with an explicit focus-visible:ring-[3px] class so the focus indicator matches the design system. Without it, this <div> falls back to the browser's default outline, which is visually inconsistent and can appear differently (or be suppressed) across browsers.
Consider adding the same focus-visible:outline-none focus-visible:ring-[3px] classes used by <Button> — conditioned on isClickable — alongside the classes variable.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/EntityRow.tsx
Line: 65-74
Comment:
**Missing focus-visible ring — inconsistent with design system**
All other interactive elements in this project (see `ui/src/components/ui/button.tsx`) pair `outline-none` with an explicit `focus-visible:ring-[3px]` class so the focus indicator matches the design system. Without it, this `<div>` falls back to the browser's default outline, which is visually inconsistent and can appear differently (or be suppressed) across browsers.
Consider adding the same `focus-visible:outline-none focus-visible:ring-[3px]` classes used by `<Button>` — conditioned on `isClickable` — alongside the `classes` variable.
How can I resolve this? If you propose a fix, please make it concise.| <div | ||
| className="h-full" | ||
| role="button" | ||
| tabIndex={0} | ||
| onClick={onClick} | ||
| onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } }} | ||
| > | ||
| {inner} | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Missing focus-visible ring — inconsistent with design system
Like the EntityRow change, this wrapper div will show the raw browser outline on focus rather than the project's design-system ring style (see ui/src/components/ui/button.tsx for the pattern). For a PR claiming WCAG 2.1 AA compliance, having a non-standard, browser-default focus ring is worth addressing to ensure consistent, predictable visual feedback across the UI.
Add focus-visible:outline-none focus-visible:ring-[3px] (plus an appropriate rounded-lg so the ring follows the card shape) to the wrapper className.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/MetricCard.tsx
Line: 46-55
Comment:
**Missing focus-visible ring — inconsistent with design system**
Like the `EntityRow` change, this wrapper div will show the raw browser outline on focus rather than the project's design-system ring style (see `ui/src/components/ui/button.tsx` for the pattern). For a PR claiming WCAG 2.1 AA compliance, having a non-standard, browser-default focus ring is worth addressing to ensure consistent, predictable visual feedback across the UI.
Add `focus-visible:outline-none focus-visible:ring-[3px]` (plus an appropriate `rounded-lg` so the ring follows the card shape) to the wrapper `className`.
How can I resolve this? If you propose a fix, please make it concise.…-tabs refactor(ui): split AgentDetail.tsx into tab-based modules
fix(ui): allow custom model input for adapters
A queued heartbeat run that never started (startedAt IS NULL) is now treated as stale in isTerminalOrMissingHeartbeatRun(), allowing checkout to supersede its executionRunId lock. Also added a final fallback in checkout() that clears stale executionRunId locks regardless of issue status, preventing permanent 409 conflicts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (QUA-165)
- aria-label={expanded ? "Collapse" : "Expand"} for screen reader context
- aria-expanded={expanded} to announce state changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e (QUA-166, QUA-167) - Projects page: add status filter bar (All / Backlog / Planned / In Progress / Completed) with count badges and localStorage persistence (key: projects:statusFilter) - ApprovalCard: show Loader2 spinner + "Approving..."/"Rejecting..." label while mutation is pending, so users get clear feedback on action progress Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…icCard onClick variants (QUA-169)
a6b1005 to
d9008cf
Compare
Summary
Fixes keyboard accessibility for two widely-used reusable components:
EntityRow (click-only mode, no
toprop):role="button"+tabIndex={0}+onKeyDown(Enter/Space)MetricCard (onClick variant):
role="button"+tabIndex={0}+onKeyDown(Enter/Space)Related Issue
QUA-169: [A11Y] EntityRow and MetricCard onClick variants not keyboard-accessible
Acceptance Criteria
Impact
These are reusable components used across many pages — fix propagates broadly.
Test plan
🤖 PM-coordinated PR (QUA-169)