Skip to content

🎨 Palette: Standard-Compliant Focus Restoration for Overlays - #189

Merged
OnlineChef merged 8 commits into
mainfrom
palette-focus-restoration-13136720007452404243
Aug 1, 2026
Merged

🎨 Palette: Standard-Compliant Focus Restoration for Overlays#189
OnlineChef merged 8 commits into
mainfrom
palette-focus-restoration-13136720007452404243

Conversation

@SoulWayy

@SoulWayy WaySoul (SoulWayy) commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

💡 What:
Implemented standard-compliant keyboard focus restoration for the dashboard's sliding panels and modal dialogs. Specifically:

  • Captures document.activeElement as detailInvoker when openDetail() is called.
  • Captures document.activeElement as credInvoker when openCredentialsModal() is called.
  • Restores keyboard focus back to the captured trigger element inside closeDetail() and closeCredentialsModal() (as well as when saving credentials succeeds).

🎯 Why:
Closing modals or sliding panels without focus restoration disrupts keyboard navigation by forcing screen readers and keyboard users to restart navigation from the top of the HTML document. Restoring focus preserves the active context and keeps navigation extremely natural.

📸 Before/After:

  • Before: Closing the details panel or the connection credentials modal dropped keyboard focus completely, requiring users to tab through the entire page layout from scratch.
  • After: Focus is immediately and clearly restored back to the clicked catalog card or button that triggered the action, highlighted by a teal border focus ring.

Accessibility:
Fully implements WCAG 2.1 focus restoration guidelines (Standard WAI-ARIA Dialog focus management) to support keyboard-only operators and screen reader users.


PR created automatically by Jules for task 13136720007452404243 started by WaySoul (@SoulWayy)

Greptile Summary

This change adds focus restoration for the dashboard detail panel and credentials modal. Two keyboard flows remain broken in src/kater/web/dashboard.py: saving credentials restores focus to Connect and then removes that control during refresh, and switching servers in an open detail panel returns focus to the originally opened server instead of the current server. Both failures were reproduced in Chromium.

Confidence Score: 4/5

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for a posted P1 finding, including a live browser reproduction source and observed output.
  • T-Rex provided a Chromium harness that reproduces the exact A then B focus sequence with open-then-close behavior, and captured the successful focus-reproduction output.
  • T-Rex bundled a multi-media proof for the credential flow, including Python sources, logs, and videos, plus screenshots of the pre- and post-save states.
  • T-Rex validated the credential-close flow against the dashboard contract, documenting focus restoration, the refresh sequence, and UI state changes through instrumentation and traces.
  • T-Rex added further contract validation around the detail-panel close flow, linking final dashboard state screenshots and relevant code references.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. src/kater/web/dashboard.py, line 2572-2579 (link)

    P1 Credential refresh removes the restored focus target

    A successful credential save calls closeCredentialsModal() before refreshing the catalog and reopening the detail panel. The modal briefly restores focus to the Connect trigger, but the refreshed configured state hides that trigger, leaving browser focus on BODY. Restore focus after the refreshed state is known, targeting a stable visible detail-panel element or action.

    Artifacts

    PR-189 live browser reproduction source

    • Playwright source opens the rendered dashboard, performs a real credential save, records refresh requests, and captures immediate and final focus states; takeaway: it directly exercises the reported sequence.

    PR-189 live browser reproduction observed output

    • Executed reproduction output records focus on the Connect trigger immediately after modal close and focus on BODY after successful catalog/detail refresh; takeaway: focus is restored first and then lost.

    Evidence from the check

    • Playwright source opens the rendered dashboard, performs a real credential save, records refresh requests, and captures immediate and final focus states; takeaway: it directly exercises the reported sequence.

    Command output from the check

    • Executed reproduction output records focus on the Connect trigger immediately after modal close and focus on BODY after successful catalog/detail refresh; takeaway: focus is restored first and then lost.

    ▶ Recording of the check

    • Rendered Chromium capture shows the GitHub credential modal opened from the visible Connect trigger before saving; takeaway: the original focus target exists before the state-changing refresh.

    Credential modal before the successful save screenshot

    • Screenshot from the rendered dashboard before saving shows the credentials modal reached from the Connect trigger; takeaway: the trigger is available before the refresh.

    ▶ Recording of the check

    • Rendered Chromium capture shows the successful credential save reaching the ready state after refresh, where the Connect trigger is hidden; takeaway: the restored focus target no longer remains available.

    Dashboard after successful credential save and refresh screenshot

    • Screenshot from the rendered dashboard after saving shows the configured ready state with the Connect trigger hidden; takeaway: the restored focus target is removed from the visible UI.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/kater/web/dashboard.py
    Line: 2572-2579
    
    Comment:
    **Credential refresh removes the restored focus target**
    
    A successful credential save calls `closeCredentialsModal()` before refreshing the catalog and reopening the detail panel. The modal briefly restores focus to the Connect trigger, but the refreshed configured state hides that trigger, leaving browser focus on `BODY`. Restore focus after the refreshed state is known, targeting a stable visible detail-panel element or action.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

  2. General comment

    P1 Closing a reselected detail panel returns focus to the original server

    • Bug
      • After opening server A, selecting server B without closing the detail panel, and closing it, Chromium focused server A even though the displayed and most recently selected server was B.
    • Cause
      • openDetail() assigns detailInvoker only when it is null (lines 2370–2373), so selecting B does not replace A. closeDetail() restores the retained A element (lines 2459–2464).
    • Fix
      • Refresh detailInvoker when a different server is selected while the detail panel is open, or explicitly maintain the intended return target as the currently selected server row.

    T-Rex Ran code and verified through T-Rex

