Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions skills/cmux-custom-sidebar/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: cmux-custom-sidebar
description: "Build a custom cmux sidebar from a plain-language request. Use when the user asks for a custom sidebar, a sidebar that shows their workspaces/tabs/PRs/clock, a vibe-coded sidebar, or anything involving files in ~/.config/cmux/sidebars/. Covers authoring the interpreted SwiftUI-style file, enabling the beta flag, selecting it, and iterating with hot reload."
---

# cmux Custom Sidebar

cmux renders custom sidebars from a small SwiftUI-style file at runtime: no Xcode, no build step, no signing. The file hot-reloads on save, binds to live cmux state (workspaces, tabs, git, PRs, clock), and can run real cmux commands on tap.

The person asking is usually describing a result ("a sidebar that shows my workspaces and lets me jump between them"), not an implementation. Turn that into a clean, native-looking sidebar and make the engineering decisions for them. Do not ask them about SwiftUI, files, or syntax.

## Full reference

This skill is the workflow summary. The complete authoring contract (every supported view, modifier, language feature, and data field) is one command away; read it before writing a non-trivial sidebar:

```bash
cmux docs sidebars
curl -fsSL https://raw.githubusercontent.com/manaflow-ai/cmux/main/docs/custom-sidebars.md
```

## Workflow

1. **Enable the beta** (once). Custom sidebars are behind Settings → Beta features → Custom sidebars (`customSidebars.beta.enabled`). If a written sidebar does not appear in the picker, this flag is the first thing to check.
2. **Write a named file.** The name becomes the menu label; use short kebab-case:
```
~/.config/cmux/sidebars/<name>.swift
```
Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add blank lines around code blocks and language specifiers for better formatting.

The code blocks at lines 25-27 and 30-33 are missing surrounding blank lines, and the shell commands at 30-33 should have a bash language tag for syntax highlighting.

📝 Proposed formatting fixes

For lines 24-28:

 2. **Write a named file.** The name becomes the menu label; use short kebab-case:
+
    ```
    ~/.config/cmux/sidebars/<name>.swift
    ```
+
    The file is a single SwiftUI-style view expression (no `struct`, no `var body`, no imports). A `.json` variant exists for static layouts; prefer `.swift` for anything dynamic.

For lines 29-34:

 3. **Validate and select it:**
