Help on every panel, task filter, unblock key, mouse zones, safe add-only rebuild

This commit is contained in:
Sam
2026-11-10 15:59:42 +11:00
parent 972d4c0184
commit 09e32da046
13 changed files with 587 additions and 16 deletions

View File

@@ -232,3 +232,87 @@ Also still true: `bin/board` builds five workspaces (shell, board, work, reply,
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-tmp-2026-11-10-03-19-00|Rich discovery: tmp 2026-11-10 03:19:00]]
- context: [[packs/paperclip-harness/20-context/tmp|tmp]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|tmp repository structure and configuration patterns]]
---
## Update (2026-11-10 03:34:34)
## Rich discovery update (2026-11-10 03:34:34)
Done. `R` rebuilds everything and I proved it by pressing it.
## Rebuild and refresh, both real
```
r read the data again in this panel. Nothing closes, nothing moves.
R rebuild every workspace and pane from the project file.
```
`R` is on the board, work, conversation, project and repair screens (not the new-task form, where letters are text). It runs `bin/board --replace` **detached in its own session**, because the panel you press it in is one of the panes being replaced — a rebuild that dies with its caller is not a rebuild.
Pressed `R` in the board pane and read the result:
```
rebuild started 2026-11-10 14:33:47
closed shell
closed board
closed work
closed reply
closed new
closed project
closed repair
shell created workspace 2
board created workspace 3
...
```
Afterwards, exactly seven panes, one per workspace, all of them drawing:
```
ws 1 shell ws 2 board ws 3 work ws 4 reply
ws 5 new ws 6 project ws 7 repair
```
The log is `/tmp/paperclip-harness-rebuild.log` and the Repair workspace has a third tab, **rebuild log**, that shows it — so the record of a repair outlives the panes it replaced.
## Missing panels added
- **Author** now has three: the form, *what a task needs* (one thing, a command, a boundary, a blocker — each explaining why), and *what will be sent*, which renders the exact description the agent receives, verify command and blocker included. At 177 columns the form sits left with the other two stacked right; narrower, they are tabs under `ctrl+n`.
- **Repair** now has health, layout and rebuild log.
- Conversation's task header is a line rather than a panel, deliberately: a panel for one line wastes the border.
## Where the layout stands
```
1 shell 2 board 3 work 4 reply 5 new 6 project 7 repair
```
One pane each, named, rebuilt by name, adopted if present. `bin/board --replace` rebuilds them all; `bin/board` alone adds whatever is missing without touching what is there.
Screenshots refreshed: `/tmp/mockups/{board,work,reply,new,project,repair}-live.png`.
Committed as `972d4c0`. Ready for you to poke at: the four things I would test first are `R` on the repair screen, the conversation follow when you pick a task on the board, the costs tab against what Paperclip reports, and whether the icons render now that the font is in.
## Source paths observed
- `home/sam/paseo/projects/paperclip-harness/tui/rebuild.go`
- `paperclip-harness/tui/paperclip`
- `tmp/mockups/board-live.png`
- `tmp/mockups/new-live.png`
- `tmp/mockups/project-live.png`
- `tmp/mockups/repair-live.png`
- `tmp/mockups/reply-live.png`
- `tmp/mockups/work-live.png`
## Evidence
- Confidence: 84%
- Reason: Update component/repository context with detailed discovered structure and configuration
## Related learned memories <!-- same-turn-2026-11-10 03:34:34 -->
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-rebuild-refresh-keys-author-repair-pan|paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-tmp-2026-11-10-03-34-34|Rich discovery: tmp 2026-11-10 03:34:34]]
- context: [[packs/paperclip-harness/20-context/tmp|tmp]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|tmp repository structure and configuration patterns]]

View File

