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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions .config/wk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
open_workspace_cmd: "./.config/wt/start.sh"
restart_workspace_cmd: "./.config/wt/start.sh --restart"
5 changes: 5 additions & 0 deletions .config/wt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[post-create]
deps = "uv sync && cd app/web_ui && npm install"

[pre-remove]
session = "zellij delete-session {{ branch | sanitize }} 2>/dev/null || true"
169 changes: 169 additions & 0 deletions .config/wt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Worktree Workflow

We use [Worktrunk](https://worktrunk.dev/) to manage git worktrees for parallel development. Each worktree gets its own Zellij session with dedicated ports, a backend server, a frontend dev server, and a coding agent.

## Setup

Run `utils/setup_env.sh` — it installs project dependencies and optionally sets up workspaces (worktrunk, Zellij, and config).

Or manually:

1. Install worktrunk: `brew install worktrunk && wt config shell install`
2. Install Zellij: `brew install zellij`
3. Install worktree TUI: `uv tool install "git+https://github.com/scosman/worktree_tui"`
4. Configure worktree path (one-time, applies to all repos):

```bash
mkdir -p ~/.config/worktrunk
echo 'worktree-path = ".worktrees/{{ branch | sanitize }}"' > ~/.config/worktrunk/config.toml
```

## Command

Just run `wk` and you can manage workspaces!

## Advanced Commands

### Create a new worktree and launch it

```bash
wt switch --create my-feature -x .config/wt/start.sh
```

This creates a branch, sets up the worktree, installs dependencies (via `post-create` hook), then launches a Zellij session with all dev tools.

To branch from something other than `main`:

```bash
wt switch --create my-feature --base other-branch -x .config/wt/start.sh
```

### Launch an existing worktree

```bash
wt switch my-feature -x .config/wt/start.sh
```

If the worktree already exists, this switches to it and launches Zellij. If the worktree was removed but the branch exists, it re-creates the worktree first.

### Switch to a worktree (cd only, no Zellij)

```bash
wt switch my-feature
```

### List all worktrees

```bash
wt list
```

With CI status and line diffs:

```bash
wt list --full
```

Include branches that don't have worktrees:

```bash
wt list --branches
```

### Interactive picker

```bash
wt switch
```

Running `wt switch` with no arguments opens an interactive picker with live diff/log previews.

### Remove a worktree

From inside the worktree:

```bash
wt remove
```

From anywhere:

```bash
wt remove my-feature
```

Force-remove with unmerged commits:

```bash
wt remove -D my-feature
```

### Merge a feature branch

From inside the feature worktree — squashes, rebases, fast-forward merges to main, and cleans up:

```bash
wt merge
```

Merge to a different target:

```bash
wt merge develop
```

Keep the worktree after merging:

```bash
wt merge --no-remove
```

### Quick navigation

```bash
wt switch - # Previous worktree (like cd -)
wt switch ^ # Default branch (main)
wt switch pr:123 # GitHub PR #123
```

### View full diff of a branch

All changes since branching (committed + uncommitted):

```bash
wt step diff
```

### Commit with LLM-generated message

```bash
wt step commit
```

### Cleanup merged branches

```bash
wt step prune
```

## Keybinds for Option Left/right to switch tabs

In `~/.config/zellij/config.kdl` add:

```
keybinds clear-defaults=true {
shared_except "locked" {
bind "Alt b" { GoToPreviousTab; }
bind "Alt f" { GoToNextTab; }
}
}
```

## Architecture

- `.config/wt.toml` — project hooks: dependency install on create, Zellij session cleanup on remove
- `.config/wt/start.sh` — launches a Zellij session with per-branch ports (via `hash_port`)
- `.config/wt/layout.kdl` — Zellij layout: terminal, coding agent, backend server, frontend dev server
- `.config/wt/bin/web` — opens the worktree's web UI in a browser (type `web` in the terminal tab)
- `.config/wt/user_settings.sh` — per-user overrides (gitignored); copy from `user_settings.sh.example`
- `.config/wt/config.toml` — worktrunk user config (worktree path template)
7 changes: 7 additions & 0 deletions .config/wt/bin/web
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
URL="${KILN_WEB_URL:-http://localhost:8758}"
if [[ "$(uname)" == "Darwin" ]]; then
open "$URL"
else
xdg-open "$URL"
fi
1 change: 1 addition & 0 deletions .config/wt/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
worktree-path = ".worktrees/{{ branch | sanitize }}"
31 changes: 31 additions & 0 deletions .config/wt/layout.kdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
layout {
default_tab_template {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
children
pane size=2 borderless=true {
plugin location="zellij:status-bar"
}
}
tab name="term" focus=true {
pane name="terminal" command="bash" start_suspended=false {
args "-c" "echo \"\nWeb UI: $KILN_WEB_URL\nBackend: http://localhost:$KILN_PORT\n\nType 'web' to open in browser.\n\"; exec $SHELL"
}
}
tab name="agent" {
pane command="bash" start_suspended=false {
args "-c" "$KILN_CODER_CMD"
}
}
tab name="backend" {
pane command="bash" start_suspended=false {
args "-c" "uv run python -m app.desktop.dev_server"
}
}
tab name="webui" cwd="app/web_ui" {
pane command="bash" start_suspended=false {
args "-c" "npm run dev -- --host --port $KILN_FRONTEND_PORT"
}
}
}
54 changes: 54 additions & 0 deletions .config/wt/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

RESTART=false
if [ "${1:-}" = "--restart" ] || [ "${1:-}" = "-r" ]; then
RESTART=true
shift
fi

BRANCH="${1:-$(git -C "$REPO_ROOT" branch --show-current 2>/dev/null || echo "main")}"
SESSION_NAME="${BRANCH//\//-}"

hash_port() {
printf '%s' "$1" | cksum | awk '{print ($1 % 10000) + 10000}'
}
PORT=$(hash_port "$BRANCH")

export KILN_PORT="$PORT"
export KILN_FRONTEND_PORT="$((PORT + 1))"
export VITE_API_PORT="$PORT"

export KILN_WEB_URL="http://localhost:$KILN_FRONTEND_PORT"

export KILN_CODER_CMD="claude"
if [ -f "$REPO_ROOT/.config/wt/user_settings.sh" ]; then
# shellcheck source=/dev/null
source "$REPO_ROOT/.config/wt/user_settings.sh"
fi

export PATH="$REPO_ROOT/.config/wt/bin:$PATH"

printf '\e]0;FEAT: %s\a' "$BRANCH"

cd "$REPO_ROOT"

LAYOUT="$REPO_ROOT/.config/wt/layout.kdl"

SESSION_STATUS=$(zellij list-sessions --no-formatting 2>/dev/null \
| awk -v name="$SESSION_NAME" '{ n=$1; gsub(/[[:space:]]/, "", n); if (n == name) { print (/EXITED/ ? "exited" : "active"); exit } }') || true

if $RESTART; then
zellij kill-session "$SESSION_NAME" 2>/dev/null || true
zellij delete-session "$SESSION_NAME" 2>/dev/null || true
exec zellij -s "$SESSION_NAME" -n "$LAYOUT"
fi

if [ -n "$SESSION_STATUS" ]; then
exec zellij attach "$SESSION_NAME"
else
exec zellij -s "$SESSION_NAME" -n "$LAYOUT"
fi
9 changes: 9 additions & 0 deletions .config/wt/user_settings.sh.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Per-user settings for the Kiln dev environment.
# Copy this file to user_settings.sh and customize:
# cp user_settings.sh.example user_settings.sh
#
# user_settings.sh is gitignored — your changes stay local.

# Coding agent command (default: claude)
# Examples: "claude", "cursor .", "aider", "zed ."
KILN_CODER_CMD="claude"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ libs/server/build
dist/

.mcp.json

.config/wt/user_settings.sh
.worktrees/
4 changes: 3 additions & 1 deletion app/desktop/dev_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
if __name__ == "__main__":
setup_resource_limits()

port = int(os.environ.get("KILN_PORT", "8757"))

uvicorn.run(
"app.desktop.dev_server:dev_app",
host="127.0.0.1",
port=8757,
port=port,
reload=True,
# Debounce when changing many files (changing branch)
reload_delay=0.1,
Expand Down
3 changes: 2 additions & 1 deletion app/web_ui/src/lib/api_client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import createClient from "openapi-fetch"
import type { paths } from "./api_schema"

export const base_url = "http://localhost:8757"
const api_port = import.meta.env.VITE_API_PORT || "8757"
export const base_url = `http://localhost:${api_port}`

export const client = createClient<paths>({
baseUrl: base_url,
Expand Down
10 changes: 5 additions & 5 deletions hooks_mcp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,20 @@ prompts:

- name: "python_test_guide.md"
description: "Guide for testing best practices for python code"
prompt-file: "agents/python_test_guide.md"
prompt-file: ".agents/python_test_guide.md"

- name: "frontend_design_guide.md"
description: "Guide for UI design including color, typography, and controls"
prompt-file: "agents/frontend_design_guide.md"
prompt-file: ".agents/frontend_design_guide.md"

- name: "frontend_controls.md"
description: "Guide for UI controls available in the web app (spinners, buttons, dialogs, etc)"
prompt-file: "agents/frontend_controls.md"
prompt-file: ".agents/frontend_controls.md"

- name: "tables_style.md"
description: "Guide for css table styling"
prompt-file: "agents/tables_style.md"
prompt-file: ".agents/tables_style.md"

- name: "card_style.md"
description: "Guide for css card styling"
prompt-file: "agents/card_style.md"
prompt-file: ".agents/card_style.md"
10 changes: 6 additions & 4 deletions libs/server/kiln_server/server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import os
from importlib.metadata import version
from typing import Sequence

Expand Down Expand Up @@ -45,11 +46,12 @@ def ping():
connect_document_api(app)
connect_custom_errors(app)

frontend_port = os.environ.get("KILN_FRONTEND_PORT", "5173")
allowed_origins = [
"http://localhost:5173",
"http://127.0.0.1:5173",
"https://localhost:5173",
"https://127.0.0.1:5173",
f"http://localhost:{frontend_port}",
f"http://127.0.0.1:{frontend_port}",
f"https://localhost:{frontend_port}",
f"https://127.0.0.1:{frontend_port}",
]

app.add_middleware(
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ kiln-studio-desktop = { workspace = true }
kiln-ai = { workspace = true }

[tool.ruff]
exclude = []
exclude = [".worktrees"]

[tool.ty.src]
exclude = ["**/test_*.py", "app/desktop/build/**", "app/web_ui/**", "**/.venv", "**/kiln_ai_server_client/**"]
exclude = [".worktrees", "**/test_*.py", "app/desktop/build/**", "app/web_ui/**", "**/.venv", "**/kiln_ai_server_client/**"]

[tool.ty.rules]
invalid-key = "ignore"
Expand Down
Loading
Loading