Skip to content

Add top-tab layout option - #420

Merged
Cheggin merged 11 commits into
mainfrom
feat/top-tab-layout
May 9, 2026
Merged

Add top-tab layout option#420
Cheggin merged 11 commits into
mainfrom
feat/top-tab-layout

Conversation

@Cheggin

@Cheggin Cheggin commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New optional horizontal tab strip across the top of the hub (alternative to the 200px left sidebar). Reclaims that width for the live browser viewport.
  • Settings → Application gains a Tab layout card with a two-card visual picker (mini-mockups of side vs top).
  • BrowserPool refits its emulated viewport when the hub layout shape changes, so toggling the layout mid-session no longer leaves black gutters around the live browser.

What changed

Hub UI (`feat(hub): add top-tab layout option with settings picker`)

  • `Sidebar` grows a `mode: 'side' | 'top'` prop. In top mode it renders a horizontal nav with chips (favicon + corner status dot + label), no separator stripes, horizontal scroll when crowded.
  • `HubApp` hydrates `tabsPosition` from `localStorage[hub-tabs-position]` (default `'side'` — no behavior change for existing users), tags `.hub-body` with `data-tabs-position`, and dispatches `pane:layout-change` after React commits the new layout (double-rAF) so `AgentPane` re-measures against the updated DOM. The selected-tab highlight is suppressed outside grid view.
  • `SettingsPane` adds a `LayoutSection` (own settings-card) with a two-card picker. Selecting a card emits a `hub:tabs-position-change` event the hub listens for.

Browser viewport refit (`fix(browser): refit emulated viewport on layout shape change`)

  • `BrowserPool.attachToWindow`'s already-attached branch now recomputes the aspect-matched emulated width and, when it changes, re-applies `enableDeviceEmulation` and refits zoom. Casual window resizes still go through the `setViewBoundsFitted` fast path, so manual Cm+=/Cmd+- zoom is preserved except across an explicit layout switch.
  • `MAX_EMULATED_VIEWPORT_WIDTH` raised from 1600 to 1920. With top tabs reclaiming sidebar width, common desktop hub regions (~1500–1800px) hit the old cap and got ~100px letterbox gutters; 1920 covers most desktop sizes without crossing the threshold where sites like X shift into ultra-wide centered layouts.

Test plan

  • Default install: hub looks identical to before (sidebar on left).
  • Settings → Application → Tab layout → click Top: tabs jump to a horizontal strip; live browser fills the wider area edge-to-edge (no black gutters).
  • Toggle back to Side mid-session: sidebar returns; live browser refits without leaving a gutter.
  • Reload after toggle: preference persists.
  • With many sessions, the top strip scrolls horizontally instead of overflowing the window or showing a popup that gets covered by the BrowserView.
  • Dashboard view: no chip is highlighted (only grid view should show selected).

Summary by cubic

Adds an optional top-tab layout that replaces the left sidebar with a horizontal tab strip to widen the live browser viewport. Also refits the emulated viewport on layout changes to remove gutters and keeps the new UI readable in light mode.

  • New Features

    • Top tab strip (no separators) with favicon chips, status dots, and horizontal scroll.
    • Settings → Application: visual Tab layout picker; preference saved in localStorage['hub-tabs-position'] and reflected on .hub-body[data-tabs-position].
    • Sidebar supports mode: 'side' | 'top'. HubApp emits pane:layout-change after commit; selected highlight is suppressed outside grid view.
  • Bug Fixes

    • Refit emulated viewport on layout shape changes; re-apply enableDeviceEmulation and zoom as needed, and skip refit on zero/non-finite bounds (preserves manual zoom except on explicit layout switches).
    • Raise MAX_EMULATED_VIEWPORT_WIDTH to 1920 to prevent letterboxing on wider layouts.
    • Make the tabstrip and layout picker theme-aware so they stay legible in light mode; add a unit test covering appearance + layout toggles.

Written for commit 3eaff99. Summary will update on new commits.

Cheggin added 2 commits May 8, 2026 15:34
Adds an alternative horizontal tab strip that renders above the browser
viewport instead of the 200px-wide left sidebar, reclaiming that width
for the live browser view. Persisted via localStorage[hub-tabs-position]
(default 'side', so existing users see no change).

- Sidebar.tsx grows a mode: 'side' | 'top' prop. In 'top' mode it renders
  a horizontal nav with TabChip components — favicon + corner status dot
  + label, no separator stripes between chips, scrollable horizontally
  when there are too many to fit.