@@ -567,3 +567,87 @@ Committed as `351cb35`. Next, when you are back: tell me which of the six you wa
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-six-workspace-build-screens-and-bug-fi|paperclip-harness TUI: six-workspace build, screens, and bug fixes (351cb35)]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-what-you-will-see-when-you-open-it-2026-11-10|Rich discovery: what-you-will-see-when-you-open-it 2026-11-10 03:29:19]]
- context: [[packs/paperclip-harness/20-context/paperclip-harness|what-you-will-see-when-you-open-it]]
---
## Update (2026-11-10 03:34:34)
## Structural/configuration observations (2026-11-10 03:34:34)
Done. `R` rebuilds everything and I proved it by pressing it.
## Rebuild and refresh, both real
```
r read the data again in this panel. Nothing closes, nothing moves.
R rebuild every workspace and pane from the project file.
```
`R` is on the board, work, conversation, project and repair screens (not the new-task form, where letters are text). It runs `bin/board --replace` **detached in its own session**, because the panel you press it in is one of the panes being replaced — a rebuild that dies with its caller is not a rebuild.
Pressed `R` in the board pane and read the result:
```
rebuild started 2026-11-10 14:33:47
closed shell
closed board
closed work
closed reply
closed new
closed project
closed repair
shell created workspace 2
board created workspace 3
...
```
Afterwards, exactly seven panes, one per workspace, all of them drawing:
```
ws 1 shell ws 2 board ws 3 work ws 4 reply
ws 5 new ws 6 project ws 7 repair
```
The log is `/tmp/paperclip-harness-rebuild.log` and the Repair workspace has a third tab, **rebuild log**, that shows it — so the record of a repair outlives the panes it replaced.
## Missing panels added
- **Author** now has three: the form, *what a task needs* (one thing, a command, a boundary, a blocker — each explaining why), and *what will be sent*, which renders the exact description the agent receives, verify command and blocker included. At 177 columns the form sits left with the other two stacked right; narrower, they are tabs under `ctrl+n`.
- **Repair** now has health, layout and rebuild log.
- Conversation's task header is a line rather than a panel, deliberately: a panel for one line wastes the border.
## Where the layout stands
```
1 shell 2 board 3 work 4 reply 5 new 6 project 7 repair
```
One pane each, named, rebuilt by name, adopted if present. `bin/board --replace` rebuilds them all; `bin/board` alone adds whatever is missing without touching what is there.
Screenshots refreshed: `/tmp/mockups/{board,work,reply,new,project,repair}-live.png`.
Committed as `972d4c0`. Ready for you to poke at: the four things I would test first are `R` on the repair screen, the conversation follow when you pick a task on the board, the costs tab against what Paperclip reports, and whether the icons render now that the font is in.
## Source paths observed
- `home/sam/paseo/projects/paperclip-harness/tui/rebuild.go`
- `paperclip-harness/tui/paperclip`
- `tmp/mockups/board-live.png`
- `tmp/mockups/new-live.png`
- `tmp/mockups/project-live.png`
- `tmp/mockups/repair-live.png`
- `tmp/mockups/reply-live.png`
- `tmp/mockups/work-live.png`
## Evidence
- Confidence: 82%
- Reason: Captured factual repo structure/configuration patterns from rich discovery
## Related learned memories <!-- same-turn-2026-11-10 03:34:34 -->
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-rebuild-refresh-keys-author-repair-pan|paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-tmp-2026-11-10-03-34-34|Rich discovery: tmp 2026-11-10 03:34:34]]
- context: [[packs/paperclip-harness/20-context/tmp|tmp]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|tmp repository structure and configuration patterns]]

View File