+
-   ```bash
+   ```bash
    cmux sidebar validate <name>   # parse/interpret check with real data shapes
    cmux sidebar select <name>     # switch the sidebar to it
    ```
+
    The user can also pick it manually: right-click the sidebar toggle button.

Also applies to: 30-33

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 25-25: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 27-27: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 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 `@skills/cmux-custom-sidebar/SKILL.md` around lines 25 - 27, Add surrounding
blank lines before and after the inline code block that shows
~/.config/cmux/sidebars/<name>.swift and before/after the command block that
lists "cmux sidebar validate <name>" and "cmux sidebar select <name>" in
SKILL.md, and mark the command block with a language tag by changing the fence
to ```bash so the shell commands are syntax-highlighted; ensure the Swift path
block remains fenced as plain code with a blank line above and below and the
commands block has a blank line above and below plus the ```bash opener.

Source: Linters/SAST tools

The file is a single SwiftUI-style view expression (no `struct`, no `var body`, no imports). A `.json` variant exists for static layouts; prefer `.swift` for anything dynamic.
3. **Validate and select it:**
```bash
cmux sidebar validate <name> # parse/interpret check with real data shapes
cmux sidebar select <name> # switch the sidebar to it
```
The user can also pick it manually: right-click the sidebar toggle button.
4. **Iterate.** Saving the file hot-reloads the sidebar in place (`cmux sidebar reload` forces it). Look at the result, fix what looks off, and verify rows show real data and taps do the right thing before declaring it done.

## Authoring rules

- **Default to live data.** Bind to the `workspaces` context instead of hard-coding text so the sidebar stays correct on its own.
- **Make it interactive by default.** Rows that represent something openable should run the matching `cmux(...)` action on tap. A list that just displays text is rarely what they wanted.
- **Prefer `Reorderable` for workspace-like lists.** It gives persisted drag-and-drop reordering for free.
- **Keep it native and uncluttered:** a title, a divider, then the content.
- **Cap long lists** (`.prefix(20)`, filter/sort before rendering). The sidebar re-evaluates about once a second; do not render hundreds of rows.
- **Stay inside the supported subset.** Unsupported syntax is skipped gracefully (never crashes), but choose the closest supported approach rather than shipping a half-blank sidebar.

## Quick start

```bash
cat > ~/.config/cmux/sidebars/mine.swift <<'SWIFT'
VStack(alignment: .leading, spacing: 8) {
Text("My sidebar").font(.title3).bold()
Text(clock.time).font(.caption).foregroundColor(.secondary)
Divider()
Reorderable(workspaces, move: "workspace.reorder") { w in
Button(action: { cmux("workspace.select", workspace_id: w.id) }) {
HStack {
Text(w.selected ? "●" : "○").foregroundColor(w.selected ? "#FF8800" : .secondary)
Text(w.title)
Spacer()
}.padding(4)
}
}
}
SWIFT
cmux sidebar validate mine && cmux sidebar select mine
```

## Live data context (read-only, refreshes ~1s)

- `workspaces`: array with `id`, `title`, `selected`, `pinned`, `index`, `directory`, `ports` + `portCount`, `unread`, `tabs` + `tabCount`; plus, when present: `description`, `color`, `branch` + `dirty`, `pr` / `prs` (`{number, label, url, status, stale, branch}`), `progress` (`{value, label}`), `latestMessage`, `latestPrompt`, `latestAt`, `remote` (`{target, state, connected}`).
- `workspaces[i].tabs`: `id`, `title`, `focused`, `pinned`; plus `directory`, `branch` + `dirty`, `ports` when available.
- `clock`: `{time, hour, minute, second, weekday, epoch}`.
- Scalars: `workspaceCount`, `selectedTitle`, `selectedId`, `unreadTotal`.

Optional fields are omitted when absent; guard with `if let b = w.branch { ... }` or `w.pr != nil ? ... : ...`.

## Actions

A button or `.onTapGesture` body calls `cmux("<method>", param: value)`, dispatched through the same surface as the `cmux` CLI. Common methods: `workspace.select` (`workspace_id`), `surface.focus` (`surface_id`), `workspace.reorder` (`workspace_id` + `index`). `openURL("https://...")` opens links. Discover the full command surface with `cmux docs api`.

## Supported subset at a glance

Containers: stacks (incl. lazy), `Group`, `List`, `Section`, grids, `ViewThatFits`, `ScrollView`, `HSplitView` (two resizable columns). Content: `Text`, `Label`, `Image(systemName:)`, `Button` (title and label form), `Menu`, `ProgressView`, `Gauge`, `Spacer`, `Divider`, shapes, gradients via `.background`. Modifiers: full typography set, colors as hex strings or tokens, `.padding`/`.frame`/layout, `.background`/`.overlay`/`.mask`/`.contextMenu` with arbitrary nested views, shadows/borders/opacity/effects, `.onTapGesture`, `.help`, `.disabled`. Language: `let`, user `func` helpers, `for`/`ForEach`, `if/else`, ternary, string interpolation, arithmetic, array methods (`filter`/`map`/`sorted`/`prefix`/...), string and number formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Gradients listed as supported but are explicitly unsupported per the canonical doc

The "Supported subset at a glance" section includes gradients via .background, but docs/custom-sidebars.md places gradients (LinearGradient/…) in the "Not yet supported" list. An agent following this skill will write gradient code that is silently skipped by the interpreter, potentially producing a partially blank or visually broken sidebar with no error shown to the user. The skill should either omit gradients from the supported list entirely or move them into the unsupported note at line 86.


Not yet supported (write the natural Swift anyway; it degrades gracefully): `@State` and input controls (`TextField`, `Toggle`, `Slider`, `Picker`), custom `struct`/`View` definitions, navigation (`sheet`/`popover`), `AsyncImage`. Two-way editing does not work yet; taps that run `cmux(...)` do.

## Troubleshooting

- Sidebar missing from the right-click picker: the beta flag is off, or the file is not directly under `~/.config/cmux/sidebars/`.
- Blank or partial render: run `cmux sidebar validate <name>`; errors show inline in the sidebar with the failing location. A broken save keeps the last working render on screen, so re-save after fixing.
- Rows not tappable: wrap the row in `Button(action: { cmux(...) }) { ... }` or add `.onTapGesture { cmux(...) }`.
- Reorder not persisting: use `Reorderable(data, move: "workspace.reorder")`, not `List`/`.onMove`/`.draggable`.
Loading