Skip to content

Phase 5: Accessibility — ARIA landmarks, keyboard support, skip-nav#223

Open
ComBba wants to merge 2 commits intophase-4/production-infrafrom
phase-5/a11y-polish
Open

Phase 5: Accessibility — ARIA landmarks, keyboard support, skip-nav#223
ComBba wants to merge 2 commits intophase-4/production-infrafrom
phase-5/a11y-polish

Conversation

@ComBba
Copy link
Copy Markdown
Contributor

@ComBba ComBba commented Apr 7, 2026

Summary

  1. layout.tsx: skip-to-main-content link for keyboard/screen reader users
  2. zero-prompt-landing.tsx: <label> for YouTube input; replace alert() with disabled "Admin Only" button
  3. kanban-board.tsx: role="region" + aria-label on board container
  4. kanban-column.tsx: role="group" + aria-label with column name and count
  5. action-feed.tsx: role="button", tabIndex, aria-pressed, keyboard handlers on filter badges
  6. demo/page.tsx: aria-hidden="true" on decorative cursor

Changes

  • 6 files changed, +26 -8 lines

Test plan

  • 31 web tests pass
  • Web build succeeds (7 routes)
  • Keyboard navigation: filter badges respond to Enter/Space
  • Screen reader: skip-nav, Kanban columns announced with name+count

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 849da483-32b7-4e83-ac10-b1199d229786

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-5/a11y-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request focuses on enhancing the accessibility of the application by introducing a 'Skip to main content' link, defining semantic landmarks like

and ARIA regions, and improving the labeling of interactive elements. The review feedback highlights opportunities to further refine these improvements by removing redundant ARIA labels, ensuring disabled buttons remain focusable for keyboard users to access descriptive tooltips, and utilizing native button elements via the asChild pattern to simplify keyboard interaction logic.

Comment on lines 156 to 164
<Input
id="youtube-url-input"
ref={inputRef}
value={youtubeUrl}
onChange={(e) => onYoutubeUrlChange(e.target.value)}
placeholder="https://www.youtube.com/watch?v=..."
className="font-mono text-sm"
aria-label="YouTube video URL"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The aria-label is redundant here because the input is already correctly associated with a visible (though visually hidden via sr-only) label on line 155 using the htmlFor attribute. Redundant labeling can lead to double announcements in some screen readers.

Suggested change
<Input
id="youtube-url-input"
ref={inputRef}
value={youtubeUrl}
onChange={(e) => onYoutubeUrlChange(e.target.value)}
placeholder="https://www.youtube.com/watch?v=..."
className="font-mono text-sm"
aria-label="YouTube video URL"
/>
<Input
id="youtube-url-input"
ref={inputRef}
value={youtubeUrl}
onChange={(e) => onYoutubeUrlChange(e.target.value)}
placeholder="https://www.youtube.com/watch?v=..."
className="font-mono text-sm"
/>

Comment on lines 165 to 172
<Button
className="shrink-0 gap-2 bg-gradient-to-r from-red-600 to-red-500 hover:from-red-500 hover:to-red-400"
onClick={() => alert("This feature is restricted to admin users and authorized IPs only.")}
disabled
className="shrink-0 gap-2 opacity-50 cursor-not-allowed"
title="Admin access required — use Zero-Prompt Start for the full pipeline"
>
<Lock className="w-4 h-4" />
Start
Admin Only
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the disabled attribute removes the button from the tab order, which prevents keyboard users from focusing it and discovering the information provided in the title attribute. Additionally, title is not consistently accessible across different assistive technologies and devices. Consider using aria-disabled="true" to keep the button focusable while indicating its state, and ensure the "Admin Only" restriction is communicated clearly (e.g., via a proper tooltip component or visible text).

                <Button
                  type="button"
                  aria-disabled="true"
                  className="shrink-0 gap-2 opacity-50 cursor-not-allowed"
                  title="Admin access required — use Zero-Prompt Start for the full pipeline"
                >
                  <Lock className="w-4 h-4" />
                  Admin Only
                </Button>

