Files
patterm/internal/app/layout.go
Harry Bayliss 3622c41fd0 Land staged session/MCP/chrome work + sidebar clear-J fix
This batches the in-flight [Unreleased] block from CHANGELOG.md into a
single commit. Highlights:

- Real MCP protocol layer (initialize / tools/list / tools/call) so
  vendor MCP clients can complete the handshake against the per-PID
  socket. Legacy direct-dispatch preserved for the harness.
- New mcp_injection kinds — cli_override for codex, config_env for
  opencode — joining the existing env-var and config_file paths so
  patterm can slot into more agents without touching their real
  config or auth.
- Ctrl+A/D and Ctrl+W/S focus navigation across tabs and intra-tab
  process lists, recognised in legacy / kitty CSI u / xterm
  modifyOtherKeys encodings.
- Palette macros (sw / k / sp ) and reordering so open sessions
  surface above spawn-new entries.
- Two-row tab bar, sidebar/tabbar/status chrome cache, viewport-wipe
  on agent spawn, CR-terminated orchestrator injections, and split-
  Enter PTY writes so paste-detecting TUIs see Enter as a key event.

Also fixes the bug logged in TODO: claude's Ctrl+O tool-call expansion
emits CSI 0 J, which the viewport renderer was forwarding verbatim —
wiping the sidebar to the right of the cursor and leaving the chrome
cache convinced nothing had changed. CSI 0 J and CSI 1 J are now
translated into per-row ECH sequences clamped to the viewport, same
as CSI 2 J and CSI K already were.

Agent guides (CLAUDE.md / AGENTS.md) now spell out the
TODO->CHANGELOG workflow so completed items land in the changelog
rather than as ticked entries left behind in TODO.
2026-05-14 19:09:35 +01:00

78 lines
1.5 KiB
Go

package app
// terminalLayout is the single source of truth for host chrome and the
// child PTY viewport.
type terminalLayout struct {
hostCols uint16
hostRows uint16
mainLeft uint16
mainTop uint16
mainCols uint16
mainRows uint16
sidebarVisible bool
sidebarLeft uint16
sidebarWidth uint16
statusRow uint16
}
func newTerminalLayout(cols, rows uint16) terminalLayout {
if cols == 0 {
cols = 1
}
if rows == 0 {
rows = 1
}
l := terminalLayout{
hostCols: cols,
hostRows: rows,
mainLeft: 1,
mainTop: tabBarRows + 1,
mainCols: cols,
mainRows: 1,
statusRow: rows,
}
if int(cols) > sidebarCols+10 {
l.sidebarVisible = true
l.sidebarWidth = sidebarCols
l.sidebarLeft = cols - sidebarCols + 1
// The sidebar's left border lives one column to the left of
// sidebarLeft. The viewport must stop one column short of that
// border or child output (and clearViewport ECH) would erase
// it whenever the cursor reached the right margin.
l.mainCols = cols - sidebarCols - 1
}
reservedRows := tabBarRows + statusRows
if int(rows) > reservedRows {
l.mainRows = rows - uint16(reservedRows)
}
return l
}
func (l terminalLayout) childCols() uint16 {
if l.mainCols == 0 {
return 1
}
return l.mainCols
}
func (l terminalLayout) childRows() uint16 {
if l.mainRows == 0 {
return 1
}
return l.mainRows
}
func ptyRows(hostRows uint16) uint16 {
return newTerminalLayout(1, hostRows).childRows()
}
func ptyCols(hostCols uint16) uint16 {
return newTerminalLayout(hostCols, 1).childCols()
}