R rebuilds every pane detached; r refreshes data; Author gains guidance and preview; Repair gains the rebuild log
This commit is contained in:
125
tui/newtask.go
125
tui/newtask.go
@@ -20,10 +20,10 @@ import (
|
||||
)
|
||||
|
||||
type createdMsg struct {
|
||||
issue paperclip.Issue
|
||||
blocked string
|
||||
issue paperclip.Issue
|
||||
blocked string
|
||||
assigned bool
|
||||
err error
|
||||
err error
|
||||
}
|
||||
|
||||
type newTaskModel struct {
|
||||
@@ -37,9 +37,12 @@ type newTaskModel struct {
|
||||
busy bool
|
||||
result string
|
||||
|
||||
tab int
|
||||
width, height int
|
||||
}
|
||||
|
||||
var authorTabs = []string{"form", "guidance", "preview"}
|
||||
|
||||
const (
|
||||
fieldTitle = iota
|
||||
fieldBody
|
||||
@@ -127,7 +130,19 @@ func (m newTaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
case tea.KeyCtrlC:
|
||||
return m, tea.Quit
|
||||
case tea.KeyCtrlN:
|
||||
m.tab = (m.tab + 1) % len(authorTabs)
|
||||
return m, nil
|
||||
case tea.KeyCtrlP:
|
||||
m.tab = (m.tab + len(authorTabs) - 1) % len(authorTabs)
|
||||
return m, nil
|
||||
case tea.KeyEsc:
|
||||
if m.tab != 0 {
|
||||
m.tab = 0
|
||||
return m, m.inputs[m.focus].Focus()
|
||||
}
|
||||
return m, tea.Quit
|
||||
case tea.KeyTab, tea.KeyDown:
|
||||
m.inputs[m.focus].Blur()
|
||||
@@ -177,6 +192,54 @@ func (m newTaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// descriptionPreview shows exactly what the agent will be given: the same
|
||||
// text that submit() puts in the task's description, built the same way.
|
||||
func (m newTaskModel) descriptionPreview() string {
|
||||
body := strings.TrimSpace(m.inputs[fieldBody].Value())
|
||||
verify := strings.TrimSpace(m.inputs[fieldVerify].Value())
|
||||
blocker := strings.TrimSpace(m.inputs[fieldBlocker].Value())
|
||||
out := body
|
||||
if verify != "" {
|
||||
out += "\n\nDone when this command succeeds:\n\n " + verify
|
||||
}
|
||||
if blocker != "" {
|
||||
out += "\n\nMust finish first: " + blocker
|
||||
}
|
||||
if strings.TrimSpace(out) == "" {
|
||||
return "nothing written yet"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m newTaskModel) previewLines(width int) []string {
|
||||
out := []string{"", dimStyle().Render(" the task's description, exactly as the agent receives it"), ""}
|
||||
for _, para := range strings.Split(m.descriptionPreview(), "\n") {
|
||||
for _, ln := range wrap(para, max(8, width-2)) {
|
||||
out = append(out, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Render(ln))
|
||||
}
|
||||
if para == "" {
|
||||
out = append(out, "")
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m newTaskModel) guidanceLines(width int) []string {
|
||||
out := []string{"", groupHeading("what a task needs", 4, width-2), ""}
|
||||
add := func(k, v string) {
|
||||
out = append(out, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Accent)).Render(k))
|
||||
for _, ln := range wrap(v, max(8, width-4)) {
|
||||
out = append(out, " "+mutedStyle2().Render(ln))
|
||||
}
|
||||
out = append(out, "")
|
||||
}
|
||||
add("one thing", "A task that asks for two things cannot be judged, only argued about.")
|
||||
add("a command", "The verify command decides when the work is finished. The agent does not.")
|
||||
add("a boundary", "Say which files may change. An unbounded task is how a review becomes an autopsy.")
|
||||
add("a blocker", "If it must wait, name the task it waits for. A sentence in the description is not a blocker.")
|
||||
return out
|
||||
}
|
||||
|
||||
func (m newTaskModel) View() string {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
@@ -187,7 +250,47 @@ func (m newTaskModel) View() string {
|
||||
}
|
||||
|
||||
head := titleStyle.Render("Author") + mutedStyle2().Render(" · new task in "+m.proj.Name)
|
||||
bodyHeight := m.height - 2
|
||||
|
||||
formLines := m.formLines(m.width - 4)
|
||||
footer := hintLine([]string{
|
||||
keyHint("tab", "next field"),
|
||||
keyHint("enter", "next, and create on the last"),
|
||||
keyHint("ctrl+n", "form, guidance, preview"),
|
||||
keyHint("esc", "cancel"),
|
||||
}, m.width)
|
||||
|
||||
// Wide screens: the form on the left, what to write and what will be sent on
|
||||
// the right. Narrow ones: one panel, switched with the tabs.
|
||||
if m.width >= 120 {
|
||||
formW := max(50, m.width*58/100)
|
||||
sideW := m.width - formW - 1
|
||||
topH := bodyHeight / 2
|
||||
bottomH := bodyHeight - topH
|
||||
|
||||
form := panel{title: "new task", width: formW, height: bodyHeight, focused: true}
|
||||
guide := panel{title: "what a task needs", width: sideW, height: topH}
|
||||
prev := panel{title: "what will be sent", width: sideW, height: bottomH}
|
||||
page := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
form.render(formLines), " ",
|
||||
lipgloss.JoinVertical(lipgloss.Left,
|
||||
guide.render(m.guidanceLines(sideW-3)),
|
||||
prev.render(m.previewLines(sideW-3))))
|
||||
return strings.Join([]string{head, page, footer}, "\n")
|
||||
}
|
||||
|
||||
p := panel{title: "new task", width: m.width, height: bodyHeight, tabs: authorTabs, active: m.tab, focused: true}
|
||||
lines := formLines
|
||||
switch m.tab {
|
||||
case 1:
|
||||
lines = m.guidanceLines(m.width - 3)
|
||||
case 2:
|
||||
lines = m.previewLines(m.width - 3)
|
||||
}
|
||||
return strings.Join([]string{head, p.render(lines), footer}, "\n")
|
||||
}
|
||||
|
||||
func (m newTaskModel) formLines(width int) []string {
|
||||
var body []string
|
||||
body = append(body, "")
|
||||
for i, in := range m.inputs {
|
||||
@@ -201,25 +304,15 @@ func (m newTaskModel) View() string {
|
||||
body = append(body, " "+mutedStyle2().Render(label))
|
||||
}
|
||||
body = append(body, " "+in.View())
|
||||
hint := fieldHints[i]
|
||||
if i == m.focus {
|
||||
body = append(body, " "+dimStyle().Render(hint))
|
||||
body = append(body, " "+dimStyle().Render(fieldHints[i]))
|
||||
}
|
||||
body = append(body, "")
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
body = append(body, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Error)).Render("error: "+m.err.Error()))
|
||||
} else if m.busy {
|
||||
body = append(body, " "+mutedStyle2().Render("writing to Paperclip…"))
|
||||
}
|
||||
|
||||
p := panel{title: "new task", width: m.width, height: m.height - 2, focused: true}
|
||||
footer := hintLine([]string{
|
||||
keyHint("tab", "next field"),
|
||||
keyHint("enter", "next, and create on the last"),
|
||||
keyHint("esc", "cancel"),
|
||||
}, m.width)
|
||||
|
||||
return strings.Join([]string{head, p.render(body), footer}, "\n")
|
||||
return body
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user