Fix All in Cursor Fix All in Codex Fix All in Claude Code Fix All in Conductor

Prompt To Fix All With AI
### Issue 1
src/kater/web/dashboard.py:2572-2579
**Credential refresh removes the restored focus target**

A successful credential save calls `closeCredentialsModal()` before refreshing the catalog and reopening the detail panel. The modal briefly restores focus to the Connect trigger, but the refreshed configured state hides that trigger, leaving browser focus on `BODY`. Restore focus after the refreshed state is known, targeting a stable visible detail-panel element or action.

### Issue 2
src/kater/web/dashboard.py:2370-2373
**Detail panel retains the original invoker**

`detailInvoker` is only captured when it is empty. If a user opens server A, selects server B while the panel remains open, and closes the panel, focus returns to A even though B is the displayed and most recently selected server. Update the return target when the selected server changes, or explicitly restore focus to the current server row.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "🎨 Palette: Standard-Compliant Focus Res..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

- Captures document.activeElement upon opening Server Details Panel or Connection Credentials Modal.
- Restores focus to the triggering element when the overlay is closed, dismissed, or saved.
- Prevents keyboard users and screen readers from losing focus state and resetting back to the top of the document.
- Fully tested using NodeJS DOM simulation script in pytest.

Co-authored-by: SoulWayy <285978010+SoulWayy@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@OnlineChef, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bb93106c-0e8a-4c62-af2d-e518ff7f84bd

📥 Commits

Reviewing files that changed from the base of the PR and between 40a6658 and 831ba67.

📒 Files selected for processing (2)
  • src/kater/web/dashboard.py
  • tests/test_dashboard.py
📝 Walkthrough

Summary by CodeRabbit

  • Accessibility Improvements
    • Restored keyboard focus to the element that opened the detail panel or credentials modal after the overlay closes, is cancelled, or completes.
    • Preserved and updated focus handling when switching between different overlay triggers or refreshing details.
    • Safely handles cases where the original trigger is no longer available or has been removed from the page.
    • Added more reliable focus restoration for keyboard and assistive technology users.

Walkthrough

The dashboard captures the active element when opening the detail panel or credentials modal. It restores focus after closing when the element remains connected and focusable. Refreshes preserve the stored invoker. Tests cover these behaviors.

Changes

Overlay focus restoration

