Every workspace split into real panes: 19 panes across 7 workspaces, one program per panel

This commit is contained in:
Sam
2026-11-10 16:29:27 +11:00
parent 2640049f1f
commit 32fa2752af
19 changed files with 972 additions and 58 deletions

View File

@@ -49,6 +49,8 @@ type boardModel struct {
activity []paperclip.Activity
needsTab int
panel string
scroll int
zones *zone.Manager
filter string
typing bool
@@ -64,10 +66,10 @@ const (
focusCount
)
func newBoardModel(c *paperclip.Client, proj paperclip.Project, folder string) boardModel {
func newBoardModel(c *paperclip.Client, proj paperclip.Project, folder, panel string) boardModel {
// 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()}
return boardModel{client: c, project: proj, folder: folder, panel: panel, loading: true, zones: zone.New()}
}
func (m boardModel) Init() tea.Cmd {
@@ -228,14 +230,28 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, tea.Quit
case "up", "k":
if m.panel == "detail" || m.panel == "needs" {
m.scroll--
return m, nil
}
if m.cursor > 0 {
m.cursor--
if m.panel != "" {
_ = rememberTask(m.folder, m.visible()[m.cursor].Issue.Identifier)
}
return m, m.loadDetail(m.selectedID())
}
return m, nil
case "down", "j":
if m.panel == "detail" || m.panel == "needs" {
m.scroll++
return m, nil
}
if m.cursor < len(m.visible())-1 {
m.cursor++
if m.panel != "" {
_ = rememberTask(m.folder, m.visible()[m.cursor].Issue.Identifier)
}
return m, m.loadDetail(m.selectedID())
}
return m, nil
@@ -264,12 +280,12 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, nil
case "[", "h":
if m.focus == focusNeeds && m.needsTab > 0 {
if (m.panel == "needs" || m.focus == focusNeeds) && m.needsTab > 0 {
m.needsTab--
}
return m, nil
case "]", "l":
if m.focus == focusNeeds && m.needsTab < 1 {
if (m.panel == "needs" || m.focus == focusNeeds) && m.needsTab < 1 {
m.needsTab++
}
return m, nil
@@ -323,6 +339,9 @@ func (m boardModel) View() string {
m.width, m.height))
}
if m.panel != "" {
return m.singleView()
}
if m.showHelp {
return helpOverlay("keys on this panel", [][2]string{
{"↑ ↓ j k", "choose a task"},
@@ -395,6 +414,64 @@ func (m boardModel) View() string {
return m.zones.Scan(strings.Join([]string{header, page, footer}, "\n"))
}
// singleView is one panel filling the pane, for when the Board workspace is
// split into real panes: tasks, the chosen task, and what needs a person.
func (m boardModel) singleView() string {
width := m.width
height := m.height
var body []string
title := "tasks"
tabs := []string(nil)
active := 0
footer := ""
switch m.panel {
case "detail":
title = "the chosen task"
body = m.summaryLines(width - 3)
footer = hintLine([]string{
keyHint("r", "read again"),
keyHint("R", "rebuild"),
keyHint("?", "keys"),
}, width)
case "needs":
title = "needs you"
tabs = []string{"decisions", "activity"}
active = m.needsTab
if m.needsTab == 0 {
body = m.needsLines(width-3, height-3)
} else {
body = m.activityLines(width-3, height-3)
}
footer = hintLine([]string{
keyHint("[ ]", "switch tab"),
keyHint("r", "read again"),
keyHint("R", "rebuild"),
}, width)
default:
body = m.treeLines(width - 3)
footer = hintLine([]string{
keyHint("↑↓ j k", "choose"),
keyHint("enter", "show everywhere"),
keyHint("a", "assign"),
keyHint("u", "unblock"),
keyHint("/", "filter"),
keyHint("?", "keys"),
}, width)
}
if m.showHelp {
return helpOverlay(title, [][2]string{
{"↑ ↓ j k", "choose a task"},
{"enter", "show it in every workspace"},
{"a / u", "assign / unblock"},
{"/", "filter"}, {"[ ]", "switch tab"},
{"r / R", "read again / rebuild"}, {"q", "leave"},
}, width, height)
}
return singlePanel(title, tabs, active, body, width, height, footer, m.scroll)
}
func (m boardModel) headerLine() string {
left := panelTitleStyle().Render("Board") + mutedStyle2().Render(" · "+m.project.Name)
state := "reading…"