Add label workflows to the CLI and TUI

Merged
RO
2mo ago
+2635-103

Summary

Add HEY label workflows to both the CLI and the Mail TUI, using the same Labels terminology as the web UI while retaining the server and SDK's internal folder resource names.

  • add hey labels and hey label <id> with cursor pagination, limits, complete traversal, and structured/styled output
  • add hey label add|create|remove for existing labels, new labels, individual removal, and removing every label
  • append labels to Mail navigation and add n/p label pagination
  • add a height-aware g label picker for adding, creating, and removing labels
  • pin the released hey-sdk/go@v0.6.1 pagination API

Behavior

Mail
├── HEY boxes
└── Labels
    ├── open labeled threads
    ├── n / p: next or previous page
    └── g: manage labels
        ├── add an existing label
        ├── create and add a label
        ├── remove one label
        └── remove every label

CLI commands:

hey labels
hey label <label-id> [--limit N | --all | --page CURSOR]
hey label add <thread-id>... --to <label-id>
hey label create <name> <thread-id>...
hey label remove <thread-id>... --from <label-id|all>

Label reads expose posting IDs, derived topic IDs, next_page, and total_count. Mutations validate positive, unique thread IDs before making requests.

Scope and compatibility

  • Uses existing typed HEY JSON operations; no server API change or HTML scraping.
  • Discovers labels through /my/navigation.json and reads them through the SDK Folders() service.
  • Preserves hey-sdk and wire-level folder names such as /folders and folder_id as implementation details.
  • Labels remain separate from box move destinations and cannot capture HEY box shortcuts, icons, or colliding IDs.
  • Label discovery and page loads reject stale responses, preserve known labels on transient failures, and support retry.
  • Markdown output now sanitizes terminal control characters and escapes table layout characters for all commands.
  • No migrations, feature flags, configuration, or deployment ordering are required. Rollback is a revert of this PR.

Validation

GOWORK=off make check
PASS

GOWORK=off make race-test
PASS

GOWORK=off make coverage
73.021% (5,700 / 7,806 statements), floor 70.8%

GOWORK=off make build
PASS

cd tests/smoke && GOWORK=off go test -count=1 -v -run '^TestLabel'
PASS

git diff --check
PASS

Unit coverage includes CLI request contracts, validation-before-request behavior, pagination, styled/Markdown sanitization, TUI navigation and picker workflows, retry and stale-response handling, pagination key scoping, and box/label identity collisions. Independent and Copilot reviews identified actionable CLI, TUI, output-contract, and smoke-cleanup edge cases. This head resolves every finding and both suggestions suppressed by Copilot’s follow-up review; all review threads are resolved, and focused re-review found no remaining P0/P1/P2 issues.

Reviewer focus

  1. internal/cmd/label.go and internal/folders/navigation.go: command contracts, opaque pagination cursors, and label discovery.
  2. internal/tui/mail.go and internal/tui/folders.go: source lifecycle, n/p scoping, stale-response guards, and label picker behavior.
  3. internal/output/writer.go: shared Markdown sanitization and escaping.

Traceability

This PR must not be merged without explicit approval.


Summary by cubic

Adds Labels to the CLI and Mail TUI so users can list and manage labels without the web app. Previously labels were unavailable; now Mail shows labels after HEY boxes, supports per‑label n/p pagination, and a height‑aware g picker to add, create, or remove labels. Markdown output now sanitizes terminal controls, escapes table pipes while preserving backslashes, and renders newlines as
.

  • CLI: hey labels; hey label <id> with cursor pagination (--limit, --all, --page); hey label add|create|remove with positive, unique thread ID validation.
  • TUI: discovers labels via /my/navigation.json; reads label pages with SDK Folders().GetPage; g manages add/create/remove; n/p paginates per label; guards reject stale navigation, keep known labels on transient failures; preserves boxes alongside labels on ID/name collisions; move picker excludes labels from destinations.
  • Implementation notes: label file/unfile via Postings().File/Unfile; create via Postings().CreateFolder; “folders” remain an internal term. Fixes hey box thread ID display to prefer topic ID and fall back to posting ID. Pins github.com/basecamp/hey-sdk/go to v0.6.1. Help, .surface, README, skills/hey, API coverage, and tests (unit and smoke) updated. No server changes, migrations, or flags; rollback is a revert.

Reviewer focus

  • CLI: internal/cmd/label.go for command contracts, validation, pagination; curated help in internal/cmd/help.go.
  • TUI: internal/folders/navigation.go for label discovery; internal/tui/mail.go and internal/tui/folders.go for per‑label pagination, picker behavior, and stale‑response guards; internal/tui/move.go and internal/tui/nav.go for excluding labels from move destinations and preserving boxes when IDs/names collide.
  • Output: internal/output/writer.go Markdown sanitization and escaping.