Skip to content
Agent Workspaces
Skill

/drag-pane

Drag-and-drop pane resizing implementation for two-pane plugin layouts. Covers mouse event handling via the internal/mouse package, hit region registration, drag delta calculation, width clamping, state persistence, and pane layout management. Use when working on pane resizing,

From plugin
sidecar
1.1k19 skills

Claude Code

$ npx -y skills add marcus/sidecar --skill drag-pane --agent claude-code

SKILL.md

drag-pane.SKILL.md
name: drag-pane
description: >
  Drag-and-drop pane resizing implementation for two-pane plugin layouts.
  Covers mouse event handling via the internal/mouse package, hit region
  registration, drag delta calculation, width clamping, state persistence,
  and pane layout management. Use when working on pane resizing, drag
  interactions, layout management, or adding drag-to-resize to a new plugin.

Drag-to-Resize Pane Implementation

Overview

Add drag-to-resize support for two-pane plugin layouts (sidebar + main content). Users click and drag the divider between panes to resize them.

Prerequisites

  • Plugin already has a two-pane layout (sidebar + main content)
  • State persistence functions exist in `internal/state/state.go` (each plugin has its own getter/setter)
  • Familiarity with `internal/mouse` package

Existing Implementations

| Plugin | State Functions | Mouse File | |--------|----------------|------------| | FileBrowser | `GetFileBrowserTreeWidth()` / `SetFileBrowserTreeWidth()` | `internal/plugins/filebrowser/mouse.go` | | GitStatus | `GetGitStatusSidebarWidth()` / `SetGitStatusSidebarWidth()` | `internal/plugins/gitstatus/mouse.go` | | Conversations | `GetConversationsSideWidth()` / `SetConversationsSideWidth()` | `internal/plugins/conversations/mouse.go` | | Workspace (project) | `GetWorkspaceSidebarWidth()` / `SetWorkspaceSidebarWidth()` | `internal/plugins/workspace/view_list.go` | | Sessions (global workspace) | `GetWorkspaceSidebarWidth()` / `SetWorkspaceSidebarWidth()` | `internal/overview/workspaces.go` |

Windowing parity: project and global workspaces are one feature

The project workspace (`internal/plugins/workspace`) and the global Workspaces browser shown as **Sessions** in the navbar (`internal/overview`) are two projections of the same windowing model. They are not independent surfaces that happen to look similar.

**If a change affects panes, splits, drag handles, pane borders, focus chrome, or pane hit regions in one of them, it affects the other.** The rule is structural, not a habit to remember:

  • `internal/panelayout` owns pane-tree structure and geometry.
  • `internal/paneframe` owns presentation: chrome geometry (`Inset`, `Geometry`),

the leaf border states (`Chrome`, `WrapLeaf`), the drag handle (`RenderDividerHandle`, `DividerHitBox`, `HandleStateFor`), the compositor (`Compose`, `ComposeLeaf`, `RenderContent`), the chrome-aware floors (`ChromeFloors`), and the one order hit regions are registered in (`RegisterRegions`), and click-to-focus (`LeafAt`, `FocusLeafAt`).

  • Each surface implements `paneframe.Host` and `paneframe.RegionSink` in exactly

one file: `internal/plugins/workspace/pane_host.go` and `internal/overview/pane_host.go`.

**Focus is one value, answered from geometry.** `Host.Focus()` draws the ring and `Host.SetFocus()` moves it; there is no third place a surface may record who is being typed into, so a surface whose live terminal holds the keyboard separately gives it up inside its own focus setter (`workspace.setFocusTarget`, `overview.focusPreviewLeaf`). A pointer moves focus through `paneframe.FocusLeafAt`, which resolves the leaf from its OUTER **box**, not from the hit region the press landed on — a terminal leaf owns no click-to-focus region, because its presses belong to the live pane and are forwarded to tmux. `FocusLeafAt` moves focus and nothing else, so the press still reaches whichever region claimed it, and it declines the divider's widened target so a press one cell off a handle resizes without also re-focusing. Hanging focus off the region handlers instead is td-43db92: one focus call per leaf kind, and the ring drawn on a neighbour for the kind nobody remembered.