@@ -0,0 +1,76 @@
---
type: session
id: session.paperclip-harness.paperclip-harness-tui-rebuild-refresh-keys-author-repair-pan
title: paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion
status: active
source_of_truth: false
freshness: current
last_reviewed: 2026-11-10
tags:
- pack/paperclip-harness
- agent-memory/session
- paperclip-harness
- tui
- go
- bubbles-tea
- keybindings
- rebuild
- layout
- author
---
# paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion
Repository: paperclip-harness, TUI component in `tui/` (Go, bubbles/tea).
## Key bindings established
- `r` — refresh/re-read data in the current panel only (nothing closes or moves).
- `R` — full rebuild of every workspace/pane from the project file.
- `R` is bound on board, work, conversation, project, and repair screens — NOT on the new-task form (letters are text there).
## Rebuild implementation detail
`R` shells out to `bin/board --replace`, run **detached in its own session**, because the pane you press `R` in is itself being replaced (a rebuild that dies with its caller is not a rebuild).
- `bin/board --replace` rebuilds all panes; plain `bin/board` adds only missing panes without touching existing ones.
- Panes are rebuilt **by name** and **adopted if already present**.
- Rebuild log written to `/tmp/paperclip-harness-rebuild.log`.
## Verified behavior
Sample rebuild log lines: `rebuild started <ts>`, `board: <uuid> (session paperclip-harness, folder /tmp/pc-sandbox-2026-11-10)`, `watching: AUD-162`, then `closed <name>` for all 7 panes and `created workspace N` for each. Post-rebuild: exactly 7 panes, all drawing, 177x53 each:
```
ws 1 shell ws 2 board ws 3 work ws 4 reply
ws 5 new ws 6 project ws 7 repair
```
## Layout (fixed order)
`1 shell 2 board 3 work 4 reply 5 new 6 project 7 repair`
## Panel changes this turn
- **Author** now has three sub-panels: the form; *what a task needs* (one thing, a command, a boundary, a blocker — each with a why); and *what will be sent* (renders the exact description the agent receives, incl. verify command and blocker). At 177 columns: form left, other two stacked right; narrower: tabs under `ctrl+n`.
- **Repair** now has three tabs: health, layout, rebuild log.
- Conversation task header is rendered as a **line, not a panel** (deliberately — a panel for one line wastes the border).
## Build/toolchain note
Initial build failed in `newtask.go:141` with `undefined: tea.KeyCtrlBracketLeft` / `tea.KeyCtrlBracketRight` (those tea constants don't exist in the installed bubbles version); resolved and `build=0`. Tests: `ok paperclip-harness/tui 0.479s`.
## Artifacts
- Commit `972d4c0`: "R rebuilds every pane detached; r refreshes data; Author gains guidance and preview; Repair gains the rebuild log".
- Screenshots refreshed: `/tmp/mockups/{board,work,reply,new,project,repair}-live.png`.
## Suggested next tests
`R` on the repair screen; conversation follow when picking a task on the board; costs tab vs what Paperclip reports; icon rendering now that the font is installed.
## Evidence
- Confidence: 85%
- Reason: Rich multi-file discovery and implementation turn with concrete pane layout, key bindings, rebuild mechanics, file paths, and commit hash; durable for future agents working in this repo.
## Related
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
## Related learned memories <!-- same-turn-2026-11-10 03:34:34 -->
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-rebuild-refresh-keys-author-repair-pan|paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-tmp-2026-11-10-03-34-34|Rich discovery: tmp 2026-11-10 03:34:34]]
- context: [[packs/paperclip-harness/20-context/tmp|tmp]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|tmp repository structure and configuration patterns]]

View File

