Add offline-first task-master TUI with tickets, board lists, and undo.

Charm-based CLI with JSON persistence, status/list filters, external editor editing, command-history undo/redo, and glamour markdown without AutoStyle OSC hangs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 10:21:48 +02:00
co-authored by Cursor
parent 4a3d0d777b
commit 24c1e146ec
37 changed files with 3259 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
package ui
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"task/internal/config"
)
func (m Model) renderHeader() string {
brand := m.styles.HeaderBrand.Render(config.AppName)
project := m.styles.HeaderMeta.Render(fmt.Sprintf("project: %s", m.cfg.ActiveProject))
board := m.styles.FilterHint.Render(fmt.Sprintf("list: %s", m.boardList.Label()))
count := m.styles.HeaderMeta.Render(fmt.Sprintf("%d tickets", m.ticketCount()))
parts := []string{brand, project, board, count}
if m.filter != "" {
parts = append(parts, m.styles.FilterHint.Render(fmt.Sprintf("filter: %q", m.filter)))
}
if m.status != "" {
style := m.styles.StatusMsg
if m.statusIsError {
style = m.styles.ErrorMsg
}
parts = append(parts, style.Render(m.status))
}
line := strings.Join(parts, m.styles.HeaderMeta.Render(" │ "))
return m.styles.Header.Width(m.width).Render(line)
}
func (m Model) renderBody(height int) string {
if height < 1 {
height = 1
}
switch m.mode {
case ModeCreate:
label := "New ticket:"
if m.creatingProject {
label = "New project:"
}
prompt := m.styles.InputPrompt.Render(label)
content := prompt + "\n" + m.input.View()
return lipgloss.NewStyle().Height(height).Width(m.width).Render(content)
case ModeSearch:
prompt := m.styles.InputPrompt.Render("Search:")
content := prompt + "\n" + m.input.View()
return lipgloss.NewStyle().Height(height).Width(m.width).Render(content)
case ModeProjects:
m.projectList.SetSize(m.width, height)
return m.projectList.View()
case ModeStatus:
menu := m.statusMenu.View()
return lipgloss.NewStyle().Height(height).Width(m.width).Render(menu)
case ModeBoard:
menu := m.boardMenu.View()
return lipgloss.NewStyle().Height(height).Width(m.width).Render(menu)
default:
m.tickets.SetSize(m.width, height)
return m.tickets.View()
}
}