Skip to content

feat(browser): redesign to the design bar with a collapsible sidebar (#66)#1227

Merged
jaylfc merged 1 commit into
devfrom
feat/browser-redesign-collapsible-sidebar
Jun 20, 2026
Merged

feat(browser): redesign to the design bar with a collapsible sidebar (#66)#1227
jaylfc merged 1 commit into
devfrom
feat/browser-redesign-collapsible-sidebar

Conversation

@jaylfc

@jaylfc jaylfc commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Collapsible left sidebar (BrowserSidebar.tsx + browser-ui-store.ts): 220px expanded with tab titles, favicons, and an active-indicator rail; collapses to 44px icon-only. Smooth CSS width transition that respects prefers-reduced-motion and data-perf=reduced. State is a lightweight zustand store (not localStorage) so it persists within a session without polluting the session-persistence snapshot.
  • Polished empty/connecting states (BrowserEmptyState.tsx): new-tab shows a Globe icon + hint instead of a blank white void; the Neko connecting state shows an animated MonitorPlay icon with loading dots. DiscardedPlaceholder refined to match the design bar's type hierarchy.
  • TabStrip tightened: reduced gap, sharper close button, active tab border merges cleanly into the content area via a surface-inset shadow. New-tab button gets a focus-visible ring.
  • Chrome toolbar gap tightened from 2 to 1.5 for a denser, more intentional feel consistent with the images/store toolbars.
  • tokens.css: sidebar collapse animation keyframes and browser-stream-pulse added alongside the existing taos-shimmer/taos-card-enter animations.

Behavior unchanged

LiveBrowserView iframe and neko_url logic is untouched. Mobile layout is untouched. All sessions/tabs/escalate/mode-toggle behavior is preserved.

Test plan

  • vitest run -- all 10 BrowserApp.test.tsx tests pass
  • TypeScript: no new errors introduced beyond pre-existing worktree environment errors (no react node_modules in worktree)
  • Visual smoke: sidebar collapses/expands, tab clicks work, new-tab placeholder shows on fresh tab, connecting state shows when starting a Neko session
  • Dark and light themes -- token-based throughout, no hardcoded colors
  • prefers-reduced-motion -- sidebar collapse should be instant

Closes #66

Summary by CodeRabbit

Release Notes

New Features

  • Introduced a collapsible browser sidebar for quick access and management of open tabs
  • Added visual placeholders for new tabs and browser session initialization states

UI & Styling

  • Optimized toolbar height and spacing for improved visual layout
  • Updated tab strip styling to enhance visual hierarchy and compactness
  • Refined the new tab experience with dedicated empty state interface

…(#66)

Brings the Browser app up to Images Studio / design-bar quality:

- Collapsible left sidebar (BrowserSidebar.tsx) -- expanded 220px shows
  tab titles + favicons with active-indicator rail; collapsed 44px is
  icon-only. Width animates via CSS transition (respects prefers-reduced-motion
  and data-perf=reduced). State lives in a lightweight zustand store
  (browser-ui-store.ts) so it persists within a session without touching
  localStorage or the session-persistence snapshot.

- Polished empty/connecting states (BrowserEmptyState.tsx) replacing the
  plain white void: new-tab shows a Globe + "New tab" prompt; the Neko
  connecting state shows an animated MonitorPlay icon with loading dots.
  DiscardedPlaceholder also refined to match the design bar's type hierarchy.

- TabStrip tightened: reduced gap, smaller close button, tab border now
  uses a surface-inset shadow so active tabs merge cleanly into the
  content area. New-tab button gets a focus-visible ring.

- Chrome toolbar gap tightened to 1.5 (from 2) for a denser, more
  intentional feel consistent with the images/store toolbars.

- Sidebar CSS keyframe animations added to tokens.css alongside the
  existing taos-shimmer / taos-card-enter animations.

All 10 BrowserApp tests pass. LiveBrowserView iframe/neko_url logic
is untouched. Mobile layout is untouched.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a collapsible BrowserSidebar component to the desktop BrowserApp layout, backed by a new Zustand useBrowserUiStore tracking sidebarCollapsed state. Introduces BrowserEmptyState with "connecting" and "new-tab" variants, integrates it into TabRenderer for blank tabs, and adds CSS tokens for sidebar width transitions and a shimmer animation with reduced-motion overrides. TabStrip and Chrome receive minor visual/sizing adjustments.

Changes

BrowserApp Collapsible Sidebar and Empty States

Layer / File(s) Summary
UI store and CSS tokens foundation
desktop/src/stores/browser-ui-store.ts, desktop/src/theme/tokens.css
Defines useBrowserUiStore with sidebarCollapsed boolean, toggleSidebar, and setSidebarCollapsed actions. Adds .browser-sidebar width transition and @keyframes browser-stream-pulse shimmer with reduced-motion overrides for both prefers-reduced-motion and :root[data-perf="reduced"].
BrowserEmptyState component and TabRenderer integration
desktop/src/apps/BrowserApp/BrowserEmptyState.tsx, desktop/src/apps/BrowserApp/TabRenderer.tsx
Creates BrowserEmptyState with "connecting" (animated dots) and "new-tab" (Globe + prompt) variants. TabRenderer introduces isBlankTab and renders the "new-tab" variant instead of a blank iframe; updates DiscardedPlaceholder markup and error overlay styling.
BrowserSidebar component
desktop/src/apps/BrowserApp/BrowserSidebar.tsx
Implements a collapsible <aside> driven by useBrowserUiStore. Splits tabs into pinned/unpinned groups rendered via SidebarTabRow, which shows favicon or Globe fallback, an active indicator bar, and a close button (non-pinned, expanded only). Includes a "New tab" button wired to addTab(windowId).
Desktop layout wiring and visual polish
desktop/src/apps/BrowserApp/BrowserApp.tsx, desktop/src/apps/BrowserApp/TabStrip.tsx, desktop/src/apps/BrowserApp/Chrome.tsx
BrowserApp restructures the desktop render into a flex body with BrowserSidebar on the left and TabRenderer/FindInPage on the right. TabStrip receives compact Tailwind updates across the tablist, new-tab button, TabItem, drag-handle, and close button. Chrome toolbar height reduced from 50px to 48px.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • jaylfc/taOS#313: Modifies BrowserApp.tsx's desktop render layout around BookmarksBar, the same area restructured in this PR to insert BrowserSidebar.
  • jaylfc/taOS#952: Modifies TabRenderer.tsx's TabFrame component directly, the same component extended here to add isBlankTab and BrowserEmptyState placeholder rendering.

Poem

🐰 A sidebar unfolds, tabs neatly in a row,
Pinned ones stay put, new tabs ready to go.
A shimmer pulses gently while sessions ignite,
Collapse the whole panel—just icons in sight.
The globe spins steady when blank pages appear,
The browser shell's tidy, the bunny cheers! 🌐✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title refers to redesigning 'the design bar' but the PR actually redesigns the browser with a collapsible sidebar; the phrasing is confusing and appears to contain a typo or error. Clarify the title to accurately describe the main change, such as: 'feat(browser): add collapsible sidebar redesign' or 'feat(browser): redesign browser with collapsible sidebar'
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/browser-redesign-collapsible-sidebar

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.

Comment on lines +17 to +31
export function BrowserEmptyState({ variant }: BrowserEmptyStateProps) {
if (variant === "connecting") {
return (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-4 bg-shell-bg-deep select-none">
<span
className="browser-stream-pulse flex h-[52px] w-[52px] items-center justify-center rounded-2xl bg-shell-surface border border-shell-border text-shell-text-tertiary"
aria-hidden="true"
>
<MonitorPlay size={24} />
</span>
<div className="flex flex-col items-center gap-1 text-center">
<p className="text-[13px] font-semibold text-shell-text">
Starting full browser session
</p>
<p className="text-[11.5px] text-shell-text-tertiary">

@gitar-bot gitar-bot Bot Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: "connecting" empty-state variant is never rendered (dead code)

BrowserEmptyState supports a "connecting" variant (animated MonitorPlay + loading dots), and the PR description states "the Neko connecting state shows an animated MonitorPlay icon with loading dots." However, the only call site renders variant="new-tab" (TabRenderer.tsx:508). When tab.liveSession is set, TabRenderer renders <LiveBrowserView> directly (lines 294-307) without ever showing the connecting placeholder. As a result the connecting branch is dead code and the advertised connecting UX is not actually wired up.

Either wire the connecting state in (e.g. render <BrowserEmptyState variant="connecting" /> while the Neko stream is still establishing, before/over LiveBrowserView), or drop the unused variant to avoid misleading dead code.

Render the connecting placeholder until the stream is connected (adjust the readiness flag to match LiveBrowserView's actual API).:

// In TabRenderer, while a live session is starting, overlay the connecting state.
if (isActive && tab.liveSession) {
  return (
    <div key={tab.id} style={{ position: "absolute", inset: 0 }} data-window-tab={tab.id}>
      <LiveBrowserView
        nekoUrl={tab.liveSession.nekoUrl}
        streamToken={tab.liveSession.streamToken}
      />
      {!tab.liveSession.connected && <BrowserEmptyState variant="connecting" />}
    </div>
  );
}

Was this helpful? React with 👍 / 👎

Comment on lines +36 to +39
<aside
aria-label="Tabs sidebar"
data-collapsed={collapsed ? "true" : "false"}
className="browser-sidebar flex flex-col flex-none bg-shell-bg-deep border-r border-shell-border overflow-hidden"

@gitar-bot gitar-bot Bot Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Sidebar rows use role=option without listbox parent or keyboard support

SidebarTabRow renders a <div role="option" aria-selected={...}> with an onClick activation, but the containing <aside> is not a role="listbox" (it is a plain landmark). role="option" is only valid inside a listbox/combobox, so screen readers will not announce these rows correctly. Additionally the row is a non-focusable <div> with no tabIndex and no onKeyDown, so keyboard users cannot activate a tab from the sidebar (the close button is explicitly tabIndex={-1}). Consider using the existing role="tablist"/role="tab" pattern (as TabStrip does) or making rows focusable buttons with keyboard handlers, and giving the scroll container the matching container role.

Add focusability and keyboard activation; wrap rows in a role="listbox" container.:

// Make the row a real button/focusable element with keyboard activation,
// and give the list container role="listbox" (or use tablist/tab).
<div
  role="option"
  tabIndex={0}
  aria-selected={isActive}
  onClick={onActivate}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onActivate(); }
  }}
  ...
>

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

Note

Your trial team has used its Gitar budget, so automatic reviews are paused. Upgrade now to unlock full capacity. Comment "Gitar review" to trigger a review manually.
Learn more about usage limits

Code Review 👍 Approved with suggestions 0 resolved / 2 findings

Implements a collapsible browser sidebar with updated empty states and refined tab strip styling. Address the unused 'connecting' empty-state variant and ensure sidebar rows are wrapped in a listbox with proper keyboard navigation support.

💡 Quality: "connecting" empty-state variant is never rendered (dead code)

📄 desktop/src/apps/BrowserApp/BrowserEmptyState.tsx:17-31 📄 desktop/src/apps/BrowserApp/TabRenderer.tsx:294-307 📄 desktop/src/apps/BrowserApp/TabRenderer.tsx:506-509

BrowserEmptyState supports a "connecting" variant (animated MonitorPlay + loading dots), and the PR description states "the Neko connecting state shows an animated MonitorPlay icon with loading dots." However, the only call site renders variant="new-tab" (TabRenderer.tsx:508). When tab.liveSession is set, TabRenderer renders <LiveBrowserView> directly (lines 294-307) without ever showing the connecting placeholder. As a result the connecting branch is dead code and the advertised connecting UX is not actually wired up.

Either wire the connecting state in (e.g. render <BrowserEmptyState variant="connecting" /> while the Neko stream is still establishing, before/over LiveBrowserView), or drop the unused variant to avoid misleading dead code.

Render the connecting placeholder until the stream is connected (adjust the readiness flag to match LiveBrowserView's actual API).
// In TabRenderer, while a live session is starting, overlay the connecting state.
if (isActive && tab.liveSession) {
  return (
    <div key={tab.id} style={{ position: "absolute", inset: 0 }} data-window-tab={tab.id}>
      <LiveBrowserView
        nekoUrl={tab.liveSession.nekoUrl}
        streamToken={tab.liveSession.streamToken}
      />
      {!tab.liveSession.connected && <BrowserEmptyState variant="connecting" />}
    </div>
  );
}
💡 Quality: Sidebar rows use role=option without listbox parent or keyboard support

📄 desktop/src/apps/BrowserApp/BrowserSidebar.tsx:36-39 📄 desktop/src/apps/BrowserApp/BrowserSidebar.tsx:156-170

SidebarTabRow renders a <div role="option" aria-selected={...}> with an onClick activation, but the containing <aside> is not a role="listbox" (it is a plain landmark). role="option" is only valid inside a listbox/combobox, so screen readers will not announce these rows correctly. Additionally the row is a non-focusable <div> with no tabIndex and no onKeyDown, so keyboard users cannot activate a tab from the sidebar (the close button is explicitly tabIndex={-1}). Consider using the existing role="tablist"/role="tab" pattern (as TabStrip does) or making rows focusable buttons with keyboard handlers, and giving the scroll container the matching container role.

Add focusability and keyboard activation; wrap rows in a role="listbox" container.
// Make the row a real button/focusable element with keyboard activation,
// and give the list container role="listbox" (or use tablist/tab).
<div
  role="option"
  tabIndex={0}
  aria-selected={isActive}
  onClick={onActivate}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onActivate(); }
  }}
  ...
>
🤖 Prompt for agents
Code Review: Implements a collapsible browser sidebar with updated empty states and refined tab strip styling. Address the unused 'connecting' empty-state variant and ensure sidebar rows are wrapped in a listbox with proper keyboard navigation support.

1. 💡 Quality: "connecting" empty-state variant is never rendered (dead code)
   Files: desktop/src/apps/BrowserApp/BrowserEmptyState.tsx:17-31, desktop/src/apps/BrowserApp/TabRenderer.tsx:294-307, desktop/src/apps/BrowserApp/TabRenderer.tsx:506-509

   BrowserEmptyState supports a `"connecting"` variant (animated MonitorPlay + loading dots), and the PR description states "the Neko connecting state shows an animated MonitorPlay icon with loading dots." However, the only call site renders `variant="new-tab"` (TabRenderer.tsx:508). When `tab.liveSession` is set, TabRenderer renders `<LiveBrowserView>` directly (lines 294-307) without ever showing the connecting placeholder. As a result the `connecting` branch is dead code and the advertised connecting UX is not actually wired up.
   
   Either wire the connecting state in (e.g. render `<BrowserEmptyState variant="connecting" />` while the Neko stream is still establishing, before/over LiveBrowserView), or drop the unused variant to avoid misleading dead code.

   Fix (Render the connecting placeholder until the stream is connected (adjust the readiness flag to match LiveBrowserView's actual API).):
   // In TabRenderer, while a live session is starting, overlay the connecting state.
   if (isActive && tab.liveSession) {
     return (
       <div key={tab.id} style={{ position: "absolute", inset: 0 }} data-window-tab={tab.id}>
         <LiveBrowserView
           nekoUrl={tab.liveSession.nekoUrl}
           streamToken={tab.liveSession.streamToken}
         />
         {!tab.liveSession.connected && <BrowserEmptyState variant="connecting" />}
       </div>
     );
   }

2. 💡 Quality: Sidebar rows use role=option without listbox parent or keyboard support
   Files: desktop/src/apps/BrowserApp/BrowserSidebar.tsx:36-39, desktop/src/apps/BrowserApp/BrowserSidebar.tsx:156-170

   `SidebarTabRow` renders a `<div role="option" aria-selected={...}>` with an `onClick` activation, but the containing `<aside>` is not a `role="listbox"` (it is a plain landmark). `role="option"` is only valid inside a `listbox`/`combobox`, so screen readers will not announce these rows correctly. Additionally the row is a non-focusable `<div>` with no `tabIndex` and no `onKeyDown`, so keyboard users cannot activate a tab from the sidebar (the close button is explicitly `tabIndex={-1}`). Consider using the existing `role="tablist"`/`role="tab"` pattern (as TabStrip does) or making rows focusable buttons with keyboard handlers, and giving the scroll container the matching container role.

   Fix (Add focusability and keyboard activation; wrap rows in a role="listbox" container.):
   // Make the row a real button/focusable element with keyboard activation,
   // and give the list container role="listbox" (or use tablist/tab).
   <div
     role="option"
     tabIndex={0}
     aria-selected={isActive}
     onClick={onActivate}
     onKeyDown={(e) => {
       if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onActivate(); }
     }}
     ...
   >

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Important

Your trial ends in 6 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@desktop/src/apps/BrowserApp/BrowserSidebar.tsx`:
- Around line 157-163: The sidebar tab rows lack keyboard accessibility in two
ways: the clickable div at line 157 with role="option" needs keyboard event
handling to support Enter and Space key presses for activating tabs, and the
close-button element in the 205-213 range has tabIndex={-1} which removes it
from keyboard focus. To fix, add an onKeyDown handler to the div element that
checks for Enter or Space key events and calls onActivate to make tab activation
keyboard-operable, and remove the tabIndex={-1} attribute (or set it to 0) from
the close-button element to restore keyboard accessibility for closing tabs.

In `@desktop/src/apps/BrowserApp/TabStrip.tsx`:
- Around line 174-176: The className string for the close button is removing the
default focus outline with focus-visible:outline-none without providing an
alternative visible focus indicator, which breaks keyboard accessibility. Add a
replacement focus-visible class (such as focus-visible:ring-1 with an
appropriate ring color) to the className array to ensure keyboard users can
clearly see when the close button has keyboard focus. This replacement should
provide a visible ring or outline that makes the focused state obvious to
keyboard navigation users.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c177bca-59ab-4561-b0d2-d6d995f14d61

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6e391 and 35ccfd7.

📒 Files selected for processing (8)
  • desktop/src/apps/BrowserApp/BrowserApp.tsx
  • desktop/src/apps/BrowserApp/BrowserEmptyState.tsx
  • desktop/src/apps/BrowserApp/BrowserSidebar.tsx
  • desktop/src/apps/BrowserApp/Chrome.tsx
  • desktop/src/apps/BrowserApp/TabRenderer.tsx
  • desktop/src/apps/BrowserApp/TabStrip.tsx
  • desktop/src/stores/browser-ui-store.ts
  • desktop/src/theme/tokens.css

Comment on lines +157 to +163
<div
role="option"
aria-selected={isActive}
aria-label={title}
onClick={onActivate}
title={title}
className={[

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sidebar tab rows are not keyboard-operable.

Line 157 uses a clickable div without keyboard interaction, and Line 212 removes close-button tab focus (tabIndex={-1}). This makes sidebar tab activation/closing inaccessible from keyboard.

Suggested fix
-    <div
-      role="option"
+    <button
+      type="button"
+      role="option"
       aria-selected={isActive}
       aria-label={title}
-      onClick={onActivate}
+      onClick={onActivate}
       title={title}
       className={[
         "group relative flex items-center gap-2 cursor-pointer select-none",
         "transition-colors focus-visible:outline-none",
         collapsed ? "mx-2 my-0.5 h-[32px] w-[28px] rounded-lg justify-center" : "mx-1.5 my-0.5 h-[32px] rounded-lg px-2",
         isActive
           ? "bg-shell-surface-active text-shell-text"
           : "text-shell-text-secondary hover:bg-white/[0.04] hover:text-shell-text",
       ].join(" ")}
     >
@@
-          tabIndex={-1}
+          tabIndex={0}
@@
-    </div>
+    </button>

Also applies to: 205-213

🤖 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 `@desktop/src/apps/BrowserApp/BrowserSidebar.tsx` around lines 157 - 163, The
sidebar tab rows lack keyboard accessibility in two ways: the clickable div at
line 157 with role="option" needs keyboard event handling to support Enter and
Space key presses for activating tabs, and the close-button element in the
205-213 range has tabIndex={-1} which removes it from keyboard focus. To fix,
add an onKeyDown handler to the div element that checks for Enter or Space key
events and calls onActivate to make tab activation keyboard-operable, and remove
the tabIndex={-1} attribute (or set it to 0) from the close-button element to
restore keyboard accessibility for closing tabs.

Comment on lines +174 to 176
"flex h-[16px] w-[16px] items-center justify-center rounded-[4px] shrink-0 text-shell-text-tertiary transition-opacity hover:bg-white/[0.08] hover:text-shell-text focus-visible:outline-none",
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
].join(" ")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Close button lost visible keyboard focus state.

Line 174 removes visible focus styling (focus-visible:outline-none without a replacement ring). Keyboard users won’t get a reliable focus indicator here.

Suggested fix
 className={[
-  "flex h-[16px] w-[16px] items-center justify-center rounded-[4px] shrink-0 text-shell-text-tertiary transition-opacity hover:bg-white/[0.08] hover:text-shell-text focus-visible:outline-none",
+  "flex h-[16px] w-[16px] items-center justify-center rounded-[4px] shrink-0 text-shell-text-tertiary transition-opacity hover:bg-white/[0.08] hover:text-shell-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40",
   isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
 ].join(" ")}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"flex h-[16px] w-[16px] items-center justify-center rounded-[4px] shrink-0 text-shell-text-tertiary transition-opacity hover:bg-white/[0.08] hover:text-shell-text focus-visible:outline-none",
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
].join(" ")}
"flex h-[16px] w-[16px] items-center justify-center rounded-[4px] shrink-0 text-shell-text-tertiary transition-opacity hover:bg-white/[0.08] hover:text-shell-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40",
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
].join(" ")}
🤖 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 `@desktop/src/apps/BrowserApp/TabStrip.tsx` around lines 174 - 176, The
className string for the close button is removing the default focus outline with
focus-visible:outline-none without providing an alternative visible focus
indicator, which breaks keyboard accessibility. Add a replacement focus-visible
class (such as focus-visible:ring-1 with an appropriate ring color) to the
className array to ensure keyboard users can clearly see when the close button
has keyboard focus. This replacement should provide a visible ring or outline
that makes the focused state obvious to keyboard navigation users.

@jaylfc jaylfc merged commit 144cf8a into dev Jun 20, 2026
8 of 9 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jun 20, 2026
jaylfc added a commit that referenced this pull request Jun 20, 2026
…idebar a11y (#124)

Three deferred nits from the streamed-browser PRs (#1227/#1228).

- browser_container.py: wrap build_neko_run_args() in asyncio.to_thread()
  at the async call site so the blocking tailscale ip subprocess (and any
  netifaces fallback) runs off the event loop. The sync function stays
  unchanged, keeping all existing tests working.

- TabRenderer.tsx: introduce LiveSessionSlot — a small wrapper that renders
  LiveBrowserView plus a BrowserEmptyState variant="connecting" overlay.
  The overlay dismisses on the neko iframe's first load event, detected via
  a MutationObserver (the iframe mounts inside LiveBrowserView after the
  wrapper mounts). LiveBrowserView is untouched.

- BrowserSidebar.tsx: replace div[role="option"] with native <button> on
  SidebarTabRow. Drops the orphan role="option" (no listbox parent), gains
  keyboard + screen-reader support for free. The close affordance is kept
  as a role="button" span inside the row to avoid nested <button> elements.
  Visual styling and collapsed/icon-only state are unchanged.
@jaylfc jaylfc deleted the feat/browser-redesign-collapsible-sidebar branch June 21, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant