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
90 changes: 1 addition & 89 deletions internal/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ type KeyMap struct {
Refresh key.Binding
Settings key.Binding
Memories key.Binding
Open key.Binding
Files key.Binding
Help key.Binding
Quit key.Binding
Expand All @@ -79,7 +78,7 @@ func (k KeyMap) FullHelp() [][]key.Binding {
{k.FocusBacklog, k.FocusInProgress, k.FocusBlocked, k.FocusDone},
{k.Enter, k.New, k.Queue, k.Close},
{k.Retry, k.Watch, k.Attach, k.Interrupt, k.Delete},
{k.Filter, k.Settings, k.Memories, k.Open, k.Files},
{k.Filter, k.Settings, k.Memories, k.Files},
{k.Refresh, k.Help, k.Quit},
}
}
Expand Down Expand Up @@ -159,10 +158,6 @@ func DefaultKeyMap() KeyMap {
key.WithKeys("m"),
key.WithHelp("m", "memories"),
),
Open: key.NewBinding(
key.WithKeys("o"),
key.WithHelp("o", "open dir"),
),
Files: key.NewBinding(
key.WithKeys("f"),
key.WithHelp("f", "files"),
Expand Down Expand Up @@ -917,11 +912,6 @@ func (m *AppModel) updateDashboard(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.loading = true
return m, m.loadTasks()

case key.Matches(msg, m.keys.Open):
if task := m.kanban.SelectedTask(); task != nil {
return m, m.openTaskDir(task)
}

case key.Matches(msg, m.keys.Help):
m.help.ShowAll = !m.help.ShowAll
return m, nil
Expand Down Expand Up @@ -1019,9 +1009,6 @@ func (m *AppModel) updateDetail(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m.showDeleteConfirm(m.selectedTask)
}
if key.Matches(keyMsg, m.keys.Open) && m.selectedTask != nil {
return m, m.openTaskDir(m.selectedTask)
}
if key.Matches(keyMsg, m.keys.Files) && m.selectedTask != nil {
m.attachmentsView = NewAttachmentsModel(m.selectedTask, m.db, m.width, m.height)
m.previousView = m.currentView
Expand Down Expand Up @@ -1426,10 +1413,6 @@ type attachDoneMsg struct {
err error
}

type openDirDoneMsg struct {
err error
}

type tickMsg time.Time

type dbChangeMsg struct{}
Expand Down Expand Up @@ -1631,77 +1614,6 @@ func (m *AppModel) interruptTask(id int64) tea.Cmd {
}
}

func (m *AppModel) openTaskDir(task *db.Task) tea.Cmd {
// Determine directory to open: worktree > project > cwd
dir := task.WorktreePath
if dir == "" {
dir = m.executor.GetProjectDir(task.Project)
}
if dir == "" {
return nil
}

// Use tmux to create a new window in the task directory
// Explicitly target task-ui session to avoid opening in task-daemon
if os.Getenv("TMUX") != "" {
cmd := osExec.Command("tmux", "new-window", "-t", "task-ui", "-c", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
}

// Detect terminal emulator and open new window
termProgram := os.Getenv("TERM_PROGRAM")
switch termProgram {
case "iTerm.app":
script := fmt.Sprintf(`tell application "iTerm2"
tell current window
create tab with default profile
tell current session
write text "cd %q"
end tell
end tell
end tell`, dir)
cmd := osExec.Command("osascript", "-e", script)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
case "Apple_Terminal":
cmd := osExec.Command("open", "-a", "Terminal", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
case "WezTerm":
cmd := osExec.Command("wezterm", "cli", "spawn", "--cwd", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
case "Alacritty":
cmd := osExec.Command("alacritty", "--working-directory", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
case "kitty":
// kitty requires remote control to be enabled; spawn new instance otherwise
cmd := osExec.Command("kitty", "--directory", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
}

// Check for GNOME Terminal
if os.Getenv("GNOME_TERMINAL_SERVICE") != "" {
cmd := osExec.Command("gnome-terminal", "--working-directory", dir)
go cmd.Run()
return func() tea.Msg { return openDirDoneMsg{} }
}

// Fallback: spawn shell in-place (user exits to return to TUI)
shell := os.Getenv("SHELL")
if shell == "" {
shell = "sh"
}
cmd := osExec.Command(shell)
cmd.Dir = dir
return tea.ExecProcess(cmd, func(err error) tea.Msg {
return openDirDoneMsg{err: err}
})
}

func (m *AppModel) waitForTaskEvent() tea.Cmd {
return func() tea.Msg {
event, ok := <-m.eventCh
Expand Down
80 changes: 0 additions & 80 deletions internal/ui/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,83 +35,3 @@ func TestInterruptKeyEnabled(t *testing.T) {
}
}

func TestDetailViewHelp(t *testing.T) {
// Create a test database
database, err := db.Open(":memory:")
if err != nil {
t.Fatalf("failed to create test db: %v", err)
}
defer database.Close()

tests := []struct {
name string
taskStatus string
wantInterrupt bool
}{
{
name: "backlog task should not show interrupt",
taskStatus: db.StatusBacklog,
wantInterrupt: false,
},
{
name: "queued task should show interrupt",
taskStatus: db.StatusQueued,
wantInterrupt: true,
},
{
name: "processing task should show interrupt",
taskStatus: db.StatusProcessing,
wantInterrupt: true,
},
{
name: "done task should not show interrupt",
taskStatus: db.StatusDone,
wantInterrupt: false,
},
{
name: "blocked task should not show interrupt",
taskStatus: db.StatusBlocked,
wantInterrupt: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create task with given status
task := &db.Task{
ID: 1,
Title: "Test Task",
Status: tt.taskStatus,
}
if err := database.CreateTask(task); err != nil {
t.Fatalf("failed to create task: %v", err)
}
defer database.DeleteTask(task.ID)

// Create detail model
model := NewDetailModel(task, database, 80, 24)

// Render the help and check for interrupt key
help := model.renderHelp()
hasInterrupt := contains(help, "interrupt")

if hasInterrupt != tt.wantInterrupt {
t.Errorf("help contains interrupt = %v, want %v (status: %s)", hasInterrupt, tt.wantInterrupt, tt.taskStatus)
}
})
}
}

// contains checks if substr is in s
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
}

func containsHelper(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
1 change: 0 additions & 1 deletion internal/ui/detail.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,6 @@ func (m *DetailModel) renderHelp() string {
{"r", "retry"},
{"c", "close"},
{"d", "delete"},
{"o", "open dir"},
{"q/esc", "back"},
}...)

Expand Down