Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a85fb44
feat: unify Cmd+K and dock workspace search into one palette (#912)
srid May 15, 2026
07096d1
refactor(lowy): drop dead query param from searchWorkspaceEntries
srid May 15, 2026
60bb746
refactor(hickey): move resolvedPr to dockModel.ts, delete dockRowChrome
srid May 15, 2026
eef0fb6
feat: workspace columns body + Raycast palette chrome (#912)
srid May 15, 2026
0c7adb0
fix(police): no-dead-code — remove unused repoColorDot factory
srid May 15, 2026
996de44
refactor(police): elegance — dedupe tokenize/matchesAllTokens via sea…
srid May 15, 2026
6f5bbdd
refactor(police): elegance — dedupe resolvedPr via dockModel
srid May 15, 2026
d74462c
refactor(police): elegance — remove dead typeLabel and extract bodyGr…
srid May 15, 2026
dab6a05
fix(police): fact-check — hoist workspaceGridBody out of createMemo
srid May 15, 2026
3f907bc
fix(police): fact-check — merge open/initialGroup effects, ordered pa…
srid May 15, 2026
c9b5b4d
test(palette): migrate Switch terminal scenarios to Search workspaces
srid May 15, 2026
4767ea9
fix: revert palette filter to substring (AND-tokens matched too lenient)
srid May 15, 2026
10e1f15
test(canvas): migrate Switch-to-terminal panning scenario to Search w…
srid May 15, 2026
de44a78
fix(palette): wider dialog, deduplicate Workspace switcher entry
srid May 15, 2026
254bea6
feat(palette): keyboard nav in workspace grid + overt dock-active row
srid May 17, 2026
1b27ef0
fix(dock): overlay active ring above body so agent rows show it too
srid May 17, 2026
dde7f73
feat(palette): ← → traverse columns in the workspace grid
srid May 17, 2026
c247cb9
fix(palette): scale dialog width with viewport (max-w-3xl was still t…
srid May 17, 2026
b8294b8
fix(palette): give workspace grid columns lateral breathing room for …
srid May 17, 2026
06be3ff
Merge branch 'master' into feat/unified-palette
srid May 17, 2026
11a5902
test(palette): cover keyboard nav + Enter activation in workspace grid
srid May 17, 2026
202c116
refactor(lowy): drop dead PaletteBase.searchText field
srid May 17, 2026
0598e98
refactor(hickey): delete dead searchWorkspaceEntries
srid May 17, 2026
5b37ad7
refactor(hickey): name the workspace-grid slot once
srid May 17, 2026
4c26460
refactor(hickey): route debug actions through CommandDeps
srid May 17, 2026
2dba177
refactor(hickey): introduce PaletteBodyGroup variant
srid May 17, 2026
047f7fb
refactor(hickey): export PaletteMode as the shared mode union
srid May 17, 2026
f6aae1e
refactor(hickey): demote bodyGroup memo to plain function
srid May 17, 2026
a4a930f
refactor(hickey): collapse split cursor into single highlightedId
srid May 17, 2026
d2b2e99
fix(police): no-dead-code — drop tileBg/tileFg from WorkspaceCard
srid May 17, 2026
402442d
refactor(police): elegance — reuse isDrillable in ActionBar.primaryLabel
srid May 17, 2026
31f31bc
refactor(police): elegance — use ts-pattern match in ActionBar.primar…
srid May 17, 2026
254f79b
refactor(police): elegance — drop redundant selectedEntry null guard
srid May 17, 2026
8256280
refactor(police): elegance — flatMap idle sub-buckets, drop defensive…
srid May 17, 2026
b49f6d0
fix(police): no-dead-code — drop unreachable body-group guard in loop
srid May 17, 2026
49effc3
refactor(police): elegance — non-null col[0] after length>0 check
srid May 17, 2026
acbae0f
fix(police): fact-check — correct lg size value in ModalDialog prop doc
srid May 17, 2026
734e988
fix(police): no-change-narration — drop pre-#912 explainer comment
srid May 17, 2026
10fb8b2
docs(website): unify dock+palette copy on landing page
srid May 17, 2026
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Open http://127.0.0.1:7681 (or the address you chose above).

### Terminals

- Create, switch, and kill terminals — every terminal renders as a draggable tile on the canvas, with the left-edge **dock** as the canonical at-a-glance navigator (rail / cards / mega levels)
- Create, switch, and kill terminals — every terminal renders as a draggable tile on the canvas, with the left-edge **dock** as the canonical at-a-glance navigator (rail / cards levels) and the **command palette** as the canonical search surface
- Split terminals — <kbd>Ctrl+&#96;</kbd> splits a bottom pane per terminal; <kbd>Ctrl+Shift+&#96;</kbd> adds tabs, <kbd>Ctrl+PageDown</kbd> / <kbd>Ctrl+PageUp</kbd> cycles
- Font zoom (<kbd>Cmd/Ctrl</kbd> <kbd>+</kbd>/<kbd>-</kbd>), persisted per terminal across sessions
- WebGL rendering with canvas fallback, clickable URLs, Unicode 11, inline images (sixel, iTerm2, kitty)
Expand All @@ -54,12 +54,13 @@ The desktop workspace is mode-less — every terminal renders as a draggable, re
- **Infinite pan & zoom** — two-finger scroll / trackpad to pan, pinch or <kbd>Ctrl+scroll</kbd> to zoom. Hold <kbd>Shift</kbd> to force pan even with the cursor over a terminal tile (hand-tool style). No boundaries — the canvas extends freely in every direction via CSS `transform: translate() scale()` (Figma/Excalidraw model)
- **Snap-to-grid** — tiles snap to a 24px grid on drag and resize for tidy layouts
- **Maximize a tile** — double-click any tile's title bar (or click the maximize button) to fill the viewport; the maximized posture persists across reload via localStorage so you land back where you left off
- **Dock — three-level navigator** — the left-edge dock is the canonical live-terminal surface, with three progressive levels of detail (#903):
- **Dock — two-level navigator** — the left-edge dock is the canonical live-terminal surface, with two progressive levels of detail (#903):
- **Rail** — a narrow strip of repo-colored swatches, one per terminal. Ambient peripheral signal; click any swatch to jump to that terminal.
- **Cards** (default) — recency-sorted rows: awaiting terminals get full cards with a tail of xterm buffer + a reply input wired straight to the PTY, working terminals get compact pills, idle terminals get faded one-liners, parked terminals get the tiniest dim row.
- **Mega** — search + repo facets + agent-state columns (`Idle`, `Awaiting you`, `Working`, `No agent`). The `Idle` column leads and sub-groups by age (`4–12h`, `12–24h`, `24–48h`, `48h+`), the same threshold ladder the minimap window picker uses, so picking `12h` on the minimap and scanning the mega's `12–24h` sub-row are the same triage view in two surfaces. Open with the dock's chevron-up affordance or <kbd>Cmd+Shift+K</kbd> (search auto-focused).

Ordering is **agent recency** (most recent agent semantic-key transition first). Recency is persisted, so a restored session lands where it left off. Click any row or mega card to focus and center its tile. In maximized-tile mode the dock renders as an opaque flush-left sidebar and the maximized terminal reflows next to it (#904), so an agent on another terminal needing attention is still visible.
Workspace search lives in the unified **command palette** (#912): the dock's search-icon button (and <kbd>Cmd+Shift+K</kbd>) opens the palette pre-drilled into the "Search workspaces" group. The group renders its own body inside the palette — a repo-facet sidebar plus agent-state columns (`Idle`, `Awaiting you`, `Working`, `No agent`), with the `Idle` column sub-grouped by age (`4–12h`, `12–24h`, `24–48h`, `48h+`) the same way the minimap window picker shows it. The palette input drives an AND-token query across 20+ metadata fields; the body filters the visible cards live.

Ordering is **agent recency** (most recent agent semantic-key transition first). Recency is persisted, so a restored session lands where it left off. Click any dock row or palette workspace card to focus and center its tile. In maximized-tile mode the dock renders as an opaque flush-left sidebar and the maximized terminal reflows next to it (#904), so an agent on another terminal needing attention is still visible.
- **Dock row border encodes state** — each row's left rail doubles as identity (repo color) and live status: a conic-gradient sweep while the agent is `thinking`/`tool_use`, a breathing pulse while `waiting`, and a flat fade when the terminal is idle/parked
- **Auto-park stale terminals** — when a terminal's last observed agent transition is more than 4 hours old, the dock routes it into the parked bucket (regardless of its prior agent state), the row fades and drops its agent-state border, and the canvas tile fades too — so a wall of "awaiting" tiles you parked yesterday doesn't drown out the one that genuinely needs you now. Parked terminals also stop counting toward the awaiting bucket and are dropped from the OS/PWA dock badge. Any fresh agent transition unparks automatically — pure derivation off `lastActivityAt`, no persisted state
- **Minimap heatmap & activity window** — the canvas minimap dots any tile whose agent is currently `waiting` (alert color) or `thinking`/`tool_use` (accent color), suppressed once the tile falls outside the active window, so you can scan a 20-tile workspace for "who needs me" or "who is making progress" without opening the switcher. A compact pill in the zoom bar shows the current window (`All` by default) and opens a five-option picker — _All / Active in last 4h / 12h / 24h / 48h_; tiles outside the window collapse to small ghost markers so visual weight shifts onto what's still in play. The choice lives in localStorage (per-device)
Expand Down Expand Up @@ -267,7 +268,7 @@ flowchart TB

**Metadata** (dashed lines) — shell activity triggers a provider DAG: CWD changes (OSC 7) → git provider (.git/HEAD watcher) → GitHub provider (`gh pr view` polling). Agent detection uses a single generic orchestrator ([`meta/agent.ts`](packages/server/src/meta/agent.ts)) driven by per-agent `AgentProvider` instances from each integration package. Today three instances are registered: `claudeCodeProvider` (from `kolu-claude-code`) wakes on title events (OSC 2) and its own `fs.watch` on `~/.claude/sessions/`; `codexProvider` (from `kolu-codex`) queries the highest-numbered `~/.codex/state_<N>.sqlite` for thread metadata and tails the matched rollout JSONL for state transitions; `opencodeProvider` (from `kolu-opencode`) queries OpenCode's SQLite database directly and watches its WAL file for live state updates. Adding a new agent CLI is one new `AgentProvider` and one line in `startProviders` — no server-side adapter file. All providers feed a single metadata channel streamed to the client as a subscription[^providers]. Separately, kolu's preexec hook emits an `OSC 633;E` command mark before each user command; the pty handler republishes the raw payload on a `commandRun` channel, and [`meta/agent-command.ts`](packages/server/src/meta/agent-command.ts) subscribes to match the first token against a known-agents allowlist and fan out to both (a) a bounded recent-agents MRU for the agent-aware command palette and (b) a per-terminal stash keyed by terminal id, so codex/opencode session detection still matches when the agent is an interpreter shim (e.g. npm-installed `codex`, whose kernel-level process name is `node`). No `/proc` lookups or argv scraping.

**User actions** — command palette, workspace switcher, and tile chrome dispatch plain oRPC client calls ([`useTerminalCrud`](packages/client/src/terminal/useTerminalCrud.ts), [`useWorktreeOps`](packages/client/src/terminal/useWorktreeOps.ts)). The server's live subscriptions push updated state to the client automatically. [`useTerminalMetadata`](packages/client/src/terminal/useTerminalMetadata.ts) uses SolidJS's `mapArray` to create per-terminal subscriptions that automatically tear down when terminals are removed[^client-state].
**User actions** — the unified command palette (Cmd+K for commands; Cmd+Shift+K or the dock's search-icon button to drill into "Search workspaces") and tile chrome dispatch plain oRPC client calls ([`useTerminalCrud`](packages/client/src/terminal/useTerminalCrud.ts), [`useWorktreeOps`](packages/client/src/terminal/useWorktreeOps.ts)). The server's live subscriptions push updated state to the client automatically. [`useTerminalMetadata`](packages/client/src/terminal/useTerminalMetadata.ts) uses SolidJS's `mapArray` to create per-terminal subscriptions that automatically tear down when terminals are removed[^client-state].

[^lazy-attach]: ~4 KB serialized snapshot instead of replaying the full scrollback buffer.

Expand Down
33 changes: 20 additions & 13 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ const App: Component = () => {
const rightPanel = useRightPanel();
const { colorScheme } = useColorScheme();

// Workspace-switcher feeds — desktop and mobile share the same
// accessors. The mega-level model lives in `buildDockModel` (consumed
// inside `Dock`); row order is shared via `rankDockRows` so the
// `Cmd+1..9` shortcut targets the same row the dock paints.
// Workspace search feeds — the live-terminal source list and recency
// accessor consumed by the unified command palette's "Search
// workspaces" group. `rankDockRows` shares row order with the dock
// so the `Cmd+1..9` shortcut targets the same row the dock paints.
const workspaceEntries = createMemo(() =>
buildWorkspaceEntries(
store.terminalIds(),
Expand Down Expand Up @@ -120,11 +120,6 @@ const App: Component = () => {
// Shortcuts help overlay state
const [shortcutsHelpOpen, setShortcutsHelpOpen] = createSignal(false);

// Impulse signal — Mod+Shift+K bumps it; the dock listens and
// opens its mega level (search + repo facets + columns). Replaces the
// chrome-bar workspace switcher's open-request wiring.
const [dockMegaOpenRequest, setDockMegaOpenRequest] = createSignal(0);

// About dialog state
const [aboutOpen, setAboutOpen] = createSignal(false);

Expand Down Expand Up @@ -200,7 +195,7 @@ const App: Component = () => {
void crud.handleCreateSubTerminal(parentId, cwd),
openNewTerminalMenu: () => openPaletteGroup("New terminal"),
openWorkspaceSwitcher: () => {
if (!isMobile()) setDockMegaOpenRequest((n) => n + 1);
if (!isMobile()) openPaletteGroup("Search workspaces");
},
setPaletteOpen,
setShortcutsHelpOpen,
Expand Down Expand Up @@ -280,10 +275,22 @@ const App: Component = () => {
if (id) closeTerminal(id);
},
handleCloseAll: () => void crud.handleCloseAll(),
handleTriggerServerError: () =>
void client.terminal.resize({
id: "00000000-0000-0000-0000-000000000000",
cols: 1,
rows: 1,
}),
handleClearLocalStorage: () => {
localStorage.clear();
location.reload();
},
simulateAlert: alerts.simulateAlert,
isMobile,
canvasCenterActive: handleCanvasCenterActive,
canvasAutoArrange: arrange.handleCanvasAutoArrange,
workspaceEntries,
recencyOf,
});

// Reset state on close and return focus to terminal
Expand Down Expand Up @@ -535,9 +542,9 @@ const App: Component = () => {
onAutoArrange={arrange.handleCanvasAutoArrange}
onSelect={store.setActiveSilently}
onClose={(id) => closeTerminal(id)}
workspaceEntries={workspaceEntries()}
getRecency={recencyOf}
openMegaRequest={dockMegaOpenRequest()}
onOpenWorkspaceSearch={() =>
openPaletteGroup("Search workspaces")
}
onCreate={() => openPaletteGroup("New terminal")}
renderTileTitle={(id) => (
<TerminalMeta info={store.getDisplayInfo(id)} />
Expand Down
Loading