@@ -0,0 +1,103 @@
---
type: session
id: session.paperclip-harness.rich-discovery-tmp-2026-11-10-03-34-34
title: Rich discovery: tmp 2026-11-10 03:34:34
status: active
source_of_truth: false
freshness: current
last_reviewed: 2026-11-10
tags:
- pack/paperclip-harness
- agent-memory/session
- autolearn
- rich-discovery
- repo/tmp
---
# Rich discovery: tmp 2026-11-10 03:34:34
Sanitized rich discovery snapshot captured automatically from the completed turn.
## Source paths observed
- `home/sam/paseo/projects/paperclip-harness/tui/rebuild.go`
- `paperclip-harness/tui/paperclip`
- `tmp/mockups/board-live.png`
- `tmp/mockups/new-live.png`
- `tmp/mockups/project-live.png`
- `tmp/mockups/repair-live.png`
- `tmp/mockups/reply-live.png`
- `tmp/mockups/work-live.png`
## Final answer snapshot
Done. `R` rebuilds everything and I proved it by pressing it.
## Rebuild and refresh, both real
```
r read the data again in this panel. Nothing closes, nothing moves.
R rebuild every workspace and pane from the project file.
```
`R` is on the board, work, conversation, project and repair screens (not the new-task form, where letters are text). It runs `bin/board --replace` **detached in its own session**, because the panel you press it in is one of the panes being replaced — a rebuild that dies with its caller is not a rebuild.
Pressed `R` in the board pane and read the result:
```
rebuild started 2026-11-10 14:33:47
closed shell
closed board
closed work
closed reply
closed new
closed project
closed repair
shell created workspace 2
board created workspace 3
...
```
Afterwards, exactly seven panes, one per workspace, all of them drawing:
```
ws 1 shell ws 2 board ws 3 work ws 4 reply
ws 5 new ws 6 project ws 7 repair
```
The log is `/tmp/paperclip-harness-rebuild.log` and the Repair workspace has a third tab, **rebuild log**, that shows it — so the record of a repair outlives the panes it replaced.
## Missing panels added
- **Author** now has three: the form, *what a task needs* (one thing, a command, a boundary, a blocker — each explaining why), and *what will be sent*, which renders the exact description the agent receives, verify command and blocker included. At 177 columns the form sits left with the other two stacked right; narrower, they are tabs under `ctrl+n`.
- **Repair** now has health, layout and rebuild log.
- Conversation's task header is a line rather than a panel, deliberately: a panel for one line wastes the border.
## Where the layout stands
```
1 shell 2 board 3 work 4 reply 5 new 6 project 7 repair
```
One pane each, named, rebuilt by name, adopted if present. `bin/board --replace` rebuilds them all; `bin/board` alone adds whatever is missing without touching what is there.
Screenshots refreshed: `/tmp/mockups/{board,work,reply,new,project,repair}-live.png`.
Committed as `972d4c0`. Ready for you to poke at: the four things I would test first are `R` on the repair screen, the conversation follow when you pick a task on the board, the costs tab against what Paperclip reports, and whether the icons render now that the font is in.
## Evidence
- Confidence: 86%
- Reason: Preserve full sanitized discovery detail from a rich investigation turn
## Related
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
## Related learned memories <!-- same-turn-2026-11-10 03:34:34 -->
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-rebuild-refresh-keys-author-repair-pan|paperclip-harness TUI: Rebuild/Refresh keys + Author/Repair panel expansion]]
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-tmp-2026-11-10-03-34-34|Rich discovery: tmp 2026-11-10 03:34:34]]
- context: [[packs/paperclip-harness/20-context/tmp|tmp]]
- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|tmp repository structure and configuration patterns]]

View File

@@ -70,6 +70,11 @@ Choosing a task on the board and pressing Enter writes it into the project's
- **`r`** — read the data again in this panel. Nothing closes, nothing moves, the
layout is untouched. Use it when the numbers look stale.
- **`B`** — on the Repair screen only: add whatever panes are missing without
closing any, so it is safe to press from inside a pane.
- **`?`** — the keys for the panel you are in, as an overlay.
- **`/`** — filter the task list by identifier, title or assignee name.
- **`u`** — unblock a task whose blocker has finished but which still says blocked.
- **`R`** — rebuild everything: `bin/board --replace` closes each pane the layout
owns and builds all seven again, in order. It runs detached in its own session,
because the panel you pressed it in is one of the panes being replaced; the

Binary file not shown.

View File

@@ -308,6 +308,10 @@ them. A bare Esc must stay free, because it closes a box.
| Any panel | t | open a tool panel here: shell, Lazygit, Hunk, git, review |
| Any panel | ? | show the keys for this panel |
| Any panel | r | read again from Paperclip — data only, layout untouched |
| Any panel | ? | show the keys for this panel |
| Task tree | / | filter by identifier, title or assignee |
| Task tree | u | unblock a task whose blocker has finished |
| Repair | B | add only the panes that are missing |
| Any panel | R | rebuild every workspace and pane from the project file |
| Any panel | q | leave this panel's program, returning the pane to its shell |
| Any panel | Esc | close the open overlay or leave the composer, never quit |

View File

