| β¦ | β¦ | @@ -3196,6 +3212,10 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve | |
| 3216 | + | tabIndex={-1} | |
| 3217 | + | data-sidebar-row="settled:more" | |
| 3218 | + | data-sidebar-parent="shelf:settled" | |
| 3199 | 3219 | disabled={olderSettledShelf.loading} | |
π‘ Medium components/Sidebar.tsx:3219
While olderSettledShelf.loading is true, settled:more remains a roving-focus candidate, but its native disabled attribute prevents .focus() from moving focus to it. Pressing End or Down therefore leaves focus on the previous row after its tabIndex is set to -1, leaving the tree without a usable Tab stop. Exclude disabled rows from installSidebarTree's candidates, or keep this row focusable with aria-disabled and guard activation.
π Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/src/components/Sidebar.tsx around line 3219: |
| 2 | |
| 3 | While `olderSettledShelf.loading` is true, `settled:more` remains a roving-focus candidate, but its native `disabled` attribute prevents `.focus()` from moving focus to it. Pressing End or Down therefore leaves focus on the previous row after its `tabIndex` is set to `-1`, leaving the tree without a usable Tab stop. Exclude disabled rows from `installSidebarTree`'s candidates, or keep this row focusable with `aria-disabled` and guard activation. |
Note
π€ Claude Opus 5.5 responding on behalf of Theo
Fixed in e31a531554. installSidebarTree now leaves :disabled rows out of the candidates and watches disabled, so the row comes back when loading ends. Covered by a new sidebarTree browser test.
| β¦ | β¦ | @@ -1752,6 +1756,7 @@ export default function ThreadTerminalDrawer({ | |
| 1752 | 1756 | return ( | |
| 1753 | 1757 | <aside | |
| 1754 | 1758 | ref={drawerRef} | |
| 1759 | + | {...(!isPanel ? terminalArea : {})} | |
π High components/ThreadTerminalDrawer.tsx:1759
F6 can focus controls in the hidden terminal drawer instead of the next visible workspace area because this aside remains registered as a workspace area when visible is false. Include visible in the condition that spreads terminalArea in both render branches.
π Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/src/components/ThreadTerminalDrawer.tsx around line 1759: |
| 2 | |
| 3 | F6 can focus controls in the hidden terminal drawer instead of the next visible workspace area because this aside remains registered as a workspace area when `visible` is `false`. Include `visible` in the condition that spreads `terminalArea` in both render branches. |
Note
π€ Claude Opus 5.5 responding on behalf of Theo
Fixed in e31a531554. Confirmed: a hidden drawer collapses with grid-rows-[0fr] but keeps its rects, so it passed the visibility check. The drawer now registers as an area only when visible is true, in both render branches.
| β¦ | β¦ | @@ -0,0 +1,261 @@ | |
| 84 | + | !control.matches('input, textarea, select, [contenteditable="true"]') && | |
| 85 | + | !rowActions.includes(control) | |
| 86 | + | ) | |
| 87 | + | control.tabIndex = -1; | |
π‘ Medium lib/sidebarTree.ts:82
After a thread is settled or snoozed, keyboard users cannot tab to the visible Undo button because this pass sets tabIndex = -1 on every non-input control in the tree. The Undo button is outside a row and is not associated through data-sidebar-action-for, so F2 cannot reach it either; restrict suppression to controls inside a row or explicitly associated with one.
| 1 | if ( |
| 2 | + (control.closest(ROW) || control.dataset.sidebarActionFor) && |
| 3 | !control.matches(ROW) && |
| 4 | !control.matches('input, textarea, select, [contenteditable="true"]') && |
| 5 | !rowActions.includes(control) |
| 6 | ) |
| 7 | control.tabIndex = -1; |
π Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/src/lib/sidebarTree.ts around lines 82-87: |
| 2 | |
| 3 | After a thread is settled or snoozed, keyboard users cannot tab to the visible Undo button because this pass sets `tabIndex = -1` on every non-input control in the tree. The Undo button is outside a row and is not associated through `data-sidebar-action-for`, so F2 cannot reach it either; restrict suppression to controls inside a row or explicitly associated with one. |
Note
π€ Claude Opus 5.5 responding on behalf of Theo
Fixed in e31a531554 with your suggested change. Tab suppression now applies only to controls inside a row or tagged with data-sidebar-action-for, so the Undo notice stays in the Tab order. Covered by the same new sidebarTree test, which fails without the fix.
| β¦ | β¦ | @@ -0,0 +1,222 @@ | |
| 2 | + | ||
| 3 | + | const AREA_SELECTOR = "[data-workspace-area]"; | |
| 4 | + | const FOCUS_BOUNDARY_SELECTOR = | |
| 5 | + | '[role="dialog"], [role="alertdialog"], [aria-modal="true"], dialog[open], [data-keybinding-capture], [data-slot$="-popup"]:not([data-slot="tooltip-popup"]):not([role="tooltip"])'; | |
π High lib/workspaceFocus.ts:5
When a snapshot shortcut button is visible but not recording, move() returns false, so F6/Shift+F6 cannot move focusβeven from unrelated Settings controls. The button carries data-keybinding-capture regardless of recording state, and this selector treats its mere presence as a focus boundary; restrict the boundary to an actively recording capture target.
π Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/src/lib/workspaceFocus.ts around line 5: |
| 2 | |
| 3 | When a snapshot shortcut button is visible but not recording, `move()` returns `false`, so F6/Shift+F6 cannot move focusβeven from unrelated Settings controls. The button carries `data-keybinding-capture` regardless of recording state, and this selector treats its mere presence as a focus boundary; restrict the boundary to an actively recording capture target. |
Note
π€ Claude Opus 5.5 responding on behalf of Theo
Fixed in e31a531554. Confirmed: the snapshot and keybinding recorders keep data-keybinding-capture even when they are not recording, so any Settings page with one blocked F6. I removed it from the boundary selector. WorkspaceNavigation already skips key events whose target is inside a capture element, so recording still gets F6. A new workspaceFocus browser test fails without the fix.