Skip to content

Latest commit

 

History

History
295 lines (193 loc) · 18.6 KB

File metadata and controls

295 lines (193 loc) · 18.6 KB

Window Manager

The workspace uses a tiling window manager inspired by macOS Magnet. All windows (terminals, file explorers, editors, browsers) share the same drag/resize/snap behavior.

Basic Operations

  • Drag a window by its title bar to move it
  • Resize by dragging any edge or corner
  • Minimize via the ─ button (restore from taskbar)
  • Maximize via the □ button (toggle)
  • Close via the ✕ button

Grid Layouts

Built-in presets

The toolbar provides quick layout presets:

Button Layout Description
□ Maximize Single window fills workspace
▐▌ 2-column Side by side
▄▀ 2-row Top and bottom
⊞ Quad 2x2 grid
▎▐▐ 3-column Three equal columns

Custom grids

Click the + button to create custom grids (e.g., 3x3, 4x2). Custom grids appear as buttons in the toolbar:

  • Grids up to 4x4 show as SVG icon previews
  • Larger grids show as "RxC" text
  • Right-click a custom grid button to delete it

Freeform mode

Click the freeform button to exit grid mode. Windows become freely positionable without snap constraints.

Snap Behavior

Grid snap

When a grid is active, dragging a window snaps it to the nearest grid cell on release. The snap indicator (blue highlight) shows where the window will land.

Edge snap

In freeform mode, dragging near screen edges snaps to half-screen positions (left/right/top/bottom).

Drag threshold

A 5-pixel drag threshold prevents accidental snaps when you click the title bar just to focus a window. Snap behavior only activates after moving at least 5 pixels.

Pre-snap size memory

When a window snaps to a grid cell or edge zone, its original size is saved. If you drag the window out of the snap zone, it restores to its pre-snap dimensions. This size is persisted in the layout, so it survives page refreshes.

Bypass snap

Hold Alt while dragging to bypass all snap behavior.

Shake to bypass snap

If you don't want to reach for the keyboard, shake the window vigorously for about a second while dragging — a "Grid snap off" badge appears next to the cursor and the window outlines with a dashed border. Snap (grid and edge) stays off for the rest of that drag; drop the window wherever you like. It re-enables automatically on the next drag. The gesture requires sustained shaking (a couple of accidental jiggles won't trigger it). Toggle the feature with Settings → Toolbar & Layout → Shake to bypass snap (on by default), and tune how long you must shake with Shake duration (seconds) (default 1s; lower triggers faster but is easier to hit by accident — the change applies on your next drag).

Shift+drag cell range

While dragging a window title bar in grid mode, press and hold Shift to activate rectangular cell selection. The selected range highlights in blue — release to span the window across all selected cells.

Note: Shift activates mid-drag (start dragging first, then press Shift), not the other way around.

Proportional Tracking

All positioned windows store their bounds as proportional fractions (0-1) of the workspace. When the workspace resizes (sidebar toggle, browser resize), windows automatically reflow to maintain their relative positions. This works in both grid and freeform modes.

Overlap Switcher

When windows overlap, the title bar shows an overlap indicator:

  • ⧉ — Other windows overlap with this one (click to show list)
  • □ — No overlap

Right-click any title bar to see a popup listing all overlapping windows. Click one to bring it to focus.

Active Window Highlight

The focused window is visually distinguished. The highlight intensity is configurable via Settings > Window > Active window highlight:

Level Appearance
Subtle Shadow only (minimal)
Normal Accent-colored border (default)
Strong Accent border + outer glow

Command Mode

Press Ctrl+\ to enter command mode. A yellow [CMD] indicator appears in the taskbar. Commands are single keystrokes — no modifier needed.

Command mode auto-exits after 2 seconds or on Escape.

Window commands

