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.
- 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
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 |
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
Click the freeform button to exit grid mode. Windows become freely positionable without snap constraints.
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.
In freeform mode, dragging near screen edges snaps to half-screen positions (left/right/top/bottom).
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.
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.
Hold Alt while dragging to bypass all snap behavior.
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).
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.
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.
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.
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 |
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.
| 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 |
| 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 |
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 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 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.
Multiple independent workspaces, each with its own set of windows and grid layout.
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.
- 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
- Click a desktop preview to switch
- Ctrl+Alt+Left/Right keyboard shortcut to cycle through 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
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.
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.
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.
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.
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.
Merge multiple windows into Chrome-style tab groups for space-efficient multitasking.
- 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.
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.
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+\thenvturns side by side on or off for the active window's group,Vswaps 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.
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.
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.
Tab groups are saved and restored as part of the layout auto-save, and synced across clients via the layout sync protocol.
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 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.
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.
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.