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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Stable Tmux Socket Directory** - Moved tmux sockets from `/tmp/tmux-{uid}/` to `~/.claudio/sockets/` via `TMUX_TMPDIR` to prevent macOS periodic `/tmp` cleanup from killing active tmux servers. `ListClaudioSockets` checks both locations for backward compatibility.

### Changed
- **Ship Experimental Features** - Graduated intelligent naming, terminal support, inline multiplan, inline ultraplan, and grouped instance view from experimental to default. These features are now always enabled without configuration. Only subprocess mode remains experimental.
- **Ship Experimental Features** - Graduated intelligent naming, inline multiplan, inline ultraplan, and grouped instance view from experimental to default. These features are now always enabled without configuration. Only subprocess mode remains experimental.
- **Extract `createTmuxSession()` Helper** - Extracted duplicated tmux session setup from `Start()` and `StartWithResume()` into a reusable `createTmuxSession()` method, eliminating ~40 lines of duplication.
- **Extract `buildInstanceCallbacks()` Helper** - Consolidated duplicated callback wiring between `newInstanceManager` and `newInstanceManagerWithBackend` into a shared method to prevent sync bugs when adding new callbacks.
- **Log tmux session option errors** - Replaced silent `_ =` error discards in `createTmuxSession` and recovery paths with Debug/Warn-level logging for better diagnostics.

### Removed
- **Terminal Pane Feature** - Removed the in-TUI terminal pane. Deleted the `internal/tui/terminal/` package, the `view/terminal.go` view, the `` ` ``/`T`/`Ctrl+Shift+T` key bindings, the `:term`/`:t`/`:termdir` commands, the `TERMINAL` mode indicator/help badge, and the `input.ModeTerminal` routing case. Layout math that previously lived on the terminal manager now uses flat `width`/`height` fields on the TUI model.
- **Codex Backend Support** - Removed Codex CLI backend support. Claudio now exclusively uses Claude Code as its AI backend. All Codex-specific configuration (`ai.codex.*`), backend implementation, validation, TUI settings, and documentation have been removed.
- **Stop Command (`:x` / `:stop`)** - Removed the `:x` / `:stop` command and its `auto_pr_on_stop` config option. Use `:e` / `:exit` to stop instances, and `claudio pr` for PR creation
- **Search Feature** - Removed the output search (`/`) feature from the TUI, including the `internal/tui/search/` package, key bindings (`/`, `n`/`N`, `Ctrl+/`), search bar, match highlighting, mode indicator, and help panel entries (#685)
Expand Down
151 changes: 16 additions & 135 deletions internal/tui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
tuimsg "github.com/Iron-Ham/claudio/internal/tui/msg"
"github.com/Iron-Ham/claudio/internal/tui/panel"
"github.com/Iron-Ham/claudio/internal/tui/styles"
"github.com/Iron-Ham/claudio/internal/tui/terminal"
"github.com/Iron-Ham/claudio/internal/tui/update"
"github.com/Iron-Ham/claudio/internal/tui/view"
"github.com/Iron-Ham/claudio/internal/ultraplan"
Expand Down Expand Up @@ -475,23 +474,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case tea.WindowSizeMsg:
wasReady := m.ready
m.terminalManager.SetSize(msg.Width, msg.Height)
m.width = msg.Width
m.height = msg.Height
m.ready = true

// Calculate the content area dimensions and resize tmux sessions
// Use the configured sidebar width to ensure tmux panels match the UI layout
cfg := config.Get()
contentWidth, contentHeight := CalculateContentDimensionsWithSidebarWidth(
m.terminalManager.Width(), m.terminalManager.Height(), cfg.TUI.SidebarWidth)
m.width, m.height, cfg.TUI.SidebarWidth)
if m.orchestrator != nil && contentWidth > 0 && contentHeight > 0 {
m.orchestrator.ResizeAllInstances(contentWidth, contentHeight)
}

// Resize terminal pane if visible
if m.terminalManager.IsVisible() {
m.resizeTerminal()
}

// Ensure active instance is still visible after resize
m.ensureActiveVisible()

Expand All @@ -513,10 +508,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tuimsg.TickMsg:
// Update outputs from instances
m.updateOutputs()
// Update terminal pane output if visible
if m.terminalManager.IsVisible() {
m.updateTerminalOutput()
}
// Check for phase changes that need notification (synthesis, consolidation pause)
m.checkForPhaseNotification()

Expand Down Expand Up @@ -941,8 +932,6 @@ func (m *Model) applyCommandResult(result command.Result) {
}
if result.Quitting != nil {
m.quitting = *result.Quitting
// Cleanup terminal pane if running
m.cleanupTerminal()
}
if result.AddingTask != nil {
m.addingTask = *result.AddingTask
Expand All @@ -960,56 +949,6 @@ func (m *Model) applyCommandResult(result command.Result) {
m.filterMode = *result.FilterMode
}

// Handle terminal-related state changes
if result.EnterTerminalMode {
m.enterTerminalMode()
}
if result.ToggleTerminal {
sessionID := ""
if m.orchestrator != nil {
sessionID = m.orchestrator.SessionID()
}
m.toggleTerminalVisibility(sessionID)
if m.terminalManager.IsVisible() {
m.infoMessage = "Terminal pane opened. Press [:t] to focus, [`] to hide."
} else {
m.infoMessage = "Terminal pane closed."
}
}
if result.TerminalDirMode != nil {
newMode := terminal.DirMode(*result.TerminalDirMode)
currentMode := m.terminalManager.DirMode()
if newMode != currentMode {
m.terminalManager.SetDirMode(newMode)
process := m.terminalManager.Process()
if process != nil && process.IsRunning() {
targetDir := m.getTerminalDir()
if err := process.ChangeDirectory(targetDir); err != nil {
m.errorMessage = "Failed to change directory: " + err.Error()
} else {
if newMode == terminal.DirWorktree {
m.infoMessage = "Terminal: switched to worktree"
} else {
m.infoMessage = "Terminal: switched to invocation directory"
}
}
} else {
if newMode == terminal.DirWorktree {
m.infoMessage = "Terminal will use worktree when opened."
} else {
m.infoMessage = "Terminal will use invocation directory when opened."
}
}
} else {
// Already in the requested mode
if newMode == terminal.DirWorktree {
m.infoMessage = "Terminal is already in worktree mode."
} else {
m.infoMessage = "Terminal is already in invocation directory mode."
}
}
}