Key Action
← → ↑ ↓ Snap active window to half-screen
1-9 Snap to grid cell N (digits accumulate for 500ms for cells >9)
m Maximize / restore
w Close window
Tab Cycle to next window (stays in command mode)
] / [ Move active window to the next / previous desktop
v Side by side on / off for the active window's tab group (see Side by side)
{ / } Move the active tab one place left / right in its group
V Swap the left and right panes

Global commands

Key Action
f Switch to freeform mode
g Prompt for grid dimensions (e.g. "3x3")
n Open new session dialog
s Toggle sidebar
b Open embedded browser
e Open file explorer
d / D Switch to the next / previous desktop

Taskbar

The taskbar at the bottom shows all open windows in a two-row layout: icon on the left with the window title and subtitle stacked to the right.

For chat/terminal sessions, the icon is a composite icon: the backend logo (Claude/Codex) with a small mode badge (chat bubble or >_ prompt) in the bottom-right corner. For other window types, inline SVG icons are used (folder, document, pencil, grid, globe). All icons scale proportionally with taskbar height via transform:scale().

Items use flex-shrink with text-overflow ellipsis to handle many windows gracefully.

Right-click context menu

Right-click any taskbar item to open a context menu:

Action Description
Move Enters move mode -- the window attaches to your cursor. Click anywhere in the workspace to place it.
Move to Desktop Submenu listing other desktops -- move the window to a different desktop (only shown when multiple desktops exist)
Minimize / Restore Toggle minimize state
Close Close the window

Move mode

Move mode detaches a window from its current position and attaches it to your cursor. A full-screen overlay blocks all other UI interaction during move, ensuring no accidental clicks reach elements underneath. If the window is maximized or snapped, it restores to its original (pre-snap) size before following the cursor. Click to place it at the new location.

Move mode can also be triggered via command mode (Ctrl+\ then a move command), or from the sidebar: expand a session card → Move. The sidebar path is the recovery route for a window accidentally dragged off-screen (no grabbable title bar) — it switches to the window's desktop first and resolves tab groups to the host.

Virtual Desktops

Multiple independent workspaces, each with its own set of windows and grid layout.

Desktop previews

The taskbar right corner shows Ubuntu-style miniature previews of each desktop. Each preview renders proportional window rectangles from gridBounds, giving you a live overview of every desktop's layout. The active desktop has an accent-colored border.

Creating and managing desktops

  • Click the + button next to the previews to add a new desktop
  • Right-click a preview for Rename / Delete
  • Deleting a desktop moves its windows to the adjacent desktop

Switching desktops

  • Click a desktop preview to switch
  • Ctrl+Alt+Left/Right keyboard shortcut to cycle through desktops

Moving windows between desktops

  • Drag a window (by title bar) onto a desktop preview — the window collapses into a mini rectangle inside the preview, showing exactly where it will land. Drag back out to cancel.
  • Drag a taskbar item onto a desktop preview
  • Right-click a taskbar item → "Move to Desktop" submenu

Notifications across desktops

When a window on another desktop finishes (Claude awaiting input), its rectangle in the desktop preview blinks yellow. The desktop preview border also turns yellow. Both clear when you switch to that desktop and focus the window.

Per-desktop grid

Each desktop maintains its own grid mode. Desktop 1 can be a 2x2 grid while Desktop 2 is freeform. Layout presets (toolbar buttons) only affect the current desktop.

Resizable taskbar

Drag the top edge of the taskbar to resize it (36-120px). All elements (desktop previews, window icons, text, usage pies) scale proportionally. Height is synced across clients.

Usage Widget

The right side of the taskbar shows backend usage as compact rows:

  • One row for Claude
  • One row for Codex
  • Each row starts with the backend logo and then two pies for the global 5-hour and 7-day rate-limit windows

Click the widget to open a popup with percentages, reset times, and the last update time. Claude usage keeps the last successful snapshot across transient fetch failures and server restarts, so the Claude row does not disappear just because one poll failed.

Technical details

Windows are hidden via visibility:hidden + pointer-events:none (not display:none). This preserves all internal state: scroll position, terminal content, input focus. Switching back is instant with no re-rendering. WebSocket handlers continue processing — hidden chat windows receive new messages normally.

Tab Groups

Merge multiple windows into Chrome-style tab groups for space-efficient multitasking.

Creating a tab group

  • Drag a window's type icon onto another window's type icon to merge them into a tab group. The source window is auto-hidden during the drag (the ghost represents it).
  • Drag a full window over another window's type icon OR tab bar to merge. Over empty space, the window follows the cursor normally with snap highlights. Entering a merge zone, it collapses to a small ghost preview. Leaving the zone restores the window.

Tab merge hit-testing uses elementFromPoint (via the shared _detectTabMergeTarget helper on the tab-group mixin), so occluded icons under the dragged window never match — dragging A's titlebar onto A's own position no longer accidentally merges with a window stacked underneath.

Tab bar

When windows merge, the title bar is replaced by a tab bar with rounded-top tabs. The active tab visually connects to the content area below.

  • Click a tab to switch to that window's content
  • Close button on each tab to remove it from the group (window closes or becomes standalone)
  • Drag a tab sideways to reorder it: a thin marker shows where it will land, Esc cancels. The first few pixels decide — a drag that starts sideways stays a reorder.
  • Drag a tab downward (>30px) to pull it out of the group. The detached window is raised above all others and follows the cursor. You can then drop it in empty space (becomes standalone), onto another window's icon/tab bar (merges into that group — including the original group, at the spot under the pointer), or on a snap zone.
  • Ctrl+Shift+PageUp / PageDown (or command mode { / }) move the active tab one place left / right.
  • Right-click a tab to open that tab's own window menu.

Merging is the only drag that does something other than move and snap: dragging a window over another window's title bar (anywhere but its icon or tab bar) just moves it, and dropping it near a screen edge snaps it as usual. A whole tab group dragged by its title bar only moves — merge its tabs one by one.

With Settings → Window → Dropping a window onto another set to Side by side at once, a merge lands side by side straight away (the window you dragged on the right); the notification then offers Undo (the window goes back where it was) and Unsplit (keep them grouped as tabs, the window at the spot you dropped it on). Dropping a window onto a group that is already side by side (with either setting) puts it in the half under the pointer and shows it there; the notification names the new pair and offers Undo (the window goes back where it was, the group back to how it was).

  • On a narrow window the right-hand pane never gets narrower than the window controls plus room for one tab (the divider stops there); when that column is too tight for the side-by-side badge as well, the badge steps aside — the divider's right-click still has Unsplit / Swap left and right.
  • Dragging a tab first shows it (a press shows the tab, as in a browser), so a tab dragged across the middle arrives shown on its new side.

Side by side

Two tabs of a group can be shown side by side in the same window — one pane on the left, one on the right, a divider between them. It is always an explicit second step after grouping, never a side effect of a drag:

  • The side-by-side button (the two-column icon at the right end of the tab bar, before the window controls): the current tab goes on the left, the tab you used most recently before it goes on the right (its tooltip names that tab). Right after you merge windows into a group, the button pulses once and a notification offers Show side by side in one click.
  • The window menu (right-click the title bar, or a tab): in a group of two or more, Show side by side ▸ Beside {name} (on the right) — this window on the left, the tab you pick on the right; the focus stays on the window you right-clicked. In side-by-side mode the menu has Unsplit and Swap left and right instead.
  • Command mode: Ctrl+\ then v turns side by side on or off for the active window's group, V swaps the sides. Outside a group of two or more tabs it tells you to group two windows first.
  • In side-by-side mode the same button is highlighted. Click it for Unsplit (back to tabs — nothing moves) or Swap left and right. Right-clicking the divider offers the same two.
  • The tab bar is split in two halves, one above each pane, following the divider as you drag it: every tab belongs to one side, the tab shown in each pane is highlighted and underlined in its own colour, and a short bar marks the middle. Click a tab in the left half to show it on the left, in the right half to show it on the right — both sides can be switched.
  • Drag a tab sideways to reorder it within its half; drag it across the middle to move it to the other side (the tab you were looking at stays visible there, and its old side shows its neighbour). Moving the last tab of a side out ends side by side.
  • Opening a file path or a local link from a chat shows it side by side with that chat by default (on the side the chat is not on, if the window is already side by side); opening the same path again shows its tab. Settings → Window → Opening a file path or link from a window switches this to As a tab of the window or In its own window.
  • Drag the divider to resize the panes; double-click it to make them equal.
  • Every side-by-side you start shows a notification with Undo for 5 seconds — it puts both windows back exactly where they were.
  • On a phone (≤ 768 px wide) a side-by-side group shows one pane at a time as tabs (one tab bar, left side first); the button is hidden and the layout on your other devices is not changed.

In the taskbar

A tab group shows as a single stacked taskbar item (Windows-style) — the unique tab icons offset like a stack of cards, with a count badge — instead of hiding the grouped windows behind the host. The item is titled by the currently active tab.

  • Click the stacked item to open the group: its window is restored if minimized, focused and brought to the front, showing the tab that was on show.
  • Click it again while the group is already in front to get the chooser — a list of every tab in the group (the one on show is marked); click one to switch straight to that tab.
  • Hover the stacked item with a mouse for a moment (~0.3 s) and the same chooser appears above it without taking the focus; move onto it and click a tab, or move away and it closes. Passing over the item on the way to another one does nothing, and there is no hover on a touch screen or during a drag.
  • With the keyboard: Tab to the item, Enter / Space = the click, ↑ = the chooser (then ↑ / ↓ and Enter to pick, Esc to close).
  • Right-click acts on the whole group (move, minimize/restore, close).

The item blinks if any tab is waiting for input.

Data model

Tab groups share a {tabs, active, layout, split} object among all grouped windows. The tabs array holds references to each window in the group, and active tracks the currently displayed tab; layout is tabs or split, and split.pair names the left and right panes.

Layout persistence

Tab groups are saved and restored as part of the layout auto-save, and synced across clients via the layout sync protocol.

Layout Sync

When multiple browsers are connected, layout changes are synced in real-time:

  • State-based: the full workspace state is broadcast after every change (not individual operations)
  • Smart diff: the receiver compares remote state against local and only applies differences
  • Synced properties: window positions, maximize/minimize state, snap, z-order, open/close, file explorer navigation
  • Proportional coordinates: all positions use gridBounds (0-1 fractions), so different screen sizes work correctly
  • Window matching: windows are matched across clients by unique ID (win-{timestamp}-{random})
  • Window creation via openSpec: every window records a serializable creation recipe. When a window exists on one client but not another, the receiver replays it. Supports all window types: sessions, file explorers, viewers, editors, browsers, subagent viewers.
  • Draft sync: chat input text synced across clients in real-time via StateSync

The sync is debounced at 500ms and uses a 1-second cooldown to prevent echo loops.

Presets

Presets save and restore the complete workspace state:

  • Window positions, sizes, z-order
  • Grid configuration
  • Theme and font settings
  • Session-to-window mapping (by claudeSessionId)

When loading a preset, windows not in the preset are minimized (not closed) — sessions stay alive.

Save presets via the toolbar menu. They persist across server restarts.

Close Behavior

Configure via Settings > Window > Window close behavior:

Mode Close window action
Terminate (default) Kills the session (process terminated)
Detach Removes the window but keeps the session alive. Re-attach from the sidebar.

Regardless of this setting, you can always explicitly terminate a session via the Terminate button in the session card's expand panel.

Bounce on Focus

Optional: when focusing a window from the sidebar or taskbar, a brief scale-bounce animation provides visual feedback. Enable via Settings > Window > Bounce on remote focus.