Every workspace split into real panes: 19 panes across 7 workspaces, one program per panel
This commit is contained in:
@@ -137,3 +137,74 @@ 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 05:13:11)
|
||||
|
||||
paperclip-harness is a terminal harness TUI organised as Workspace>pane. Current seven workspaces (capitalised) and their panes: Shell>shell, Board>board, Work>work, Reply>reply, New task>new (name lives in bin/board), Project>project, Repair>repair. Pane names remain the program names. Full rebuild/reload is key 'R'.
|
||||
|
||||
Key panels: Board>board lists tasks with status icons before the status word (todo circle, running triangle, blocked square, done tick). Reply>reply shows 'the task' panel (identifier/status/assignee/title/verify command) above a threaded conversation and a reply composer that leaves status alone. Project>project has facts/costs/settings panels; settings shows .harness/harness.toml fields then Paperclip agent limits (monthly budget cents, spend). Repair>repair has health, layout, rebuild-log panels plus an icon test row.
|
||||
|
||||
Data model: .harness/harness.toml is a project's only self-owned file; Paperclip owns tasks, blockers, comments, runs, cost, worktrees, and agent month budgets/spend.
|
||||
|
||||
Recent commit: 2640049 (workspace names capitalised, Reply task panel, Project settings/limits, icon test row). Screenshots in /tmp/mockups/: board-live.png, work-live.png, reply-live.png, new-live.png, project-live.png, repair-live.png.
|
||||
|
||||
## Detailed context from completed turn
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 90%
|
||||
- Reason: Matches the component so future turns can update this context rather than creating transcript noise.; enriched with final-answer detail because persistence should be reusable
|
||||
|
||||
## Related learned memories <!-- same-turn-2026-11-10 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
|
||||
@@ -385,3 +385,70 @@ Tell me which of those you want first, and whether the icons appear after reopen
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-icon-font-fix-help-filter-unblock-safe|paperclip-harness TUI: icon font fix, help/filter/unblock, safe rebuild (commit 09e32da)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-config-2026-11-10-05-00-01|Rich discovery: .config 2026-11-10 05:00:01]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|.config]]
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Update (2026-11-10 05:13:11)
|
||||
|
||||
## Rich discovery update (2026-11-10 05:13:11)
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Source paths observed
|
||||
|
||||
- `etc/profiles/per-user/sam/bin/hunk`
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 84%
|
||||
- Reason: Update component/repository context with detailed discovered structure and configuration
|
||||
|
||||
## Related learned memories <!-- same-turn-2026-11-10 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
|
||||
@@ -248,3 +248,74 @@ Deliverables: built six (plus shell = seven) styled TUI workspaces in the paperc
|
||||
- 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 05:13:11)
|
||||
|
||||
Deliverable: commit 2640049 in paperclip-harness — capitalised Workspace>pane naming (Shell, Board, Work, Reply, New task, Project, Repair), Reply>reply 'the task' panel, Project>project settings+limits panel, Repair>repair icon test row and 3-panel layout.
|
||||
|
||||
Artifacts: bin/board (workspace name word), refreshed screenshots in /tmp/mockups/ (board-live.png, work-live.png, reply-live.png, new-live.png, project-live.png, repair-live.png).
|
||||
|
||||
Validation: build=0; rebuilt via 'R'; seven workspaces, one pane each, all drawing; verified Reply shows AUD-166 done with assignee resolved to a name after a test reply; Repair icon row renders glyphs.
|
||||
|
||||
Follow-ups suggested in order: Board needs-you cards that act on the spot; Work diff panel beside runs at wide widths; Project settings that writes verify command; Repair one-key whole-session check.
|
||||
|
||||
## Detailed deliverable from completed turn
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 90%
|
||||
- Reason: Completed work with concrete artifacts and validation, useful for future context.; enriched with final-answer detail because persistence should be reusable
|
||||
|
||||
## Related learned memories <!-- same-turn-2026-11-10 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
|
||||
@@ -77,3 +77,186 @@ Tell me which of those you want first, and whether the icons appear after reopen
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-icon-font-fix-help-filter-unblock-safe|paperclip-harness TUI: icon font fix, help/filter/unblock, safe rebuild (commit 09e32da)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-config-2026-11-10-05-00-01|Rich discovery: .config 2026-11-10 05:00:01]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|.config]]
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Update (2026-11-10 05:13:11)
|
||||
|
||||
Icons showing as boxes in the harness TUI on host .27 was caused by a missing font setting in that host's Ghostty config, not missing fonts: 98 Nerd Font entries were already installed. Adding the one Ghostty config line and restarting the config fixed it. Note: no nixos-rebuild exists on .27.
|
||||
|
||||
How to verify icon rendering:
|
||||
- Repair>repair, first tab: an 'icons' row prints the glyphs. Real shapes = Nerd Font working; boxes/blanks = terminal still on default font.
|
||||
- Board>board: each task status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Detailed observations from completed turn
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 90%
|
||||
- Reason: Concrete operational discovery about font/icon issue and verification method.; enriched with final-answer detail because persistence should be reusable
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Update (2026-11-10 05:13:11)
|
||||
|
||||
In paperclip-harness, a project's only self-owned file is .harness/harness.toml. It holds: session, machine, folder, paperclip id, watching (task identifier, e.g. AUD-166), and verify command (shown with the task it was derived from, e.g. 'test -s CHANGELOG.md (from AUD-166)'). Paperclip owns everything else: tasks, blockers, comments, runs, cost and worktrees. Paperclip-held agent limits are monthly budgets in cents (researcher 2000, System 3000, Hub 0, Chief of Staff 5000, Documenter 2000, Builder 5000) with spend tracked. Example sandbox project spike-2026-11-10 used folder /tmp/pc-sandbox-2026-11-10 on machine nixos-desktop, session paperclip-harness.
|
||||
|
||||
## Detailed observations from completed turn
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 90%
|
||||
- Reason: Evidence-backed architecture/configuration details useful to future agents.; enriched with final-answer detail because persistence should be reusable
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Update (2026-11-10 05:13:11)
|
||||
|
||||
## Structural/configuration observations (2026-11-10 05:13:11)
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## Source paths observed
|
||||
|
||||
- `etc/profiles/per-user/sam/bin/hunk`
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 82%
|
||||
- Reason: Captured factual repo structure/configuration patterns from rich discovery
|
||||
|
||||
## Related learned memories <!-- same-turn-2026-11-10 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
type: session
|
||||
id: session.paperclip-harness.paperclip-harness-tui-workspace-rename-new-panels-commit-264
|
||||
title: paperclip-harness TUI workspace rename + new panels (commit 2640049)
|
||||
status: active
|
||||
source_of_truth: false
|
||||
freshness: current
|
||||
last_reviewed: 2026-11-10
|
||||
tags:
|
||||
- pack/paperclip-harness
|
||||
- agent-memory/session
|
||||
- paperclip-harness
|
||||
- tui
|
||||
- workspaces
|
||||
- panels
|
||||
- commit-2640049
|
||||
---
|
||||
|
||||
# paperclip-harness TUI workspace rename + new panels (commit 2640049)
|
||||
|
||||
Turn outcome on the paperclip-harness TUI (commit 2640049, message: "Workspaces named for what they are, capitalised; Reply gains a task panel; Project settings and limits; icon test row").
|
||||
|
||||
Workspaces renamed/capitalised, pane names stay program names, format is Workspace>pane:
|
||||
Shell>shell, Board>board, Work>work, Reply>reply, New task>new (workspace name configurable by one word in bin/board), Project>project, Repair>repair. Seven workspaces, one pane each, all drawing; rebuilt via 'R' and verified.
|
||||
|
||||
Panels added:
|
||||
- Reply>reply: 'the task' panel above the thread — identifier, status, assignee by name, title, verify command.
|
||||
- Project>project settings panel: the project's own .harness/harness.toml fields (session, machine, folder, Paperclip id, watched task, verify command + its source task), then Paperclip-held limits (each agent's monthly budget in cents and spend, e.g. Hub budget 0 spent 1, Builder budget 5000 spent 10).
|
||||
- Repair>repair: icon test row printing icon glyphs, with text 'if these are boxes, this terminal is not using a Nerd Font'; plus health, layout and rebuild-log panels.
|
||||
|
||||
Screenshots refreshed in /tmp/mockups/: board-live.png, work-live.png, reply-live.png, new-live.png, project-live.png, repair-live.png.
|
||||
|
||||
Suggested next work in order: Board>board needs-you cards that act on the spot; Work>work diff panel beside runs at wide widths; Project>project settings that write the verify command; Repair>repair one-key whole-session check.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Confidence: 90%
|
||||
- Reason: Rich implementation snapshot from final answer with concrete names, panels, commit, artifacts, and next steps.
|
||||
|
||||
## Related
|
||||
|
||||
- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]]
|
||||
|
||||
## Related learned memories <!-- same-turn-2026-11-10 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
type: session
|
||||
id: session.paperclip-harness.rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-
|
||||
title: Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11
|
||||
status: active
|
||||
source_of_truth: false
|
||||
freshness: current
|
||||
last_reviewed: 2026-11-10
|
||||
tags:
|
||||
- pack/paperclip-harness
|
||||
- agent-memory/session
|
||||
- autolearn
|
||||
- rich-discovery
|
||||
- repo/how-to-tell-whether-the-icons-loaded
|
||||
---
|
||||
|
||||
# Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11
|
||||
|
||||
Sanitized rich discovery snapshot captured automatically from the completed turn.
|
||||
|
||||
|
||||
## Source paths observed
|
||||
|
||||
- `etc/profiles/per-user/sam/bin/hunk`
|
||||
|
||||
## Final answer snapshot
|
||||
|
||||
"No `nixos-rebuild` on `.27`." The fonts are already there — 98 Nerd Font entries installed. What was missing was one line in its Ghostty config, which I added. Your restart of the config was the right move.
|
||||
|
||||
## How to tell whether the icons loaded
|
||||
|
||||
On `.27`, look at **Repair>repair**, first tab. There is now a row that prints the icons themselves:
|
||||
|
||||
```
|
||||
icons · ▶ ■ ◆ if these are boxes, this terminal is not using a Nerd Font
|
||||
```
|
||||
|
||||
If you see actual shapes, the font is working and the panels will show icons everywhere. If those are boxes or blanks, the terminal is still on its default font and one more thing needs checking.
|
||||
|
||||
The second place to look is **Board>board**: each task's status should have a small icon before the word (todo circle, running triangle, blocked square, done tick).
|
||||
|
||||
## Names, the way you asked
|
||||
|
||||
Workspaces are now capitalised and say what they are. Pane names stay as the program names, so everything is written **Workspace>pane**:
|
||||
|
||||
| Workspace>pane | was |
|
||||
|---|---|
|
||||
| Shell>shell | shell |
|
||||
| Board>board | board |
|
||||
| Work>work | work |
|
||||
| Reply>reply | reply |
|
||||
| **New task**>new | new |
|
||||
| Project>project | project |
|
||||
| Repair>repair | repair |
|
||||
|
||||
`New task` is the workspace name now; if you would prefer `What` or `Author`, it is one word in `bin/board`.
|
||||
|
||||
## Missing panels built
|
||||
|
||||
- **Reply>reply** gained a *the task* panel above the thread: identifier, status, assignee by name, the title, and the verify command. You no longer have to go back to Board>board to remember what you are replying about.
|
||||
- **Project>project** settings is now a real panel: the six things this project keeps in its own file (session, machine, folder, Paperclip id, watched task, verify command with the task it came from), then the limits Paperclip holds — every agent with its monthly budget and spend.
|
||||
- **Repair>repair** has the icon row, and its three panels are health, layout and rebuild log.
|
||||
|
||||
Everything rebuilt through `R` and verified: seven workspaces, one pane each, all drawing. Screenshots refreshed in `/tmp/mockups/` — `board-live.png`, `work-live.png`, `reply-live.png`, `new-live.png`, `project-live.png`, `repair-live.png`.
|
||||
|
||||
Committed as `2640049`.
|
||||
|
||||
Next by Workspace>pane, in the order I would do them: **Board>board** needs-you cards that act on the spot, **Work>work** a diff panel visible beside the runs at wide widths, **Project>project** a settings panel that writes the verify command, and **Repair>repair** a one-key check of the whole session.
|
||||
|
||||
## 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 05:13:11 -->
|
||||
|
||||
- context: [[packs/paperclip-harness/20-context/paperclip-harness|paperclip-harness]]
|
||||
- observation: [[packs/paperclip-harness/60-observations/paperclip-harness-tui-editor-pitfall-illegal-rune-literal-af|Nerd Font icon troubleshooting on .27 host]]
|
||||
- action: [[packs/paperclip-harness/40-actions/2026-11-10-wrote-docs-ui-layout-md-and-three-live-layout-mockups|Committed paperclip-harness workspace rename + Reply/Project/Repair panels (2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/paperclip-harness-tui-workspace-rename-new-panels-commit-264|paperclip-harness TUI workspace rename + new panels (commit 2640049)]]
|
||||
- session: [[packs/paperclip-harness/80-sessions/rich-discovery-how-to-tell-whether-the-icons-loaded-2026-11-|Rich discovery: how-to-tell-whether-the-icons-loaded 2026-11-10 05:13:11]]
|
||||
- context: [[packs/paperclip-harness/20-context/tmp|how-to-tell-whether-the-icons-loaded]]
|
||||
30
README.md
30
README.md
@@ -53,18 +53,32 @@ TUIOS it has to close a box.
|
||||
|
||||
## The six workspaces
|
||||
|
||||
Written the way we talk about them: **Workspace>pane**. `Board>board` means the board
|
||||
program running in the Board workspace.
|
||||
Written the way we talk about them: **Workspace>pane**. `Board>tasks` means the tasks pane
|
||||
inside the Board workspace. Every workspace holds real panes — three of them, except Shell
|
||||
— so each panel can be zoomed, resized, closed or moved on its own.
|
||||
|
||||
| Workspace>pane | shows | keys |
|
||||
|---|---|---|
|
||||
| Shell>shell | a prompt in the project folder | — |
|
||||
| Board>board | tasks by group · the chosen task · needs you | ↑↓ j k · enter · a · u · / · tab |
|
||||
| Work>work | runs · change · facts | [ ] tab · ↑↓ j k · g G |
|
||||
| Reply>reply | the task · the thread · the reply box | enter · s · esc |
|
||||
| New task>new | the form · what a task needs · what will be sent | tab · enter · ctrl+n |
|
||||
| Project>project | facts · costs · settings | [ ] tab · ↑↓ j k |
|
||||
| Repair>repair | health · layout · rebuild log | [ ] tab · ↑↓ j k · R · B |
|
||||
| Shell>shell | a prompt in the project folder | — |
|
||||
| Board>tasks | the task tree, filtered with / | ↑↓ j k · enter · a · u · / · ? |
|
||||
| Board>detail | the chosen task: status, blockers, verify command, last run, cost | ↑↓ j k · r |
|
||||
| Board>needs | decisions waiting on you, and the activity of the chosen task | [ ] tab |
|
||||
| Work>runs | run timeline and activity | ↑↓ j k · g G |
|
||||
| Work>change | worktree, branch, commits, changed files, the diff | ↑↓ j k |
|
||||
| Work>facts | task, status, worktree path, branch, base, cost | ↑↓ j k |
|
||||
| Reply>task | the task above the conversation | r |
|
||||
| Reply>thread | every message | ↑↓ j k |
|
||||
| Reply>replybox | the reply box, and the status it will send | enter · alt+enter · s |
|
||||
| New task>form | the five fields | tab · enter |
|
||||
| New task>guidance | what a task needs | — |
|
||||
| New task>preview | exactly what the agent will receive | — |
|
||||
| Project>facts-p | project, folder, machine, session, agents | ↑↓ j k |
|
||||
| Project>costs | spend per task and the total | ↑↓ j k |
|
||||
| Project>settings | the project file, and the limits Paperclip holds | ↑↓ j k |
|
||||
| Repair>health | checks, three states, and an icon test row | ↑↓ j k |
|
||||
| Repair>layout | what TUIOS actually has | ↑↓ j k |
|
||||
| Repair>rebuild-log | the last rebuild's output | ↑↓ j k |
|
||||
|
||||
Choosing a task on the board and pressing Enter writes it into the project's
|
||||
`.harness/harness.toml`, and every other workspace follows within a second.
|
||||
|
||||
63
bin/board
63
bin/board
@@ -41,16 +41,38 @@ def pages(cfg, folder):
|
||||
proj = cfg["paperclip"]["project"]
|
||||
task = pj.watch_task(cfg)
|
||||
# (pane name, workspace name, command). The workspace is what a person reads
|
||||
# on the rail, so it is capitalised and says what the page is for; the pane
|
||||
# keeps the program's own name. Written Workspace>pane when talking about it.
|
||||
# on the rail; the pane keeps its own name. Written Workspace>pane.
|
||||
# Each workspace holds several real panes, not one program drawing panels.
|
||||
b = shlex.quote(bin_path)
|
||||
f = shlex.quote(folder)
|
||||
def pane(name, panel, extra):
|
||||
return "%s -screen %s -panel %s -folder %s %s" % (b, name, panel, f, extra)
|
||||
return task, [
|
||||
("shell", "Shell", "exec zsh -i"),
|
||||
("board", "Board", "%s -screen board -project %s -folder %s" % (shlex.quote(bin_path), proj, shlex.quote(folder))),
|
||||
("work", "Work", "%s -screen work -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))),
|
||||
("reply", "Reply", "%s -screen reply -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))),
|
||||
("new", "New task", "%s -screen new -project %s" % (shlex.quote(bin_path), proj)),
|
||||
("project", "Project", "%s -screen project -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))),
|
||||
("repair", "Repair", "%s -screen repair -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))),
|
||||
|
||||
("tasks", "Board", pane("board", "tasks", "-project " + shlex.quote(proj))),
|
||||
("detail", "Board", pane("board", "detail", "-project " + shlex.quote(proj))),
|
||||
("needs", "Board", pane("board", "needs", "-project " + shlex.quote(proj))),
|
||||
|
||||
("runs", "Work", pane("work", "runs", "")),
|
||||
("change", "Work", pane("work", "change", "")),
|
||||
("facts", "Work", pane("work", "facts", "")),
|
||||
|
||||
("task", "Reply", pane("reply", "task", "")),
|
||||
("thread", "Reply", pane("reply", "thread", "")),
|
||||
("replybox", "Reply", pane("reply", "reply", "")),
|
||||
|
||||
("form", "New task", pane("new", "form", "-project " + shlex.quote(proj))),
|
||||
("guidance", "New task", pane("new", "guidance", "-project " + shlex.quote(proj))),
|
||||
("preview", "New task", pane("new", "preview", "-project " + shlex.quote(proj))),
|
||||
|
||||
("facts-p", "Project", pane("project", "facts", "")),
|
||||
("costs", "Project", pane("project", "costs", "")),
|
||||
("settings", "Project", pane("project", "settings", "")),
|
||||
|
||||
("health", "Repair", pane("repair", "health", "")),
|
||||
("layout", "Repair", pane("repair", "layout", "")),
|
||||
("rebuild-log", "Repair", pane("repair", "log", "")),
|
||||
]
|
||||
|
||||
|
||||
@@ -75,15 +97,28 @@ def main():
|
||||
if tuios.close_pane(name, session):
|
||||
h.say(" closed %s" % name)
|
||||
|
||||
# Group the panes by the workspace they belong to, in first-seen order, so a
|
||||
# workspace is claimed once and all of its panes are made inside it.
|
||||
order = []
|
||||
groups = {}
|
||||
for name, workspace, command in page_list:
|
||||
result, ident, ws = tuios.open_pane(
|
||||
name, workspace, folder, command, session,
|
||||
adopt_existing=(name == "shell"),
|
||||
)
|
||||
h.say(" %-9s %-8s workspace %s" % (name, result, ws))
|
||||
if workspace not in groups:
|
||||
groups[workspace] = []
|
||||
order.append(workspace)
|
||||
groups[workspace].append((name, command))
|
||||
|
||||
for workspace in order:
|
||||
number = tuios.claim(workspace, session)
|
||||
for name, command in groups[workspace]:
|
||||
if any((w.get("custom_name") or "") == name for w in tuios.windows(session)):
|
||||
h.say(" %-11s adopted %s" % (name, workspace))
|
||||
continue
|
||||
tuios.new_window(name, number, folder, command, session)
|
||||
h.say(" %-11s created %s" % (name, workspace))
|
||||
|
||||
# Number the workspaces in the order a person reads them.
|
||||
for number, name in tuios.arrange([(p[0], p[1]) for p in page_list], session):
|
||||
h.say(" placed %-8s workspace %s" % (name, number))
|
||||
h.say(" placed %-11s workspace %s" % (name, number))
|
||||
|
||||
in_use = [w for w in tuios.workspaces(session) if (w.get("window_count") or 0) > 0]
|
||||
h.say("workspaces in use: %d" % len(in_use))
|
||||
|
||||
BIN
bin/harness
BIN
bin/harness
Binary file not shown.
Binary file not shown.
@@ -133,30 +133,51 @@ def open_pane(name, workspace_name, cwd, command, session=None, adopt_existing=F
|
||||
|
||||
|
||||
def arrange(order, session=None):
|
||||
"""Put these panes on workspaces 1..N in this order, and name the workspaces.
|
||||
"""Put these panes on their workspaces, in this order, and name the workspaces.
|
||||
|
||||
`order` is a list of (pane name, workspace name) pairs. The pane keeps its
|
||||
own name; the workspace gets the name a person reads on the rail.
|
||||
|
||||
TUIOS numbering follows the order panes land in, and closing panes renumbers
|
||||
the rest, so a rebuild can scatter the pages. Moving each pane once, at the
|
||||
end, is what makes the rail readable and stable.
|
||||
`order` is a list of (pane name, workspace name). Panes that name the same
|
||||
workspace are put in that same workspace, so one workspace can hold several
|
||||
panes. Workspaces are numbered in the order they first appear.
|
||||
"""
|
||||
names = []
|
||||
for _pane, workspace in order:
|
||||
if workspace not in names:
|
||||
names.append(workspace)
|
||||
|
||||
moved = []
|
||||
for number, (pane, workspace) in enumerate(order, start=1):
|
||||
win = next((w for w in windows(session) if (w.get("custom_name") or "") == pane), None)
|
||||
if win is None:
|
||||
continue
|
||||
if win.get("workspace") != number:
|
||||
subprocess.run(
|
||||
["tuios", "move-window", str(number), "-w", win["window_id"], "-s", session or SESSION],
|
||||
capture_output=True, text=True, timeout=30, check=False,
|
||||
)
|
||||
for number, workspace in enumerate(names, start=1):
|
||||
for pane, ws in order:
|
||||
if ws != workspace:
|
||||
continue
|
||||
win = next((w for w in windows(session) if (w.get("custom_name") or "") == pane), None)
|
||||
if win is None:
|
||||
continue
|
||||
if win.get("workspace") != number:
|
||||
subprocess.run(
|
||||
["tuios", "move-window", str(number), "-w", win["window_id"], "-s", session or SESSION],
|
||||
capture_output=True, text=True, timeout=30, check=False,
|
||||
)
|
||||
_name_workspace(number, workspace, session)
|
||||
moved.append((number, workspace))
|
||||
return moved
|
||||
|
||||
|
||||
def new_window(name, workspace_number, cwd, command, session=None):
|
||||
"""Create one pane in a workspace that has already been claimed."""
|
||||
subprocess.run(
|
||||
[
|
||||
"tuios", "new-window", name,
|
||||
"-s", session or SESSION,
|
||||
"--workspace", str(workspace_number),
|
||||
"--cwd", cwd,
|
||||
"--no-focus", "--keep-open",
|
||||
"--", "/bin/sh", "-c", command,
|
||||
],
|
||||
capture_output=True, text=True, timeout=60, check=False,
|
||||
)
|
||||
return name
|
||||
|
||||
|
||||
def close_pane(name, session=None):
|
||||
for w in windows(session):
|
||||
if (w.get("custom_name") or "") == name:
|
||||
|
||||
85
tui/board.go
85
tui/board.go
@@ -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…"
|
||||
|
||||
25
tui/frame.go
25
tui/frame.go
@@ -289,3 +289,28 @@ func helpOverlay(title string, entries [][2]string, w, h int) string {
|
||||
p := panel{title: title, width: boxW, height: boxH, focused: true}
|
||||
return lipgloss.Place(w, h, lipgloss.Center, lipgloss.Center, p.render(lines))
|
||||
}
|
||||
|
||||
// singlePanel draws one panel filling the whole pane: a title, optional tabs,
|
||||
// a scrolling body, and one line of keys. It is what a pane shows when a
|
||||
// workspace has been split into real panes instead of one program drawing
|
||||
// several panels inside itself.
|
||||
func singlePanel(title string, tabs []string, active int, body []string, w, h int, footer string, scroll int) string {
|
||||
if w < 12 || h < 4 {
|
||||
return fmt.Sprintf("this pane is %d × %d; it needs 12 × 4", w, h)
|
||||
}
|
||||
p := panel{title: title, tabs: tabs, active: active, width: w, height: h - 1, focused: true}
|
||||
visible := p.bodyHeight()
|
||||
maxScroll := max(0, len(body)-visible)
|
||||
if scroll > maxScroll {
|
||||
scroll = maxScroll
|
||||
}
|
||||
if scroll < 0 {
|
||||
scroll = 0
|
||||
}
|
||||
from := scroll
|
||||
to := min(len(body), from+visible)
|
||||
if to < from {
|
||||
to = from
|
||||
}
|
||||
return p.render(body[from:to]) + "\n" + truncateVisible(footer, w)
|
||||
}
|
||||
|
||||
13
tui/main.go
13
tui/main.go
@@ -20,6 +20,7 @@ const defaultAgent = "403de1f7-645c-4d35-bc7e-19b01d77bacc"
|
||||
|
||||
func main() {
|
||||
screen := flag.String("screen", "board", "which screen to run: board, work, reply, new, project, repair")
|
||||
panelName := flag.String("panel", "", "run one panel of that screen, for a workspace split into real panes")
|
||||
folder := flag.String("folder", "", "project folder, for the board screen (defaults to the current directory)")
|
||||
light := flag.String("theme", "", "colour variant: dark, light, or empty to follow the terminal")
|
||||
ascii := flag.Bool("ascii", false, "plain characters instead of Nerd Font icons")
|
||||
@@ -52,21 +53,21 @@ func main() {
|
||||
if dir == "" {
|
||||
dir, _ = os.Getwd()
|
||||
}
|
||||
m = newBoardModel(client, proj, dir)
|
||||
m = newBoardModel(client, proj, dir, *panelName)
|
||||
|
||||
case "project":
|
||||
dir := *folder
|
||||
if dir == "" {
|
||||
dir, _ = os.Getwd()
|
||||
}
|
||||
m = newProjectModel(client, dir)
|
||||
m = newProjectModel(client, dir, *panelName)
|
||||
|
||||
case "repair":
|
||||
dir := *folder
|
||||
if dir == "" {
|
||||
dir, _ = os.Getwd()
|
||||
}
|
||||
m = newRepairModel(client, dir)
|
||||
m = newRepairModel(client, dir, *panelName)
|
||||
|
||||
case "work":
|
||||
dir := *folder
|
||||
@@ -77,7 +78,7 @@ func main() {
|
||||
if issue == "" {
|
||||
issue = watchedTask(dir)
|
||||
}
|
||||
m = newWorkModel(client, dir, issue)
|
||||
m = newWorkModel(client, dir, issue, *panelName)
|
||||
|
||||
case "tree":
|
||||
proj, err := client.ResolveProject(*project)
|
||||
@@ -100,7 +101,7 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
m = newTaskForm(client, proj, *agent)
|
||||
m = newTaskForm(client, proj, *agent, *panelName)
|
||||
|
||||
case "log":
|
||||
task, err := resolveIssue(client, *issue)
|
||||
@@ -124,7 +125,7 @@ func main() {
|
||||
}
|
||||
id = task.ID
|
||||
}
|
||||
m = newReplyModel(client, dir, id)
|
||||
m = newReplyModel(client, dir, id, *panelName)
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown screen %q: use board, work, reply, new, project or repair\n", *screen)
|
||||
|
||||
@@ -27,6 +27,7 @@ type createdMsg struct {
|
||||
}
|
||||
|
||||
type newTaskModel struct {
|
||||
panel string
|
||||
client *paperclip.Client
|
||||
proj paperclip.Project
|
||||
agent string
|
||||
@@ -61,8 +62,8 @@ var fieldHints = []string{
|
||||
"yes, to hand it to the configured agent after the blocker is set",
|
||||
}
|
||||
|
||||
func newTaskForm(c *paperclip.Client, proj paperclip.Project, agentID string) newTaskModel {
|
||||
m := newTaskModel{client: c, proj: proj, agent: agentID}
|
||||
func newTaskForm(c *paperclip.Client, proj paperclip.Project, agentID, panel string) newTaskModel {
|
||||
m := newTaskModel{client: c, proj: proj, agent: agentID, panel: panel}
|
||||
for range fieldNames {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = ""
|
||||
@@ -240,7 +241,39 @@ func (m newTaskModel) guidanceLines(width int) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// singleView is one panel filling the pane: the form, the guidance, or what
|
||||
// will be sent. The form keeps the keyboard, because its letters are text.
|
||||
func (m newTaskModel) singleView() string {
|
||||
title := "new task"
|
||||
var body []string
|
||||
footer := ""
|
||||
switch m.panel {
|
||||
case "guidance":
|
||||
title = "what a task needs"
|
||||
body = m.guidanceLines(m.width - 3)
|
||||
footer = hintLine([]string{keyHint("r", "nothing to read here"), keyHint("R", "rebuild")}, m.width)
|
||||
case "preview":
|
||||
title = "what will be sent"
|
||||
body = m.previewLines(m.width - 3)
|
||||
footer = hintLine([]string{keyHint("tab", "back to the form"), keyHint("R", "rebuild")}, m.width)
|
||||
default:
|
||||
body = m.formLines(m.width - 3)
|
||||
footer = hintLine([]string{
|
||||
keyHint("tab", "next field"),
|
||||
keyHint("enter", "next, and create on the last"),
|
||||
keyHint("esc", "cancel"),
|
||||
}, m.width)
|
||||
}
|
||||
return singlePanel(title, nil, 0, body, m.width, m.height, footer, 0)
|
||||
}
|
||||
|
||||
func (m newTaskModel) View() string {
|
||||
if m.panel != "" {
|
||||
if m.width == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
return m.singleView()
|
||||
}
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type taskCost struct {
|
||||
}
|
||||
|
||||
type projectModel struct {
|
||||
panel string
|
||||
client *paperclip.Client
|
||||
folder string
|
||||
|
||||
@@ -55,8 +56,8 @@ type projectModel struct {
|
||||
|
||||
var projectTabs = []string{"facts", "costs", "settings"}
|
||||
|
||||
func newProjectModel(c *paperclip.Client, folder string) projectModel {
|
||||
return projectModel{client: c, folder: folder}
|
||||
func newProjectModel(c *paperclip.Client, folder, panel string) projectModel {
|
||||
return projectModel{client: c, folder: folder, panel: panel}
|
||||
}
|
||||
|
||||
func (m projectModel) Init() tea.Cmd {
|
||||
@@ -193,7 +194,46 @@ func (m projectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// singleView is one panel filling the pane: facts, costs or settings.
|
||||
func (m projectModel) singleView() string {
|
||||
titles := map[string]string{"facts": "the project", "costs": "what it has cost", "settings": "settings and limits"}
|
||||
title := titles[m.panel]
|
||||
if title == "" {
|
||||
title = "the project"
|
||||
}
|
||||
var body []string
|
||||
switch m.panel {
|
||||
case "costs":
|
||||
body = m.costLines()
|
||||
case "settings":
|
||||
body = m.settingLines()
|
||||
default:
|
||||
body = m.factLines()
|
||||
}
|
||||
if m.err != nil {
|
||||
body = append([]string{lipgloss.NewStyle().Foreground(lipgloss.Color(active.Error)).Render("error: " + m.err.Error()), ""}, body...)
|
||||
}
|
||||
footer := hintLine([]string{
|
||||
keyHint("↑↓ j k", "scroll"),
|
||||
keyHint("r", "read again"),
|
||||
keyHint("R", "rebuild"),
|
||||
keyHint("?", "keys"),
|
||||
}, m.width)
|
||||
if m.showHelp {
|
||||
return helpOverlay(title, [][2]string{
|
||||
{"↑ ↓ j k", "scroll"}, {"r / R", "read again / rebuild"}, {"q", "leave"},
|
||||
}, m.width, m.height)
|
||||
}
|
||||
return singlePanel(title, nil, 0, body, m.width, m.height, footer, m.scroll)
|
||||
}
|
||||
|
||||
func (m projectModel) View() string {
|
||||
if m.panel != "" {
|
||||
if m.width == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
return m.singleView()
|
||||
}
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ type layoutRow struct {
|
||||
}
|
||||
|
||||
type repairModel struct {
|
||||
panel string
|
||||
client *paperclip.Client
|
||||
folder string
|
||||
|
||||
@@ -58,8 +59,8 @@ type repairModel struct {
|
||||
|
||||
var repairTabs = []string{"health", "layout", "rebuild log"}
|
||||
|
||||
func newRepairModel(c *paperclip.Client, folder string) repairModel {
|
||||
return repairModel{client: c, folder: folder}
|
||||
func newRepairModel(c *paperclip.Client, folder, panel string) repairModel {
|
||||
return repairModel{client: c, folder: folder, panel: panel}
|
||||
}
|
||||
|
||||
func (m repairModel) Init() tea.Cmd {
|
||||
@@ -254,7 +255,45 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// singleView is one panel filling the pane: health, layout or the rebuild log.
|
||||
func (m repairModel) singleView() string {
|
||||
titles := map[string]string{"health": "checks", "layout": "the layout as TUIOS has it", "log": "the last rebuild"}
|
||||
title := titles[m.panel]
|
||||
if title == "" {
|
||||
title = "checks"
|
||||
}
|
||||
var body []string
|
||||
switch m.panel {
|
||||
case "layout":
|
||||
body = m.layoutLines()
|
||||
case "log":
|
||||
body = m.logLines()
|
||||
default:
|
||||
body = m.healthLines()
|
||||
}
|
||||
footer := hintLine([]string{
|
||||
keyHint("↑↓ j k", "scroll"),
|
||||
keyHint("R", "rebuild every pane"),
|
||||
keyHint("B", "add missing panes"),
|
||||
keyHint("r", "check again"),
|
||||
keyHint("?", "keys"),
|
||||
}, m.width)
|
||||
if m.showHelp {
|
||||
return helpOverlay(title, [][2]string{
|
||||
{"R", "rebuild every pane"}, {"B", "add only the missing panes"},
|
||||
{"↑ ↓ j k", "scroll"}, {"r", "check again"}, {"q", "leave"},
|
||||
}, m.width, m.height)
|
||||
}
|
||||
return singlePanel(title, nil, 0, body, m.width, m.height, footer, m.scroll)
|
||||
}
|
||||
|
||||
func (m repairModel) View() string {
|
||||
if m.panel != "" {
|
||||
if m.width == 0 {
|
||||
return "checking…"
|
||||
}
|
||||
return m.singleView()
|
||||
}
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
|
||||
61
tui/reply.go
61
tui/reply.go
@@ -28,6 +28,7 @@ type replyThreadMsg struct {
|
||||
type replySentMsg struct{ task paperclip.Issue }
|
||||
|
||||
type replyModel struct {
|
||||
panel string
|
||||
client *paperclip.Client
|
||||
folder string
|
||||
issue string
|
||||
@@ -50,8 +51,9 @@ type replyModel struct {
|
||||
height int
|
||||
}
|
||||
|
||||
func newReplyModel(c *paperclip.Client, folder, issueID string) replyModel {
|
||||
return replyModel{client: c, folder: folder, issue: issueID, explicit: issueID != ""}
|
||||
func newReplyModel(c *paperclip.Client, folder, issueID, panel string) replyModel {
|
||||
return replyModel{client: c, folder: folder, issue: issueID, explicit: issueID != "",
|
||||
panel: panel, inBox: panel == "reply"}
|
||||
}
|
||||
|
||||
func (m replyModel) Init() tea.Cmd {
|
||||
@@ -227,7 +229,62 @@ func (m replyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// singleView is one panel filling the pane: the task, the thread, or the box.
|
||||
func (m replyModel) singleView() string {
|
||||
title := "thread"
|
||||
var body []string
|
||||
footer := ""
|
||||
switch m.panel {
|
||||
case "task":
|
||||
title = "the task"
|
||||
body = m.headerLines(m.width - 3)
|
||||
footer = hintLine([]string{keyHint("r", "read again"), keyHint("R", "rebuild")}, m.width)
|
||||
case "reply":
|
||||
title = "reply · " + m.statusNote()
|
||||
body = m.composerLines(m.width-3, m.height-3)
|
||||
footer = hintLine([]string{
|
||||
keyHint("enter", "send"),
|
||||
keyHint("alt+enter", "new line"),
|
||||
keyHint("s", "status"),
|
||||
}, m.width)
|
||||
if m.notice != "" {
|
||||
footer = lipgloss.NewStyle().Foreground(lipgloss.Color(active.Success)).Render(m.notice)
|
||||
}
|
||||
if m.err != nil {
|
||||
footer = lipgloss.NewStyle().Foreground(lipgloss.Color(active.Error)).Render(truncateVisible("error: "+m.err.Error(), m.width))
|
||||
}
|
||||
default:
|
||||
body = m.threadLines(m.width - 3)
|
||||
footer = hintLine([]string{
|
||||
keyHint("↑↓ j k", "scroll"),
|
||||
keyHint("r", "read again"),
|
||||
keyHint("R", "rebuild"),
|
||||
keyHint("?", "keys"),
|
||||
}, m.width)
|
||||
}
|
||||
if m.showHelp {
|
||||
return helpOverlay(title, [][2]string{
|
||||
{"enter", "write a reply"}, {"s", "status to send with it"},
|
||||
{"↑ ↓ j k", "scroll"}, {"r / R", "read again / rebuild"},
|
||||
}, m.width, m.height)
|
||||
}
|
||||
return singlePanel(title, nil, 0, body, m.width, m.height, footer, m.scroll)
|
||||
}
|
||||
|
||||
func (m replyModel) statusNote() string {
|
||||
if m.next == "" {
|
||||
return "leaves the status alone"
|
||||
}
|
||||
return "also sets the status to " + m.next
|
||||
}
|
||||
|
||||
func (m replyModel) View() string {
|
||||
if m.panel != "" {
|
||||
if m.width == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
return m.singleView()
|
||||
}
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
|
||||
47
tui/work.go
47
tui/work.go
@@ -29,6 +29,7 @@ type workMsg struct {
|
||||
}
|
||||
|
||||
type workModel struct {
|
||||
panel string
|
||||
client *paperclip.Client
|
||||
folder string
|
||||
issue string
|
||||
@@ -52,8 +53,8 @@ type workModel struct {
|
||||
|
||||
var workTabs = []string{"runs", "change", "facts"}
|
||||
|
||||
func newWorkModel(c *paperclip.Client, folder, issueID string) workModel {
|
||||
return workModel{client: c, folder: folder, issue: issueID, explicit: issueID != ""}
|
||||
func newWorkModel(c *paperclip.Client, folder, issueID, panel string) workModel {
|
||||
return workModel{client: c, folder: folder, issue: issueID, explicit: issueID != "", panel: panel}
|
||||
}
|
||||
|
||||
func (m workModel) Init() tea.Cmd {
|
||||
@@ -187,7 +188,49 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// singleView is one panel filling the pane: runs, change or facts.
|
||||
func (m workModel) singleView() string {
|
||||
titles := map[string]string{"runs": "runs and activity", "change": "the change", "facts": "worktree facts"}
|
||||
title, ok := titles[m.panel]
|
||||
if !ok {
|
||||
title = "runs and activity"
|
||||
}
|
||||
var body []string
|
||||
switch m.panel {
|
||||
case "change":
|
||||
body = m.diffLines(m.width - 3)
|
||||
case "facts":
|
||||
body = m.factsLines(m.width - 3)
|
||||
default:
|
||||
body = m.runsLines(m.width - 3)
|
||||
}
|
||||
head := m.task.Identifier
|
||||
if head == "" {
|
||||
head = "no task chosen"
|
||||
}
|
||||
footer := hintLine([]string{
|
||||
keyHint("↑↓ j k", "scroll"),
|
||||
keyHint("g G", "top / bottom"),
|
||||
keyHint("r", "read again"),
|
||||
keyHint("R", "rebuild"),
|
||||
keyHint("?", "keys"),
|
||||
}, m.width)
|
||||
if m.showHelp {
|
||||
return helpOverlay(title, [][2]string{
|
||||
{"↑ ↓ j k", "scroll"}, {"g / G", "top / bottom"},
|
||||
{"r / R", "read again / rebuild"}, {"q", "leave"},
|
||||
}, m.width, m.height)
|
||||
}
|
||||
return singlePanel(title+" · "+head, nil, 0, body, m.width, m.height, footer, m.scroll)
|
||||
}
|
||||
|
||||
func (m workModel) View() string {
|
||||
if m.panel != "" {
|
||||
if m.width == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
return m.singleView()
|
||||
}
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "reading…"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user