@@ -17,6 +17,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
zone "github.com/lrstanley/bubblezone"
"paperclip-harness/tui/paperclip"
)
@@ -48,6 +49,11 @@ type boardModel struct {
activity []paperclip.Activity
needsTab int
zones *zone.Manager
filter string
typing bool
showHelp bool
width, height int
}
@@ -59,7 +65,9 @@ const (
)
func newBoardModel(c *paperclip.Client, proj paperclip.Project, folder string) boardModel {
return boardModel{client: c, project: proj, folder: folder, loading: true}
// One click-zone manager for the whole program, created once. Wrapping each
// panel separately drifts as soon as a pane is resized.
return boardModel{client: c, project: proj, folder: folder, loading: true, zones: zone.New()}
}
func (m boardModel) Init() tea.Cmd {
@@ -94,16 +102,36 @@ func (m boardModel) loadDetail(id string) tea.Cmd {
}
}
// visible is the list after the filter. The cursor moves over this, so what is
// highlighted is always what would be acted on.
func (m boardModel) visible() []Row {
if m.filter == "" {
return m.rows
}
needle := strings.ToLower(m.filter)
var out []Row
for _, r := range m.rows {
if strings.Contains(strings.ToLower(r.Issue.Identifier), needle) ||
strings.Contains(strings.ToLower(r.Issue.Title), needle) ||
strings.Contains(strings.ToLower(r.Who), needle) {
out = append(out, r)
}
}
return out
}
func (m boardModel) selectedID() string {
if m.cursor >= 0 && m.cursor < len(m.rows) {
return m.rows[m.cursor].Issue.ID
vis := m.visible()
if m.cursor >= 0 && m.cursor < len(vis) {
return vis[m.cursor].Issue.ID
}
return ""
}
func (m boardModel) selectedTask() *Row {
if m.cursor >= 0 && m.cursor < len(m.rows) {
return &m.rows[m.cursor]
vis := m.visible()
if m.cursor >= 0 && m.cursor < len(vis) {
return &vis[m.cursor]
}
return nil
}
@@ -117,10 +145,73 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tickMsg:
return m, tea.Batch(m.reload(), tickEvery(5*time.Second))
case tea.MouseMsg:
if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft {
for i, r := range m.visible() {
if z := m.zones.Get("row-" + r.Issue.ID); z != nil && z.InBounds(msg) {
m.cursor = i
return m, m.loadDetail(r.Issue.ID)
}
}
}
return m, nil
case tea.KeyMsg:
if m.showHelp {
m.showHelp = false
return m, nil
}
if m.typing {
switch msg.Type {
case tea.KeyEsc:
m.typing = false
m.filter = ""
return m, nil
case tea.KeyEnter, tea.KeyTab:
m.typing = false
return m, nil
case tea.KeyBackspace:
r := []rune(m.filter)
if len(r) > 0 {
m.filter = string(r[:len(r)-1])
}
return m, nil
case tea.KeySpace:
m.filter += " "
return m, nil
case tea.KeyRunes:
m.filter += string(msg.Runes)
return m, nil
}
return m, nil
}
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
case "?":
m.showHelp = true
return m, nil
case "/":
m.typing = true
return m, nil
case "u":
// A task whose blocker finished but which still says blocked is the
// one thing a person unblocks without reading anything else.
if r := m.selectedTask(); r != nil && isReadyButBlocked(*r) {
c, id := m.client, r.Issue.ID
return m, func() tea.Msg {
if _, err := c.SetStatus(id, "todo", "Unblocked: its blocker finished."); err != nil {
return errMsg{err}
}
rows, err := loadRows(c, m.project)
if err != nil {
return errMsg{err}
}
return rowsMsg(rows)
}
}
return m, nil
case "tab":
m.focus = (m.focus + 1) % focusCount
return m, nil
@@ -143,7 +234,7 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, nil
case "down", "j":
if m.cursor < len(m.rows)-1 {
if m.cursor < len(m.visible())-1 {
m.cursor++
return m, m.loadDetail(m.selectedID())
}
@@ -192,8 +283,8 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.loading = false
m.readAt = time.Now()
m.err = nil
if m.cursor >= len(m.rows) {
m.cursor = max(0, len(m.rows)-1)
if m.cursor >= len(m.visible()) {
m.cursor = max(0, len(m.visible())-1)
}
if id := m.selectedID(); id != "" && id != m.detailID {
return m, m.loadDetail(id)
@@ -232,6 +323,19 @@ func (m boardModel) View() string {
m.width, m.height))
}
if m.showHelp {
return helpOverlay("keys on this panel", [][2]string{
{"↑ ↓ j k", "choose a task"},
{"enter", "show that task in every workspace"},
{"a", "assign it to the configured agent"},
{"u", "unblock it, when its blocker has finished"},
{"/", "filter the list by identifier, title or name"},
{"tab", "move to the next panel"},
{"r / R", "read again / rebuild every pane"},
{"q", "leave this panel"},
}, m.width, m.height)
}
header := m.headerLine()
footer := m.footerLine()
bodyHeight := m.height - 2
@@ -257,7 +361,7 @@ func (m boardModel) View() string {
left.render(m.treeLines(treeW-3)), " ",
mid.render(m.summaryLines(sumW-3)), " ",
rightP.render(body))
return strings.Join([]string{header, page, footer}, "\n")
return m.zones.Scan(strings.Join([]string{header, page, footer}, "\n"))
}
leftWidth := max(34, m.width*40/100)
@@ -288,7 +392,7 @@ func (m boardModel) View() string {
)
page := lipgloss.JoinHorizontal(lipgloss.Top, left, " ", right)
return strings.Join([]string{header, page, footer}, "\n")
return m.zones.Scan(strings.Join([]string{header, page, footer}, "\n"))
}
func (m boardModel) headerLine() string {
@@ -319,6 +423,9 @@ func (m boardModel) footerLine() string {
keyHint("↑↓ or j k", "choose a task"),
keyHint("enter", "show it everywhere"),
keyHint("a", "assign"),
keyHint("u", "unblock"),
keyHint("/", "filter"),
keyHint("?", "keys"),
keyHint("tab", "next panel"),
keyHint("r", "read again"),
keyHint("R", "rebuild every pane"),
@@ -345,13 +452,17 @@ func (m boardModel) footerLine() string {
// treeLines draws the grouped task list.
func (m boardModel) treeLines(width int) []string {
var out []string
if len(m.rows) == 0 && !m.loading {
vis := m.visible()
if len(vis) == 0 && !m.loading {
if m.filter != "" {
return []string{mutedStyle2().Render(" nothing matches " + m.filter)}
}
return []string{mutedStyle2().Render(" no tasks in this project")}
}
index := 0
for _, g := range groupOrder {
var group []Row
for _, r := range m.rows {
for _, r := range vis {
if r.Group == g {
group = append(group, r)
}
@@ -414,7 +525,10 @@ func (m boardModel) taskLine(r Row, index, width int) []string {
if chosen {
first, second = padTo(first, width), padTo(second, width)
}
return []string{firstStyle.Render(first), secondStyle.Render(second)}
return []string{
m.zones.Mark("row-"+r.Issue.ID, firstStyle.Render(first)),
secondStyle.Render(second),
}
}
// shortStatus is the one word a row has room for.

View File

@@ -272,3 +272,20 @@ func wrap(s string, w int) []string {
}
return lines
}
// helpOverlay draws the keys for the panel that asked for it, centred over the
// screen. It replaces the whole view rather than floating over it, so nothing
// behind it can be misread as still active.
func helpOverlay(title string, entries [][2]string, w, h int) string {
boxW := min(64, max(30, w-8))
boxH := min(len(entries)+4, max(8, h-4))
lines := []string{""}
for _, e := range entries {
key := lipgloss.NewStyle().Foreground(lipgloss.Color(active.Accent)).Bold(true).
Width(16).Render(e[0])
lines = append(lines, " "+key+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Render(e[1]))
}
lines = append(lines, "", dimStyle().Render(" esc or ? closes this"))
p := panel{title: title, width: boxW, height: boxH, focused: true}
return lipgloss.Place(w, h, lipgloss.Center, lipgloss.Center, p.render(lines))
}

View File

@@ -49,6 +49,7 @@ type projectModel struct {
tab int
scroll int
showHelp bool
width, height int
}
@@ -140,7 +141,14 @@ func (m projectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(m.load(), tickEvery(10*time.Second))
case tea.KeyMsg:
if m.showHelp {
m.showHelp = false
return m, nil
}
switch msg.String() {
case "?":
m.showHelp = true
return m, nil
case "q", "ctrl+c":
return m, tea.Quit
case "r":
@@ -194,6 +202,17 @@ func (m projectModel) View() string {
fmt.Sprintf("This workspace needs 80 × 24.\nThis panel is %d × %d.", m.width, m.height))
}
if m.showHelp {
return helpOverlay("the project", [][2]string{
{"[ ] h l", "facts, costs, settings"},
{"↑ ↓ j k", "scroll"},
{"r", "read again"},
{"R", "rebuild every pane"},
{"q", "leave"},
{"?", "this list"},
}, m.width, m.height)
}
head := titleStyle.Render("Project") + mutedStyle2().Render(" · ")
if m.project.Name != "" {
head += lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Render(m.project.Name)

View File

@@ -52,6 +52,7 @@ type repairModel struct {
tab int
scroll int
showHelp bool
width, height int
}
@@ -196,7 +197,14 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(m.load(), tickEvery(8*time.Second))
case tea.KeyMsg:
if m.showHelp {
m.showHelp = false
return m, nil
}
switch msg.String() {
case "?":
m.showHelp = true
return m, nil
case "q", "ctrl+c":
return m, tea.Quit
case "r":
@@ -207,6 +215,17 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
return m, tea.Quit
case "B":
// Add what is missing without replacing what is there. Safe to press
// from inside a pane, because it never closes one.
out, err := exec.Command(filepath.Join(exeDir(), "board"), "--folder", m.folder).CombinedOutput()
if err != nil {
m.err = fmt.Errorf("adding the missing panes failed: %v: %s", err, strings.TrimSpace(string(out)))
return m, nil
}
m.err = nil
m.tab = 1
return m, m.load()
case "[", "h":
m.tab = (m.tab + len(repairTabs) - 1) % len(repairTabs)
m.scroll = 0
@@ -244,6 +263,18 @@ func (m repairModel) View() string {
fmt.Sprintf("This workspace needs 80 × 24.\nThis panel is %d × %d.", m.width, m.height))
}
if m.showHelp {
return helpOverlay("repair", [][2]string{
{"[ ] h l", "health, layout, rebuild log"},
{"R", "rebuild every pane"},
{"B", "add only the missing panes"},
{"↑ ↓ j k", "scroll"},
{"r", "check again"},
{"q", "leave"},
{"?", "this list"},
}, m.width, m.height)
}
head := titleStyle.Render("Repair") + mutedStyle2().Render(" · layout and health")
state := "read " + m.readAt.Format("15:04:05")
if m.readAt.IsZero() {

View File

@@ -42,9 +42,10 @@ type replyModel struct {
err error
readAt time.Time
scroll int
width int
height int
showHelp bool
scroll int
width int
height int
}
func newReplyModel(c *paperclip.Client, folder, issueID string) replyModel {
@@ -226,6 +227,19 @@ func (m replyModel) View() string {
fmt.Sprintf("This workspace needs 80 × 24.\nThis panel is %d × %d.", m.width, m.height))
}
if m.showHelp {
return helpOverlay("the conversation", [][2]string{
{"enter", "write a reply"},
{"alt+enter", "new line inside the box"},
{"s", "choose the status to send with it"},
{"↑ ↓ j k", "read the thread"},
{"r", "read again"},
{"R", "rebuild every pane"},
{"q", "leave"},
{"?", "this list"},
}, m.width, m.height)
}
head := titleStyle.Render("Conversation") + mutedStyle2().Render(" · ")
if m.task.Identifier != "" {
head += lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).

View File

@@ -46,6 +46,7 @@ type workModel struct {
scroll int
readAt time.Time
showHelp bool
width, height int
}
@@ -122,7 +123,14 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(m.load(), tickEvery(5*time.Second))
case tea.KeyMsg:
if m.showHelp {
m.showHelp = false
return m, nil
}
switch msg.String() {
case "?":
m.showHelp = true
return m, nil
case "q", "ctrl+c":
return m, tea.Quit
case "r":
@@ -188,6 +196,18 @@ func (m workModel) View() string {
fmt.Sprintf("This workspace needs 80 × 24.\nThis panel is %d × %d.", m.width, m.height))
}
if m.showHelp {
return helpOverlay("the work", [][2]string{
{"[ ] h l", "switch between runs, change and facts"},
{"↑ ↓ j k", "scroll"},
{"g / G", "top / bottom"},
{"r", "read again"},
{"R", "rebuild every pane"},
{"q", "leave"},
{"?", "this list"},
}, m.width, m.height)
}
head := titleStyle.Render("Work") + mutedStyle2().Render(" · ")
if m.task.Identifier != "" {
head += lipgloss.NewStyle().Foreground(lipgloss.Color(active.Text)).Render(m.task.Identifier)