- HubApp.tsx hydrates tabsPosition from localStorage, sets data-tabs-position
  on .hub-body so CSS can flip flex-direction, and dispatches
  pane:layout-change AFTER React commits the new layout (via double rAF)
  so AgentPane re-measures bounds against the updated DOM. The selected-tab
  highlight is suppressed when not in grid view.
- SettingsPane.tsx adds a LayoutSection card under Application with a
  two-card visual picker (mini-mockups of side vs top), wired via a
  hub:tabs-position-change event the hub listens for.
- hub.css covers the new .tabstrip strip + .layout-picker visual cards.
The hub's emulated viewport width was matched to the physical rect's
aspect ratio only on first attach. On subsequent attaches the cached
emulatedWidth was reused, so when the user changed layout shape (e.g.
toggling the new top-tab layout, which reclaims the sidebar's width),
fitBoundsToView shrank-and-centered the WebContentsView using the stale
emulatedWidth — leaving black gutters on each side of the live browser.

- attachToWindow's already-attached branch now recomputes the
  aspect-matched emulatedWidth and, when it differs from the cached
  value, re-applies enableDeviceEmulation and refits the zoom factor.
  This branch only fires on real layout-shape changes (not casual
  window resizes, which still hit setViewBoundsFitted), so the user's
  manual Cmd+=/Cmd+- zoom is preserved everywhere except an explicit
  layout switch.
- MAX_EMULATED_VIEWPORT_WIDTH raised from 1600 to 1920. With top tabs
  reclaiming the sidebar width, common desktop monitors (~1500-1800px
  hub area) were running into the 1600 cap and producing ~100px
  letterbox gutters even with the refit fix above. 1920 covers most
  desktop sizes without crossing the threshold where sites like
  X/Twitter shift into ultra-wide centered layouts.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/renderer/hub/hub.css">

<violation number="1" location="app/src/renderer/hub/hub.css:4395">
P2: Top-tab chips still render separator stripes, conflicting with the intended “no separator stripes” layout.</violation>
</file>

<file name="app/src/main/sessions/BrowserPool.ts">

<violation number="1" location="app/src/main/sessions/BrowserPool.ts:621">
P1: Guard the aspect-ratio recomputation against zero/invalid bounds to avoid propagating `NaN` into `setBounds` during transient relayout states.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread app/src/main/sessions/BrowserPool.ts
Comment thread app/src/renderer/hub/hub.css Outdated
Cheggin and others added 3 commits May 8, 2026 15:47
The chip separator pseudo-element was left over from an earlier Chrome-
inspired pass; the intended top-tab look has no inter-chip stripes.
Removes both the separator rule and the now-dead hover/active-hide
companions.
If a frame fires while the hub is mid-relayout (pane width or height
momentarily 0, or non-finite for any reason), the aspect-ratio recompute
in attachToWindow's already-attached branch divides by zero / produces
NaN and propagates Infinity/NaN into the clamp + setBounds path.

Skip the recompute and the trailing setBounds when the incoming bounds
shape is invalid; ResizeObserver fires again with a real rect.

Also updates the two BrowserPool unit tests whose hard-coded fitted
geometry encoded the old MAX_EMULATED_VIEWPORT_WIDTH (1600). With the
cap raised to 1920, a 2000x900 rect at zoom 0.5 yields renderedWidth
960 and a 520px letterbox (was 800/600).
@Cheggin

Cheggin commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@cubic review

@cubic-dev-ai

cubic-dev-ai Bot commented May 8, 2026

Copy link
Copy Markdown

@cubic review

@Cheggin I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 6 files

Cheggin and others added 6 commits May 8, 2026 20:36
Latest main added a light/dark/system appearance picker, so the top-tab layout branch needs its custom picker and tabstrip states to use theme-aware tokens instead of hard-coded dark-mode highlights. The settings regression now clicks both the light theme option and top layout option in one render so this merge point stays covered.

Constraint: origin/main now owns the appearance picker and renderer theme-mode plumbing

Rejected: Leave white alpha fills in the layout mockups | they wash out when data-mode=light flips the shell palette

Confidence: high

Scope-risk: narrow

Directive: New hub UI states should use semantic theme tokens or mode-aware RGB vars, not fixed dark-surface rgba values

Tested: npm run test -- tests/unit/hub/SettingsPane.spec.tsx

Tested: npm run lint

Tested: npm run typecheck
@Cheggin
Cheggin merged commit c449198 into main May 9, 2026
10 checks passed
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