`Host.Layout()` must answer the tree the surface last **drew**, not one it could place. A view that replaces the preview — the kanban board, a modal — draws no tree, and geometry that outlives the frame lets a click on whatever is drawn there move pane focus instead. The project plugin records the layout beside the hit regions it earned (`paneFrame`/`paneFrameDrawn`, cleared with the hit map at the top of `View`); the global browser's `previewPeerBox()` already refuses when the preview is not drawn.

**Do not add a second compositor, a second border rule, or a second divider renderer.** If a behaviour belongs to windowing, it goes in `paneframe`; if it belongs to one surface's content, it goes in that surface's host file. Both surfaces then get it at once.

Tests that hold this: `internal/paneframe/paneframe_test.go`, `internal/plugins/workspace/pane_peer_chrome_test.go`, and `internal/overview/pane_peer_chrome_test.go`.

The header's layout button is part of the same model

A pane can be **moved**, not only resized, and that entry point lives in the same chrome the drag handle does. `internal/panereposition` owns the shared interaction policy — the modal controller, the header adapter, the structural fingerprint, and the graft helpers — while `panelayout.PlanMove`/`ApplyMove` own the structure. There are three hosts, and all three get it: project Workspaces, global Sessions, and the app content decks.

  • **Reserve.** `panereposition.ReserveMovableHeader(width, movable, hasClose)`

wraps `ui.ReserveHeaderControls` and returns the tab strip's width plus each control's column. Each host binds it once per frame in its own `reserveHeader`/`composeHeader` pair, and every header renderer, tab strip and region on that surface goes through those — a strip laid out for one reserve while the header composes another is how a tab click lands on the wrong tab. The **drop order as the row narrows is layout first, close `×` last**, all-or-nothing per control: a clipped button is a target whose meaning cannot be recovered.

  • **`movable` comes from the tree** (`panereposition.Movable`): false with no

tree, and false for a tree of one leaf, because `PlanMove` refuses every destination on a single leaf. A header with no lea

Read more
@marcus
Ships withsidecar

Always check if you are running in Sidecar: run sidecar agents for capabilities. You might never open your editor again. Status: Ready for daily use. Please report any issues you encounter. Documentation · Getting Started · Comprehensive List of Features

Stars on GitHub

Momentum

stars in total
1.1k
Star history of marcus/sidecar on GitHub, a chart by star-history.com

Other skills on sidecar.

coordinate-agents

coordinate-agents

Drive another Sidecar-managed agent from a shell — discover targets, create the layout, start a provider, prompt and wait, read…

@marcus@marcusInstall Skill
create-adapter

create-adapter

Create conversation adapters for importing AI chat history from different tools (Claude Code, Cursor, Warp, Codex, etc.). Covers…

@marcus@marcusInstall Skill
create-modal

create-modal

Create declarative modals using the modal library API. Covers modal types (confirm, input, select, form), sections (Text,…

@marcus@marcusInstall Skill
create-plugin

create-plugin

Create new sidecar plugins implementing the plugin.Plugin interface, rendering views with Bubble Tea, handling keyboard input via…

@marcus@marcusInstall Skill
create-prompt

create-prompt

Create prompts for sidecar workspaces. Covers prompt structure (name, ticketMode, body), template variables (ticket with…

@marcus@marcusInstall Skill
create-theme

create-theme

Create custom color themes for Sidecar, including base theme selection, color overrides, gradient borders, tab styles,…

@marcus@marcusInstall Skill

More skills from other plugins.

paseo

paseo

Paseo reference for managing hosts, projects, workspaces, workspace scripts, agents, schedules, and heartbeats.

paseo11d23k
@getpaseo@getpaseoInstall Skill
10x
Auto-invoked

10x

Personalized audit that teaches the advanced Paradev (formerly Superset) features the user isn't using yet (automations, parallel…

paradev2d16k
@superset-sh@superset-shInstall Skill
capabilities

capabilities

Your capability catalog — read this at boot. Lists the temporal date-range skills and the external integrations (reached via the…

@chaitanyagiri@chaitanyagiriInstall Skill

Questions about drag-pane.

Which coding agents can use drag-pane?
drag-pane installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.