Workspaces named for what they are, capitalised; Reply gains a task panel; Project settings and limits; icon test row
This commit is contained in:
@@ -332,9 +332,11 @@ func (c *Client) SetStatus(issueID, status, comment string) (Issue, error) {
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
BudgetCents int `json:"budgetMonthlyCents"`
|
||||
SpentCents int `json:"spentMonthlyCents"`
|
||||
}
|
||||
|
||||
// Agents lists the company's agents, used only to put a name on a task.
|
||||
|
||||
@@ -311,12 +311,24 @@ func (m projectModel) costLines() []string {
|
||||
}
|
||||
|
||||
func (m projectModel) settingLines() []string {
|
||||
out := []string{""}
|
||||
out := []string{"", groupHeading("this project's own file", 6, m.width-4)}
|
||||
out = append(out, m.row("session", m.cfg["session"]))
|
||||
out = append(out, m.row("machine", m.cfg["machine.host"]))
|
||||
out = append(out, m.row("folder", m.cfg["machine.folder"]))
|
||||
out = append(out, m.row("paperclip", m.cfg["paperclip.project"]))
|
||||
out = append(out, m.row("watching", m.cfg["watching"]))
|
||||
out = append(out, m.row("verify command", m.verifyCommand()))
|
||||
out = append(out, "")
|
||||
out = append(out, dimStyle().Render(" verify command: the first task whose description has one,"))
|
||||
out = append(out, dimStyle().Render(" or .harness/verify.sh when the project keeps one."))
|
||||
out = append(out, dimStyle().Render(" .harness/harness.toml is the only thing this project keeps for itself."))
|
||||
out = append(out, dimStyle().Render(" Paperclip owns tasks, blockers, comments, runs, cost and worktrees."))
|
||||
out = append(out, "")
|
||||
out = append(out, groupHeading("limits held by Paperclip", len(m.agents), m.width-4))
|
||||
for _, a := range m.agents {
|
||||
out = append(out, m.row(a.Name, fmt.Sprintf("%s · monthly budget %d cents · spent %d",
|
||||
a.Status, a.BudgetCents, a.SpentCents)))
|
||||
}
|
||||
out = append(out, "", dimStyle().Render(" change a budget in Paperclip, not here: it is their fact, and a second"))
|
||||
out = append(out, dimStyle().Render(" copy of it is how two screens start disagreeing."))
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -339,6 +339,13 @@ func (m repairModel) healthLines() []string {
|
||||
mutedStyle2().Render(truncateVisible(c.detail, max(10, m.width-30)))))
|
||||
}
|
||||
out = append(out, "")
|
||||
// The one way to tell whether the terminal is drawing the font: show the
|
||||
// glyphs themselves. Nothing else on this screen answers that question.
|
||||
out = append(out, " "+mutedStyle2().Render("icons ")+
|
||||
icons.Done+" "+icons.Running+" "+icons.Blocked+" "+icons.Review+" "+
|
||||
icons.Branch+" "+icons.Folder+" "+icons.Cost+" "+
|
||||
dimStyle().Render("if these are boxes, this terminal is not using a Nerd Font"))
|
||||
out = append(out, "")
|
||||
if failed == 0 {
|
||||
out = append(out, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Success)).Render("everything checked passes"))
|
||||
} else {
|
||||
|
||||
38
tui/reply.go
38
tui/reply.go
@@ -22,6 +22,7 @@ import (
|
||||
type replyThreadMsg struct {
|
||||
task paperclip.Issue
|
||||
comments []paperclip.Comment
|
||||
names map[string]string
|
||||
}
|
||||
|
||||
type replySentMsg struct{ task paperclip.Issue }
|
||||
@@ -33,6 +34,7 @@ type replyModel struct {
|
||||
explicit bool
|
||||
|
||||
task paperclip.Issue
|
||||
names map[string]string
|
||||
thread []paperclip.Comment
|
||||
box string
|
||||
inBox bool
|
||||
@@ -75,7 +77,13 @@ func (m replyModel) load() tea.Cmd {
|
||||
if err != nil {
|
||||
return errMsg{err}
|
||||
}
|
||||
return replyThreadMsg{task: task, comments: cs}
|
||||
names := map[string]string{}
|
||||
if agents, err := c.Agents(); err == nil {
|
||||
for _, a := range agents {
|
||||
names[a.ID] = a.Name
|
||||
}
|
||||
}
|
||||
return replyThreadMsg{task: task, comments: cs, names: names}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +200,7 @@ func (m replyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case replyThreadMsg:
|
||||
m.task = msg.task
|
||||
m.names = msg.names
|
||||
wasEmpty := len(m.thread)
|
||||
m.thread = msg.comments
|
||||
m.readAt = time.Now()
|
||||
@@ -251,8 +260,10 @@ func (m replyModel) View() string {
|
||||
}
|
||||
|
||||
composerHeight := max(5, min(9, m.height/4))
|
||||
threadHeight := m.height - composerHeight - 2
|
||||
headerHeight := 5
|
||||
threadHeight := m.height - composerHeight - headerHeight - 2
|
||||
|
||||
headerPanel := panel{title: "the task", width: m.width, height: headerHeight}
|
||||
thread := panel{
|
||||
title: fmt.Sprintf("thread · %s", fmtCount(len(m.thread), "message", "messages")),
|
||||
width: m.width, height: threadHeight, focused: !m.inBox,
|
||||
@@ -305,12 +316,35 @@ func (m replyModel) View() string {
|
||||
gap := max(2, m.width-lipgloss.Width(head)-lipgloss.Width(state)-1)
|
||||
return strings.Join([]string{
|
||||
head + strings.Repeat(" ", gap) + mutedStyle2().Render(state),
|
||||
headerPanel.render(m.headerLines(m.width - 3)),
|
||||
thread.render(lines[from:to]),
|
||||
composer.render(boxLines),
|
||||
footer,
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
// headerLines is the panel the conversation needed: what task this is, without
|
||||
// having to go back to the Board to remember.
|
||||
func (m replyModel) headerLines(width int) []string {
|
||||
if m.task.Identifier == "" {
|
||||
return []string{"", mutedStyle2().Render("no task chosen yet")}
|
||||
}
|
||||
who := m.names[m.task.AssigneeAgent]
|
||||
if who == "" {
|
||||
who = "nobody yet"
|
||||
}
|
||||
status, _ := shortStatus(m.task.Status)
|
||||
line1 := " " + lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Bold(true).
|
||||
Render(m.task.Identifier) + " " + status + " " + mutedStyle2().Render("assigned to "+who)
|
||||
out := []string{"", line1}
|
||||
out = append(out, " "+truncateVisible(m.task.Title, max(8, width-2)))
|
||||
if cmd := verifyFromDescription(m.task.Description); cmd != "" {
|
||||
out = append(out, " "+dimStyle().Render("done when ")+
|
||||
lipgloss.NewStyle().Foreground(lipgloss.Color(active.Info)).Render(cmd))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m replyModel) composerLines(width, height int) []string {
|
||||
text := m.box
|
||||
if text == "" {
|
||||
|
||||
Reference in New Issue
Block a user