// Handle active tab adjustment after instance removal
if result.ActiveTabAdjustment != 0 {
if m.activeTab >= m.instanceCount() {
Expand Down Expand Up @@ -1229,15 +1168,11 @@ func (m Model) View() string {
b.WriteString(header)
b.WriteString("\n")

// Get pane dimensions, accounting for dynamic footer elements
dims := m.terminalManager.GetPaneDimensions(m.calculateExtraFooterLines())
// Calculate main area dimensions, accounting for dynamic footer elements
cfg := config.Get()
effectiveSidebarWidth := CalculateEffectiveSidebarWidthWithConfig(dims.TerminalWidth, cfg.TUI.SidebarWidth)
mainContentWidth := dims.TerminalWidth - effectiveSidebarWidth - 3 // 3 for gap between panels

// Main area height is pre-calculated by terminal manager
// (accounts for header, footer, and terminal pane)
mainAreaHeight := dims.MainAreaHeight
effectiveSidebarWidth := CalculateEffectiveSidebarWidthWithConfig(m.width, cfg.TUI.SidebarWidth)
mainContentWidth := m.width - effectiveSidebarWidth - 3 // 3 for gap between panels
mainAreaHeight := m.mainAreaHeight(m.calculateExtraFooterLines())

// Sidebar + Content area (horizontal layout)
// Use view component for sidebar rendering - handles all modes including ultraplan
Expand Down Expand Up @@ -1275,12 +1210,6 @@ func (m Model) View() string {
mainArea := lipgloss.JoinHorizontal(lipgloss.Top, sidebarStyled, " ", contentStyled)
b.WriteString(mainArea)

// Terminal pane (if visible)
if m.terminalManager.IsVisible() {
b.WriteString("\n")
b.WriteString(m.renderTerminalPane())
}

// Info or error message if any
if m.infoMessage != "" {
b.WriteString("\n")
Expand Down Expand Up @@ -1331,19 +1260,18 @@ func (m Model) renderUnifiedHeader() string {

// Build mode indicator state
modeState := &view.ModeIndicatorState{
CommandMode: m.commandMode,
FilterMode: m.filterMode,
InputMode: m.inputMode,
TerminalFocused: m.terminalManager.IsFocused(),
AddingTask: m.addingTask,
CommandMode: m.commandMode,
FilterMode: m.filterMode,
InputMode: m.inputMode,
AddingTask: m.addingTask,
}

// Get the workflow status and mode indicator
workflowStatus := view.RenderWorkflowStatus(workflowState)
modeIndicator := view.RenderModeIndicator(modeState)

// Calculate available width for layout
termWidth := m.terminalManager.Width()
termWidth := m.width

// If no workflow status and no mode indicator, render simple header
if workflowStatus == "" && modeIndicator == "" {
Expand Down Expand Up @@ -1403,40 +1331,6 @@ func (m Model) buildWorkflowStatusState() *view.WorkflowStatusState {
}
}

// renderTerminalPane renders the terminal pane at the bottom of the screen.
func (m Model) renderTerminalPane() string {
dims := m.terminalManager.GetPaneDimensions(m.calculateExtraFooterLines())
if dims.TerminalPaneHeight == 0 {
return ""
}

// Build the terminal state for the view
process := m.terminalManager.Process()
state := view.TerminalState{
Output: m.terminalManager.Output(),
TerminalMode: m.terminalManager.IsFocused(),
InvocationDir: m.terminalManager.GetDir(nil), // Pass nil to get invocation dir
IsWorktreeMode: m.terminalManager.DirMode() == terminal.DirWorktree,
}

// Set current directory
if process != nil {
state.CurrentDir = process.CurrentDir()
} else {
state.CurrentDir = state.InvocationDir
}

// Set instance ID if in worktree mode
if state.IsWorktreeMode {
if inst := m.activeInstance(); inst != nil {
state.InstanceID = inst.ID
}
}

termView := view.NewTerminalView(dims.TerminalWidth, dims.TerminalPaneHeight)
return termView.Render(state)
}

// renderContent renders the main content area
func (m Model) renderContent(width int) string {
if m.addingTask {
Expand Down Expand Up @@ -1593,7 +1487,7 @@ func (m Model) renderHelpPanel(width int) string {
helpPanel := panel.NewHelpPanel()
state := &panel.RenderState{
Width: width - 4, // Account for content box padding
Height: m.terminalManager.Height() - 4,
Height: m.height - 4,
ScrollOffset: m.helpScroll,
Theme: styles.NewTheme(),
}
Expand All @@ -1609,7 +1503,7 @@ func (m Model) renderDiffPanel(width int) string {
diffPanel := panel.NewDiffPanel()
state := &panel.RenderState{
Width: width - 4, // Account for content box padding
Height: m.terminalManager.Height() - 4,
Height: m.height - 4,
ScrollOffset: m.diffScroll,
Theme: styles.NewTheme(),
ActiveInstance: m.activeInstance(),
Expand Down Expand Up @@ -1640,26 +1534,13 @@ func (m Model) calculateExtraFooterLines() int {

// buildHelpBarState creates the view.HelpBarState from the current model state.
func (m Model) buildHelpBarState() *view.HelpBarState {
state := &view.HelpBarState{
return &view.HelpBarState{
CommandMode: m.commandMode,
CommandBuffer: m.commandBuffer,
InputMode: m.inputMode,
ShowDiff: m.showDiff,
FilterMode: m.filterMode,
}

// Terminal manager may be nil in tests
if m.terminalManager != nil {
state.TerminalFocused = m.terminalManager.IsFocused()
state.TerminalVisible = m.terminalManager.IsVisible()
if m.terminalManager.DirMode() == terminal.DirWorktree {
state.TerminalDirMode = "worktree"
} else {
state.TerminalDirMode = "invoke"
}
}

return state
}

// renderCommandModeHelp renders the help bar when in command mode.
Expand All @@ -1683,7 +1564,7 @@ func (m Model) renderStatsPanel(width int) string {
// Build render state for the panel
state := &panel.RenderState{
Width: width,
Height: m.terminalManager.Height(),
Height: m.height,
Theme: styles.NewTheme(),
CostWarningThreshold: cfg.Resources.CostWarningThreshold,
CostLimit: cfg.Resources.CostLimit,
Expand Down
43 changes: 0 additions & 43 deletions internal/tui/command/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ type Dependencies interface {
InstanceCount() int

// State queries
IsTerminalVisible() bool
IsDiffVisible() bool
GetDiffContent() string
IsUltraPlanMode() bool
Expand Down Expand Up @@ -89,11 +88,6 @@ type Result struct {
ActiveTabAdjustment int
EnsureActiveVisible bool

// Terminal-related state changes
EnterTerminalMode bool
ToggleTerminal bool // signals that terminal visibility should be toggled
TerminalDirMode *int // 0 = invocation, 1 = worktree

// Mode transition - Triple-Shot
StartTripleShot *bool // Request to switch to triple-shot mode

Expand Down Expand Up @@ -273,18 +267,6 @@ func (h *Handler) registerCommands() {
h.argCommands["r"] = cmdPRWithArgs
h.argCommands["pr"] = cmdPRWithArgs

// Terminal pane commands
h.commands["t"] = cmdTerminalFocus
h.commands["term"] = cmdTerminal
h.commands["terminal"] = cmdTerminal
h.commands["termdir worktree"] = cmdTerminalDirWorktree
h.commands["termdir wt"] = cmdTerminalDirWorktree
h.commands["termdir project"] = cmdTerminalDirProject
h.commands["termdir proj"] = cmdTerminalDirProject
// Legacy aliases for backward compatibility
h.commands["termdir invoke"] = cmdTerminalDirProject
h.commands["termdir invocation"] = cmdTerminalDirProject

// Ultraplan commands
h.commands["cancel"] = cmdUltraPlanCancel
h.argCommands["ultraplan"] = cmdUltraPlan
Expand Down Expand Up @@ -360,7 +342,6 @@ func (h *Handler) buildCategories() {
{
Name: "Terminal",
Commands: []CommandInfo{
{ShortKey: "t", LongKey: "term", Description: "Focus/toggle terminal pane", Category: "terminal"},
{ShortKey: "", LongKey: "tmux", Description: "Show tmux attach command", Category: "terminal"},
},
},
Expand Down Expand Up @@ -776,30 +757,6 @@ func cmdFilter(_ Dependencies) Result {
return Result{FilterMode: &filterMode}
}

func cmdTerminal(_ Dependencies) Result {
return Result{ToggleTerminal: true}
}

func cmdTerminalFocus(deps Dependencies) Result {
if deps.IsTerminalVisible() {
return Result{
EnterTerminalMode: true,
InfoMessage: "Terminal focused. Press Ctrl+] to exit.",
}
}
return Result{ErrorMessage: "Terminal not visible. Use :term to open it first."}
}

func cmdTerminalDirWorktree(_ Dependencies) Result {
mode := 1 // TerminalDirWorktree
return Result{TerminalDirMode: &mode}
}

func cmdTerminalDirProject(_ Dependencies) Result {
mode := 0 // TerminalDirProject (the directory where Claudio was started)
return Result{TerminalDirMode: &mode}
}

func cmdTmux(deps Dependencies) Result {
inst := deps.ActiveInstance()
if inst == nil {
Expand Down
Loading
Loading