Layer / File(s) Summary
Overlay invoker tracking and focus restoration
src/kater/web/dashboard.py
The detail panel and credentials modal store external active elements. They clear stored invokers and restore focus when possible.
Refresh-aware detail integration
src/kater/web/dashboard.py
Credential saves, toggles, WebSocket updates, and server-detail openings pass refresh state without replacing the detail invoker.
Focus restoration harness and tests
tests/test_dashboard.py
DOM and Node tests cover capture, reselection, refresh preservation, clearing, connected elements, detached elements, and body focus.
Overlay focus guidance
.Jules/palette.md
The learning entry documents overlay focus capture and restoration.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Trigger
  participant Dashboard
  participant Overlay
  participant RefreshSource
  Trigger->>Dashboard: Open detail panel or credentials modal
  Dashboard->>Overlay: Store trigger and show overlay
  RefreshSource->>Dashboard: Refresh open detail view
  Dashboard->>Overlay: Preserve stored trigger
  Trigger->>Dashboard: Close overlay
  Dashboard->>Trigger: Restore focus when connected
Loading

Possibly related PRs

Suggested reviewers: onlinechef

Poem

A rabbit opens panels with care,
Then stores the trigger waiting there.
Refreshes pass without a shove,
Closing brings the focus back with love.
Detached buttons stay untouched.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: standard-compliant focus restoration for overlays.
Description check ✅ Passed The description directly explains the focus restoration changes for detail panels and credential modals.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch palette-focus-restoration-13136720007452404243

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.Jules/palette.md:
- Around line 32-34: Vertaal de leerregel onder “2026-07-24 -
[Standard-Compliant Focus Restoration for Overlays]” naar het Nederlands en voeg
een lege regel direct na de heading toe. Behoud de bestaande inhoud en
structuur, inclusief de secties “Learning” en “Action”, maar formuleer alle
tekst in het Nederlands.

In `@tests/test_dashboard.py`:
- Around line 484-564: Breid _FOCUS_HARNESS en
test_focus_restoration_behavior_node uit met uitvoerbare dekking voor
openCredentialsModal en closeCredentialsModal. Gebruik afzonderlijke focusbare
invokers en verifieer dat credInvoker bij openen wordt vastgelegd, bij sluiten
wordt gewist en focus alleen wordt hersteld voor verbonden elementen, inclusief
een BODY-invoker; dek ook een niet-verbonden invoker af volgens het bestaande
DOM-gedrag.
- Around line 525-533: Remove the trivial inline comments “Test openDetail” and
“Test closeDetail” from the JavaScript harness, leaving the surrounding
openDetail, closeDetail, and assertion-related statements unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95b0ba14-63e9-4f7d-9cdb-da358116c923

📥 Commits

Reviewing files that changed from the base of the PR and between c8cf146 and 2979dc6.

📒 Files selected for processing (4)
  • .Jules/palette.md
  • .agents/eval/scorecard.json
  • src/kater/web/dashboard.py
  • tests/test_dashboard.py

Comment thread .Jules/palette.md Outdated
Comment thread tests/test_dashboard.py
Comment thread tests/test_dashboard.py Outdated
WaySoul (SoulWayy) and others added 2 commits August 1, 2026 15:03
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Comment thread src/kater/web/dashboard.py Outdated
WaySoul (SoulWayy) and others added 3 commits August 1, 2026 15:12
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
- Captures document.activeElement upon opening Server Details Panel or Connection Credentials Modal.
- Restores focus to the triggering element when the overlay is closed, dismissed, or saved.
- Prevents keyboard users and screen readers from losing focus state and resetting back to the top of the document.
- Fully tested using NodeJS DOM simulation script in pytest.

