Skip to content
Merged
Show file tree
Hide file tree
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [Unreleased]

### Added

- `[ui].placement` for how Sessionizer / Worktree pickers open in Herdr: `overlay`, `split`, or `popup` (popup requires Herdr `>= 0.7.4`)
- Optional `[ui].width` / `[ui].height` for popup outer size (terminal cells or percentages such as `"80%"`)

### Changed

- Picker placement moves from `[layout].placement` to `[ui]`; workspace bootstrap `[layout]` now only carries `focus` (no fallback for the old key)
- New default config uses `[ui].placement = "popup"` with `width` / `height` = `"80%"`
- Placement is no longer required when `[tabs]` are defined (omitted `[ui]` still falls back to `overlay`)
- Minimum Herdr version is now `0.7.4` (`min_herdr_version` + README prerequisites; required for popup picker placement)

## [0.6.2] - 2026-07-26

### Added
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Inspired by [ThePrimeagen's tmux-sessionizer](https://github.com/ThePrimeagen/tm

Sessionizer does not install system tools for you.

- [Herdr](https://herdr.dev/) `>= 0.7.0`
- [Herdr](https://herdr.dev/) `>= 0.7.4`
- [Bun](https://bun.sh/) — plugin build and runtime
- [fzf](https://github.com/junegunn/fzf) — interactive pickers

Expand Down Expand Up @@ -123,10 +123,14 @@ When Sessionizer **creates** a new project or worktree workspace, it applies the

Created automatically on first run if missing.

`[layout]` and `[tabs]` are optional. A config with only `[projects]` is
`[ui]`, `[layout]`, and `[tabs]` are optional. A config with only `[projects]` is
valid: new workspaces then open with a plain shell and no layout is applied.
When `[tabs]` sections exist, `[layout].placement` and `[layout].focus` are
required.
When `[tabs]` sections exist, `[layout].focus` is required.

`[ui]` controls how Sessionizer / Worktree **pickers** open inside Herdr (not
workspace bootstrap). New configs default to a session-modal `popup` at `80%`
width/height (Herdr `>= 0.7.4`). You can switch to `overlay` or `split`, or omit
`[ui]` entirely to fall back to `overlay`.

If you want an agent to help edit either the global config or a repo-local override, see [Agent skills](#agent-skills).

Expand All @@ -138,8 +142,12 @@ roots = ["~/Projects", "~/Workspace"]
git_only = true
depth = 1

[ui]
placement = "popup" # overlay | split | popup (popup needs Herdr >= 0.7.4)
width = "80%"
height = "80%"

[layout]
placement = "overlay"
focus = "editor"

[tabs.dev]
Expand Down Expand Up @@ -202,7 +210,8 @@ Second tab shape:
- `[projects].roots` — parent folders scanned by both pickers (plain paths; optional globs — see [Glob roots](#glob-roots-optional) below)
- `[projects].git_only` — `true` returns only directories with `.git` metadata; `false` lists all immediate child folders
- `[projects].depth` — maximum levels below each root to scan when `git_only = true`; `1` means immediate children
- `[layout].placement` — how plugin panes open (`overlay` or `split`)
- `[ui].placement` — how Sessionizer / Worktree pickers open in Herdr (`overlay`, `split`, or `popup`; new configs default to `popup`, which needs Herdr `>= 0.7.4`)
- `[ui].width` / `[ui].height` — popup outer size (cells or `"80%"`); only with `placement = "popup"` (new configs default to `"80%"`)
- `[layout].focus` — which tab or pane to focus after layout bootstrap
- `[tabs.<name>]` — one Herdr tab to create per section
- `[[tabs.<name>.panes]]` — panes inside the tab; `from` + `split` (`right` or `down`) define the split tree
Expand Down Expand Up @@ -264,7 +273,7 @@ When Sessionizer or Worktree creates a new workspace at `cwd`, Sessionizer check
1. `<cwd>/.sessionizer/config.toml` — if present, use its `[layout].focus` and `[tabs.*]` (full replacement; no merge with global tabs)
2. Global `config.toml` — default layout

`[projects].roots` and `[layout].placement` always come from the global config. Repo-local files may include those sections, but they are ignored. Invalid repo-local config fails with an error that names the file path.
`[projects].roots` and `[ui]` (picker placement/size) always come from the global config. Repo-local files may include those sections, but they are ignored. Invalid repo-local config fails with an error that names the file path.

| Event | Layout source |
| ------------------------------------------- | ---------------------------------------------------- |
Expand Down
21 changes: 13 additions & 8 deletions docs/prd/per-repo-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Related: #12, ADR-0001, `src/config.ts`, `src/sessionizer.ts`, `src/worktree.ts`

Today every newly created workspace uses the same global layout from the plugin config dir (`HERDR_PLUGIN_CONFIG_DIR/config.toml`). This PRD adds optional **repo-local layout overrides** at `<project>/.sessionizer/config.toml` so each repository can declare its own tabs, panes, and focus target when Sessionizer or Worktree bootstraps a **new** workspace.

Global config continues to own `[projects].roots` and `[layout].placement`. Repo-local config owns only the layout slice: `[layout].focus` and `[tabs.*]`.
Global config continues to own `[projects].roots` and picker UI (`[ui].placement`). Repo-local config owns only the layout slice: `[layout].focus` and `[tabs.*]`.

## Goals

Expand Down Expand Up @@ -38,7 +38,7 @@ $HERDR_PLUGIN_CONFIG_DIR/config.toml
Required global fields (unchanged):

- `[projects].roots`
- `[layout].placement`
- `[ui].placement` (optional; defaults to `overlay`)
- `[layout].focus` and `[tabs.*]` — used as **default layout** when no repo override exists

### Repo-local override
Expand All @@ -55,7 +55,7 @@ Allowed fields in repo-local file:
Ignored if present in repo-local file (read from global only):

- `[projects]`
- `[layout].placement`
- `[ui]`

### Lookup order at bootstrap time

Expand All @@ -64,15 +64,20 @@ Given `layoutCwd` (absolute project or worktree checkout path):
1. If `join(layoutCwd, '.sessionizer', 'config.toml')` exists → parse as layout override
2. Else → use `focus` + `tabs` from global `config.toml`

`projects.roots` and `layout.placement` always come from global config.
`projects.roots` and `ui.placement` always come from global config.

### API shape (proposed)

```ts
// src/config.ts
export interface SessionizerConfig {
projects: { roots: string[] };
layout: { placement: PanePlacement; focus: string };
ui: {
placement: PanePlacement;
width?: number | string;
height?: number | string;
};
layout: { focus: string };
tabs: TabConfig[];
}

Expand All @@ -81,8 +86,8 @@ export function loadConfig(): SessionizerConfig; // global; unchanged call sites
export function resolveLayoutConfig(
layoutCwd: string,
global?: SessionizerConfig
): Pick<SessionizerConfig, "layout" | "tabs">;
// Returns merged config: global.placement + resolved focus/tabs
): SessionizerConfig;
// Returns merged config: global.ui + resolved focus/tabs
```

`resolveLayoutConfig` throws with a message that includes the repo-local path on parse/validation failure.
Expand Down Expand Up @@ -208,4 +213,4 @@ Use `mkdtemp` fixtures; no dependency on real `llm-wiki` path in unit tests.
| Partial overrides | No — full layout slice replacement |
| Invalid repo config | Fail loud |
| `[projects].roots` in repo file | Ignored |
| `layout.placement` in repo file | Ignored; global only |
| `ui` in repo file | Ignored; global only |
2 changes: 1 addition & 1 deletion docs/prd/worktree-close.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ The action entry point follows the same thin-wrapper pattern as `src/open-worktr

- `src/worktree-close.ts` — the core flow, runtime-injected for testability. Mirrors the structure of `src/worktree.ts`.
- `src/worktree-close-pane.ts` — one-liner pane entry point: imports `runWorktreeClose`, calls it with the default runtime, logs and exits 1 on failure. Mirrors `src/sessionizer-pane.ts` (no env-var scaffolding, since the close flow takes no caller-supplied arguments).
- `src/open-worktree-close-pane.ts` — the action entry point. Mirrors `src/open-worktree-pane.ts`; opens the `worktree-close` pane using `config.layout.placement`.
- `src/open-worktree-close-pane.ts` — the action entry point. Mirrors `src/open-worktree-pane.ts`; opens the `worktree-close` pane using `config.ui.placement`.
- `src/ui/workspace-row.ts` — extracted `workspaceRow` and `extractWorkspaceId` (currently private to `src/sessionizer.ts`). Adding `repo_name` brings the column count from 7 to 8.

### Modified files
Expand Down
2 changes: 1 addition & 1 deletion herdr-plugin.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
id = "sessionizer"
name = "Sessionizer"
version = "0.6.2"
min_herdr_version = "0.7.0"
min_herdr_version = "0.7.4"
description = "Inspired by ThePrimeagen's tmux-sessionizer: fuzzy pickers to open projects and Git worktrees into Herdr workspaces."
platforms = ["macos", "linux"]

Expand Down
35 changes: 26 additions & 9 deletions skills/sessionizer-layout-editor/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: sessionizer-layout-editor
description: Sessionizer config edits. Use when the user wants project roots, git_only, or depth; global tabs, panes, focus, or split ratios; or a repo-local .sessionizer override.
description: Sessionizer config edits. Use when the user wants project roots, git_only, or depth; global tabs, panes, focus, or split ratios; picker [ui].placement (overlay|split|popup) or size; or a repo-local .sessionizer override.
---

# Sessionizer Layout Editor
Expand All @@ -12,31 +12,48 @@ description: Sessionizer config edits. Use when the user wants project roots, gi
| global | `~/.config/herdr/plugins/config/sessionizer/config.toml` |
| repo-local | `<repo>/.sessionizer/config.toml` |

**Bootstrap** — layout changes apply only when Sessionizer or Worktree creates a **new** workspace, not on reopen.
**Bootstrap** — layout changes (`[layout].focus`, `[tabs.*]`) apply only when Sessionizer or Worktree creates a **new** workspace, not on reopen. **Picker UI** (`[ui]`) applies every time a picker opens and is global-only.

## Workflow

1. Read the existing target file if present.
2. Enforce **scope**:
- **repo-local**: `[layout].focus` and `[tabs.*]` only — no `[projects]`, no `[layout].placement`; tabs fully replace global (no merge)
- **global**: `[projects]`, `[layout]`, and tabs/panes
- **repo-local**: `[layout].focus` and `[tabs.*]` only — no `[projects]`, no `[ui]`; tabs fully replace global (no merge)
- **global**: `[projects]`, `[ui]`, `[layout]`, and tabs/panes
3. Build or edit layout:
- `[layout].focus` required
- `[layout].focus` required when tabs exist
- `[tabs.<name>]` with `label` and `[[tabs.<name>.panes]]`
- first pane: no `from`; later panes: `from` + `split` (`right` or `down`); optional `ratio` in `(0, 1)` on the split axis
- pane ids unique per tab; `focus` must name an existing tab or pane
4. **Discovery** edits (global only): follow [references/discovery.md](references/discovery.md)
5. Minimal diff — change only what the user asked for; stay in the chosen **scope**
6. Done when: TOML is valid, scope rules hold, and you summarized the layout plus the **bootstrap** reminder
4. **Picker UI** edits (global only):
- `[ui].placement` is `overlay` | `split` | `popup` (popup needs Herdr `>= 0.7.4`)
- optional `[ui].width` / `[ui].height` only with `popup` (cells or `"80%"`)
- **do not** put placement under `[layout]`
- if the file still has legacy `[layout].placement`, **move it to `[ui].placement` and remove the old key** (no dual-write)
- new seeded configs default to `placement = "popup"`, `width = "80%"`, `height = "80%"`; omitted `[ui]` falls back to `overlay` at runtime
5. **Discovery** edits (global only): follow [references/discovery.md](references/discovery.md)
6. Minimal diff — change only what the user asked for; stay in the chosen **scope**. When editing global layout or discovery, **preserve existing `[ui]`** (and `[projects]` for layout edits) unless the user asked to change them.
7. Done when: TOML is valid, scope rules hold, and you summarized the layout/UI change plus the **bootstrap** reminder when layout tabs changed

## Examples

- "Add `~/Work` to my project roots" → global `[projects].roots`
- "Add `~/Projects/github.com/*` to my project roots" → global `[projects].roots`; globs expand at use-time — see reference
- "Set `git_only = false`" → global discovery; see reference
- "Open pickers as a popup" → global `[ui].placement = "popup"` (optional width/height; default size is Herdr half-size if omitted)
- "Use the default popup size" → `placement = "popup"`, `width = "80%"`, `height = "80%"`
- "Add a repo-local override with lazygit + copilot" → repo-local file
- "Make the right pane 30% with `ratio = 0.3`" → layout pane edit in the active **scope**

## Global `[ui]` snippet

```toml
[ui]
placement = "popup" # overlay | split | popup
width = "80%"
height = "80%"
```

## Repo-local template

```toml
Expand All @@ -59,4 +76,4 @@ split = "right"
command = "copilot"
```

For global layout edits, preserve the existing `[projects]` section unless discovery is in **scope**.
For global layout edits, preserve the existing `[projects]` and `[ui]` sections unless those are in **scope**.
Loading
Loading