Task and run navigation inside Work>work; explain what a run and a merge are

This commit is contained in:
Sam
2026-11-11 13:17:22 +11:00
parent 049e25af70
commit 6aef6ce249
11 changed files with 566 additions and 6 deletions

View File

@@ -32,6 +32,7 @@ type pickTasksMsg struct {
type workMsg struct {
task paperclip.Issue
tasks []paperclip.Issue
runs []paperclip.Run
activity []paperclip.Activity
cost paperclip.Cost
@@ -63,6 +64,7 @@ type workModel struct {
toolAt int
// choosing a task here, so the screen does not depend on the Board
taskList []paperclip.Issue
picking bool
pickList []paperclip.Issue
pickNames map[string]string
@@ -111,9 +113,18 @@ func (m workModel) load() tea.Cmd {
return errMsg{err}
}
runs, _ := c.Runs(id)
var list []paperclip.Issue
if all, err := c.Issues(); err == nil {
cfg, _ := readHarnessFile(folder)
for _, i := range all {
if i.ProjectID == cfg["paperclip.project"] {
list = append(list, i)
}
}
}
act, _ := c.Activity(id)
cost, _ := c.CostSummary(id)
msg := workMsg{task: task, runs: runs, activity: act, cost: cost}
msg := workMsg{task: task, runs: runs, activity: act, cost: cost, tasks: list}
if ws := task.Workspace; ws != nil && ws.CWD != "" {
msg.diff, msg.diffErr = worktreeDiff(ws.CWD, ws.BaseRef)
} else {
@@ -234,6 +245,35 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "t":
m.tools = true
return m, nil
case "n", "p":
if len(m.taskList) == 0 {
return m, nil
}
at := -1
for i, t := range m.taskList {
if t.ID == m.task.ID {
at = i
}
}
if at < 0 {
at = 0
}
if msg.String() == "n" {
at = (at + 1) % len(m.taskList)
} else {
at = (at + len(m.taskList) - 1) % len(m.taskList)
}
chosen := m.taskList[at]
if err := rememberTask(m.folder, chosen.Identifier); err != nil {
m.err = err
return m, nil
}
m.explicit = true
m.issue = chosen.ID
m.task = chosen
m.tab = 0
m.runAt = 0
return m, m.load()
case "o":
c := m.client
folder := m.folder
@@ -334,6 +374,9 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case workMsg:
m.task = msg.task
if msg.tasks != nil {
m.taskList = msg.tasks
}
m.runs = msg.runs
m.activity = msg.activity
m.cost = msg.cost
@@ -374,7 +417,13 @@ func (m workModel) View() string {
head := titleStyle.Render("Work") + mutedStyle2().Render(" · ")
if m.task.Identifier != "" {
head += lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Render(m.task.Identifier)
head += mutedStyle2().Render(" " + truncateVisible(m.task.Title, max(10, m.width-50)))
head += mutedStyle2().Render(" " + truncateVisible(m.task.Title, max(10, m.width-60)))
for i, t := range m.taskList {
if t.ID == m.task.ID {
head += mutedStyle2().Render(fmt.Sprintf(" task %d of %d", i+1, len(m.taskList)))
break
}
}
}
state := "read " + m.readAt.Format("15:04:05")
if m.readAt.IsZero() {
@@ -403,8 +452,13 @@ func (m workModel) View() string {
if cmd := verifyFromDescription(m.task.Description); cmd != "" {
verify = "verify command: " + cmd
}
return helpOverlay("merge "+m.task.Identifier+"?", [][2]string{
{"what happens", "rebase onto the base branch"},
branch := "(no branch yet)"
if m.task.Workspace != nil && m.task.Workspace.Branch != "" {
branch = m.task.Workspace.Branch
}
return helpOverlay("merge "+m.task.Identifier+" into main?", [][2]string{
{"what is merged", branch + " → " + baseRefOf(m.task)},
{"what happens", "rebase the branch onto the base, then merge it"},
{"", verify},
{"", "ocr reviews the change; a critical finding refuses the merge"},
{"", "merge, then re-run the checks on the base branch"},
@@ -435,8 +489,12 @@ func (m workModel) View() string {
return helpOverlay("open a tool pane here", entries, m.width, m.height)
}
title := "the work"
if m.tab == 0 {
title = fmt.Sprintf("the work · %s", fmtCount(len(m.runs), "run", "runs"))
}
p := panel{
title: "the work", width: m.width, height: m.height - 2,
title: title, width: m.width, height: m.height - 2,
tabs: workTabs, active: m.tab, focused: true,
}
@@ -674,3 +732,11 @@ func (m workModel) factsLines(width int) []string {
row("cost", fmt.Sprintf("%d cent(s), %d run(s), %d tokens out", m.cost.CostCents, m.cost.RunCount, m.cost.OutputTokens))
return out
}
// baseRefOf is the branch a task's work will be merged into.
func baseRefOf(t paperclip.Issue) string {
if t.Workspace != nil && t.Workspace.BaseRef != "" {
return t.Workspace.BaseRef
}
return "main"
}