Co-authored-by: SoulWayy <285978010+SoulWayy@users.noreply.github.com>
Re-apply detail-panel invoker retargeting, credential-modal Node harness
coverage, and Dutch palette learning text that tip 56ecd1d undid.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread tests/test_dashboard.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/kater/web/dashboard.py`:
- Around line 2373-2376: Prevent handleWSMessage status refreshes from
overwriting detailInvoker while the detail panel is open. Update
openServerDetail/openDetail so explicit server-selection paths pass the selected
row or card before asynchronous fetching, and only those paths set the invoker;
leave detailInvoker unchanged during background refreshes. Add a Node test
covering an open detail view with focus outside the panel during a WebSocket
refresh, verifying closeDetail restores focus to the last selected server row.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9f393ee6-accf-4be3-9815-7cac1c1d7948

📥 Commits

Reviewing files that changed from the base of the PR and between 2979dc6 and 1877260.

📒 Files selected for processing (3)
  • .Jules/palette.md
  • src/kater/web/dashboard.py
  • tests/test_dashboard.py

Comment thread src/kater/web/dashboard.py Outdated
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_dashboard.py (1)

563-588: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test de refresh-contracten via echte callers.

Deze test voert alleen openDetail() en closeDetail() uit. Een regressie in openServerDetail() die refresh niet doorgeeft, of in de succesvolle saveCredentials()-refresh, blijft daardoor ongedetecteerd. Voeg een Node-case toe met een stub voor api() en test minstens openServerDetail() en de credential-save-flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_dashboard.py` around lines 563 - 588, Breid
test_focus_restoration_behavior_node uit met een Node-case die api() stubt en de
echte callers openServerDetail() en de succesvolle saveCredentials()-flow
uitvoert. Controleer daarbij dat beide refresh-aanroepen de verwachte
refresh-parameter doorgeven, zodat regressies in deze callers naast openDetail()
en closeDetail() worden gedetecteerd.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/kater/web/dashboard.py`:
- Around line 2376-2381: Capture the selected row/card before the asynchronous
detail fetch and pass that original invoker into openDetail(), rather than
reading document.activeElement afterward; update src/kater/web/dashboard.py
lines 2376-2381. Capture the credential invoker before promptCredentials() and
pass it into openCredentialsModal() at src/kater/web/dashboard.py lines
2501-2505. Update tests/test_dashboard.py lines 540-558 to use a delayed
response, move focus during the fetch, and assert that the original invoker is
retained.
- Around line 3575-3578: Update openServerDetail to track request generations or
cancel previous requests, and apply the fetched data only when the request is
still current. For refresh=true, additionally require the detail panel to remain
open and selectedNode.name to still match name before calling openDetail(data,
refresh).

In `@tests/test_dashboard.py`:
- Around line 540-558: Maak de regressietest rond openDetail asynchroon met een
deferred promise die de API-fetchrespons controleert. Laat openDetail starten,
verplaats de focus naar commandBar vóór het vrijgeven van de deferred response,
wacht daarna op afronding en controleer dat detailInvoker de oorspronkelijk
geselecteerde rij cardB blijft; behoud de bestaande close- en focusasserties.

---

Outside diff comments:
In `@tests/test_dashboard.py`:
- Around line 563-588: Breid test_focus_restoration_behavior_node uit met een
Node-case die api() stubt en de echte callers openServerDetail() en de
succesvolle saveCredentials()-flow uitvoert. Controleer daarbij dat beide
refresh-aanroepen de verwachte refresh-parameter doorgeven, zodat regressies in
deze callers naast openDetail() en closeDetail() worden gedetecteerd.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2e3c3186-3678-46bf-9902-452a8cd6528f

📥 Commits

Reviewing files that changed from the base of the PR and between 1877260 and 40a6658.

📒 Files selected for processing (2)
  • src/kater/web/dashboard.py
  • tests/test_dashboard.py

Comment thread src/kater/web/dashboard.py Outdated
Comment thread src/kater/web/dashboard.py Outdated
Comment thread tests/test_dashboard.py Outdated
Pass pre-fetch invokers into openDetail/openCredentialsModal, drop stale
openServerDetail responses via a generation counter, and cover the async
refresh contract in the Node harness so CodeRabbit findings on tip 40a6658
stay closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@OnlineChef
OnlineChef merged commit a40d143 into main Aug 1, 2026
23 checks passed
@OnlineChef
OnlineChef deleted the palette-focus-restoration-13136720007452404243 branch August 1, 2026 20:42
OnlineChef added a commit that referenced this pull request Aug 1, 2026
Keep collision-safe credential modal label pairing from #167 and focus
restoration notes from #189.
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.

2 participants