Comment on lines +76 to 86
<Badge
role="button"
tabIndex={0}
aria-pressed={typeFilter === null}
variant={typeFilter === null ? "default" : "outline"}
className="cursor-pointer text-[10px] whitespace-nowrap"
onClick={() => setTypeFilter(null)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setTypeFilter(null); } }}
>
All
</Badge>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To adhere to the first rule of ARIA, it is better to use a native <button> element instead of a span with role="button". The Badge component supports the asChild pattern, which allows you to wrap a native button. This provides built-in keyboard support (Enter and Space) and native focus management without needing manual tabIndex or onKeyDown handlers.

            <Badge
              asChild
              variant={typeFilter === null ? "default" : "outline"}
              className="cursor-pointer text-[10px] whitespace-nowrap"
            >
              <button
                type="button"
                aria-pressed={typeFilter === null}
                onClick={() => setTypeFilter(null)}
              >
                All
              </button>
            </Badge>

Comment on lines +88 to 99
<Badge
key={type}
role="button"
tabIndex={0}
aria-pressed={typeFilter === type}
variant={typeFilter === type ? "default" : "outline"}
className="cursor-pointer text-[10px] whitespace-nowrap"
onClick={() => setTypeFilter(type === typeFilter ? null : type)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setTypeFilter(type === typeFilter ? null : type); } }}
>
{type}
</Badge>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the "All" badge, using asChild with a native <button> is preferred for better accessibility and to avoid manual keyboard event handling.

              <Badge
                key={type}
                asChild
                variant={typeFilter === type ? "default" : "outline"}
                className="cursor-pointer text-[10px] whitespace-nowrap"
              >
                <button
                  type="button"
                  aria-pressed={typeFilter === type}
                  onClick={() => setTypeFilter(type === typeFilter ? null : type)}
                >
                  {type}
                </button>
              </Badge>

@ComBba ComBba force-pushed the phase-5/a11y-polish branch from cbbfba5 to efc3ff5 Compare April 7, 2026 03:42
1. layout.tsx: add skip-to-main-content link for keyboard/screen reader users
2. zero-prompt-landing.tsx: add <label> for YouTube URL input; replace
   alert() Start button with disabled "Admin Only" state + tooltip
3. kanban-board.tsx: add role="region" + aria-label on board container
4. kanban-column.tsx: add role="group" + aria-label with column name
   and item count for screen readers
5. action-feed.tsx: add role="button", tabIndex, aria-pressed, and
   keyboard handlers (Enter/Space) to all filter badges
6. demo/page.tsx: add aria-hidden="true" to decorative cursor animation

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@ComBba ComBba force-pushed the phase-5/a11y-polish branch from efc3ff5 to 3f996ba Compare April 7, 2026 03:45
@ComBba ComBba force-pushed the phase-4/production-infra branch from e09da87 to 3495679 Compare April 7, 2026 03:45
New Frontend Tests (30 unit + 9 E2E):
- use-demo-zero-prompt.test.ts (11): session lifecycle, card mutations, timer cleanup
- use-dashboard.test.ts (6): polling, score distribution, verdict breakdown
- dashboard-api.test.ts (13): health check, stats, results, deployments with auth
- navigation.spec.ts (5): landing, demo, dashboard page loads + navigation
- demo-flow.spec.ts (4): auto-type, kanban board, cards, completion

New Backend Tests (52):
- test_zp_store.py (23): column allowlist validation, JSONB serialization, row parsing
- test_connection_pool.py (15): pool creation, env overrides, lock safety, close behavior
- test_pipeline_runtime_score.py (14): score=0 preserved, None fallback, verdict mapping

Fixed Stale Tests (6):
- test_runtime_config: pool defaults 1/1 → 2/10
- test_server_coverage: health endpoint {"status":"ok"} only
- test_tools_integration (4): GENERATED_APP_DATABASE_URL/INFERENCE_KEY env vars
- digitalocean.py: apply URL scheme conversion to generated_db_url

E2E Infrastructure:
- Playwright config (chromium, webServer: npm run dev, retries: 1)
- vitest.config.ts: exclude e2e/ from unit test runs

Totals: 8 web test files (61 tests), 85 agent test files (~1438 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant