Release v0.0.1
Some checks failed
release / build-linux-amd64 (push) Failing after 10m52s

Bundles the in-flight work into the first tagged release. See
CHANGELOG.md `[0.0.1] - 2026-05-14` for the full per-change list.
Highlights:

- Sidebar / chrome stability: clamp absolute cursor positioning and
  printable bytes to the viewport so long-running TUIs (claude, codex)
  can't spray into the right rail; bound tab bar's row clear to the
  viewport width so the rail isn't wiped on every tab redraw; flag
  scroll escapes (RI/IND/NEL/SU/SD/IL/DL) and clamp `CSI 0/1/2 J`/`K`
  to viewport columns.
- Palette: "Spawn process…" form, macros (`sw `, `k `, `sp `), kill
  entries mark the focused tab, dead agents drop out of the switch
  list.
- Sidebar: split into Processes (session-wide) + Agent Tree
  (per-active-agent) sections; relaunch indicator; Ctrl+W/S walks the
  combined list, Ctrl+A/D steps tabs.
- MCP: protocol handshake (`initialize`, `tools/list`, `tools/call`,
  `ping`), `mcp_injection.kind = cli_override / config_env` so codex
  and opencode pick up the server with no file writes, `lifecycle`
  help topic and tool-description cleanup-duty pointers.
- Lifecycle: orchestrator-spawned children cascade-killed when the
  parent dies; orchestrator-injected prompts end with CR + delayed
  Enter so claude submits cleanly.
This commit is contained in:
2026-05-14 22:04:32 +01:00
parent 63f0ddcb38
commit 52e06c914e
18 changed files with 1031 additions and 62 deletions

View File

@@ -23,6 +23,7 @@ import (
type cursorShifter struct {
rowOffset int
childRows int // viewport height in child rows; used for DECSTBM resets
childCols int // viewport width in child cols; used to clamp CUP/HVP/CHA/HPA columns
state shifterState
buf []byte // bytes accumulated in current escape sequence (incl. introducer)
@@ -45,13 +46,25 @@ const (
stSOSPMAPCEsc
)
func newCursorShifter(rowOffset, childRows int) *cursorShifter {
return &cursorShifter{rowOffset: rowOffset, childRows: childRows}
func newCursorShifter(rowOffset, childRows, childCols int) *cursorShifter {
return &cursorShifter{rowOffset: rowOffset, childRows: childRows, childCols: childCols}
}
func (cs *cursorShifter) SetGeometry(rowOffset, childRows int) {
func (cs *cursorShifter) SetGeometry(rowOffset, childRows, childCols int) {
cs.rowOffset = rowOffset
cs.childRows = childRows
cs.childCols = childCols
}
// clampCol returns col clamped to the viewport's rightmost column, so a
// child that drifted into believing it has more horizontal space than
// patterm assigned it can't reach into the sidebar. childCols == 0 (an
// uninitialised shifter, only seen in tests) disables clamping.
func (cs *cursorShifter) clampCol(col int) int {
if cs.childCols > 0 && col > cs.childCols {
return cs.childCols
}
return col
}
// Shift consumes a chunk of PTY-master bytes, applies row offsets to
@@ -194,11 +207,24 @@ func (cs *cursorShifter) emitCSI() {
return
}
r += cs.rowOffset
c = cs.clampCol(c)
cs.pending.WriteString("\x1b[")
cs.pending.WriteString(strconv.Itoa(r))
cs.pending.WriteByte(';')
cs.pending.WriteString(strconv.Itoa(c))
cs.pending.WriteByte(final)
case 'G', '`':
// CHA / HPA: absolute column. Clamp to the viewport so a stale
// child width can't reach into the sidebar.
c, ok := parseOneParam(paramsRaw, 1)
if !ok {
cs.pending.Write(cs.buf)
return
}
c = cs.clampCol(c)
cs.pending.WriteString("\x1b[")
cs.pending.WriteString(strconv.Itoa(c))
cs.pending.WriteByte(final)
case 'd':
// VPA: row.
r, ok := parseOneParam(paramsRaw, 1)