Open
Conversation
Plan for a new tb-pr tool — kanban TUI + non-interactive CLI for tracking GitHub PRs that need attention across the Productive org. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add new tb-pr workspace crate with clap CLI scaffolding for all planned subcommands (tui, list, show, refresh, open, prime, skill, config, doctor). Only doctor, config init/show, and prime are wired up — the rest return a "not implemented" error noting the milestone where they land. Goal: cargo build -p tb-pr succeeds and tb-pr doctor reports gh auth status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add core module with github client (parallel search + per-PR detail fetch via gh auth token + reqwest), model structs (Pr, Column, BoardState), classifier (size bucket, rotting bucket per column), and productive task URL extraction. Wire list --json to output the four column JSON dump. Support --column=<slug> and --stale-days=<n> filters. ready_to_merge_mine stays empty until M3 lands the reviews API split. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add core::reviews with per-reviewer latest-state logic, extend the gh client with pull_reviews and commit_date, and rework fetch_board_state: - Split review_mine into review_mine (still needs review) and ready_to_merge_mine (≥1 approval + no pending CHANGES_REQUESTED). - Filter waiting_on_author to keep only PRs where the viewer's last review was COMMENTED or CHANGES_REQUESTED. Flag has_new_commits_since_my_review when the head commit post-dates the review, driving the 🆕 marker planned for the TUI. Column counts verified against gh api: 34/6/1/33/20 (was 34/7/0/33/32 before the split + filter). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement human-readable output for the three non-interactive commands: - list (default, no --json): flattened table sorted by urgency bucket (critical → fresh), with column tag, size, color-coded age, task ID, and a 🆕 marker for PRs where the author pushed after the viewer's review. Footer shows per-column counts + fetch timestamp. - show <ref>: detail view — metadata, per-reviewer latest-state summary, viewer's last review with 🆕 if author pushed since. - open <ref>: resolve the PR URL and open it via `open`/`xdg-open`. `<ref>` accepts a full GitHub URL, `owner/repo#N`, or a bare number (resolved from `git remote get-url origin`). Extended PullDetail with title/body/user/draft/created_at/comments so show only needs one PR endpoint call (plus reviews + head commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap toolbox-core::cache for the tb-pr data model: - list: reads BoardState from cache (5m TTL), falls back to fetch. Footer shows "refreshed Nm ago" based on fetched_at. - show: caches the per-PR detail+reviews+head-date payload keyed by URL. - refresh: clears cache, fetches fresh, saves. Enables toolbox-core `cache` feature. Adds Serialize to PullDetail, BaseRef, HeadRef, SearchItem/User, Review/User so the show payload can round-trip through the cache. Warm list hits in <10ms (vs ~5s cold). Refresh forces re-fetch. Closes productive-work-7j6
Add ratatui + crossterm workspace deps and a tui/ module: - app.rs: App state, event loop, arrow+hjkl nav, Enter opens URL, q/Esc/Ctrl-C quit. - columns.rs: header (user + "refreshed Nm ago"), five equal-width columns, footer. - card.rs: two-row PR card (title, repo#num + size + age). tb-pr tui (and bare tb-pr) now launches the kanban board against the same cached BoardState used by list/show. No auto-refresh yet — one-shot fetch on startup (cache hit when fresh). Covered by two unit tests: navigation wrap/clamp and a TestBackend render smoke test. Closes productive-work-sfz
TUI presentation: - Each PR renders as its own bordered Block, border color from the rotting bucket (grey/green/yellow/orange/red). Selected card uses reversed + bold on the same color. - Card body: bold title (🆕 prefix for waiting-on-author with new commits), repo#N + [P-xxx] task chip, SIZE + AGE + 💬N comments. - Column scrolls: selection keeps the card in view; "+N more ↓" hint when clipped. Dimmed borders on unfocused columns. - Header shows spinner + "fetching…" during refresh and ⚠ err message on failure without dropping the previous state. Event loop: - Migrated to an mpsc channel fed by a blocking keyboard thread, a tokio `interval(5 min)` auto-refresh tick, and a 120ms animation tick. Fetches are spawned as tokio tasks — UI never blocks. - Intent enum decouples key handling from side effects so handle_key stays unit-testable. Keybinds added: t (open Productive task), r (manual refresh, no-op while fetching), c (copy URL via pbcopy/xclip), ? (help popup), d (alias for Enter). Ctrl-C always quits. Closes productive-work-1ap
Two UX tweaks for the TUI only — CLI output (`list`, `show`, `--json`) keeps the raw GitHub title. - `display_title()` strips conventional-commit prefixes (fix/feat/ refactor/chore/docs/update/test/ci/perf/build/style/revert), including scoped forms (`fix(tb-pr):`) and breaking markers (`feat!:`). Only strips when the head before the colon matches a known tag, so "this: whatever" sentences stay intact. - New `w` keybind flips `full_titles`: titles wrap across as many lines as needed and card height grows per-PR. Scroll math now walks variable heights to keep the selected card fully visible, and resets when the mode toggles so re-anchoring is predictable.
Swap `review-requested:@me` for `user-review-requested:@me` in the GitHub search query. The former matches PRs where the user OR any team they're a member of was requested (via CODEOWNERS), which meant the column lit up for every team PR. The latter is direct-only. Dropped waiting_on_me from 33 to 8 on ilucin@productiveio.
- prime command now loads the cached BoardState (or fetches) and emits a markdown summary: counts per column, plus oldest-first lists for waiting-on-me, ready-to-merge, and waiting-on-author. PRs include age, size, Productive task chip, 💬 comments, and 🆕 new-commit marker. - SKILL.md rewritten: clear capabilities, quick reference, and a live `!\`tb-pr prime\`` directive so Claude sessions get current state. - install.sh --all now includes tb-pr. Closes productive-work-1sn
- doctor now verifies cache readability (reports cached PR count when present) and probes /orgs/productiveio so a bad token / missing org access surfaces immediately. Migrated to async. - Rate-limit detection in the GitHub client: 429 or `X-RateLimit-Remaining: 0` on a 403 now returns a friendly message with the reset time instead of the raw body. - New crates/tb-pr/README.md documenting features, columns, keybinds, config, and cache. Root README.md gains a tb-pr row and a `Bash(tb-pr:*)` permission entry. - scripts/bump.sh accepts tb-pr. Version stays at 0.1.0 (initial). Closes productive-work-tbi
- Wire `refresh.interval_minutes` through `FetchCtx` so the TUI auto-refresh cadence honours config instead of a hardcoded 5m. Clamp to ≥30s to guard against accidental hammering. - Add an `App.status` slot distinct from `last_error`; route clipboard success into it and render it green (`✓`) instead of red (`⚠`). - Paginate `search_issues` via the `Link: rel="next"` header up to 10 pages (GitHub's 1000-result cap). Previously columns silently truncated at 100 items, which mattered most for `reviewed-by:@me`. - Replace silent `.ok()` on the head-commit-date fetch in `tb-pr show` with a stderr warning so users know the "🆕 new commits" indicator was skipped rather than seeing it vanish without explanation. Adds unit tests for `parse_link_next` and status/error exclusivity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Dedup `show.rs` "latest review per user" by exposing `ReviewSummary::iter_latest()` and reusing it in place of the inline hashmap. Keeps classification logic single-sourced with the fetcher. - Key per-PR lookup maps by `(owner, repo, number)` instead of `(repo, number)`. Safe today since we scope to `org:productiveio`, but the owner-less key was a quiet footgun the moment the org is overridden or a mirror-forked repo shares a name. New `PrKey` alias names the contract. - Cap concurrent per-PR API calls (`pull_detail`, `pull_reviews`, `commit_date`) with a `Semaphore`(16) so a 100-PR refresh doesn't trip GitHub's secondary rate limiter. - Swap the bare `setup_terminal`/`restore_terminal` pair for a `RawModeGuard` with Drop. Panics inside `event_loop` now restore the terminal instead of leaving the shell in raw-mode + alt-screen. - Extract `is_wait_on_author_state()` as a pure helper — makes the column-filter predicate testable without a live `GhClient`. - Tighten `parse_pr_ref` / `parse_git_remote` to https-only; drop the http:// branch so a typoed or log-scrubbed URL fails loudly. - Add `crates/tb-pr/tests/cli.rs` integration smoke tests (assert_cmd + predicates) for `--help`, `-V`, and `list --help`. Justifies the dev-deps that were previously declared but unused. - Drop unused `tempfile` dev-dep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a 6th "Mentions" column that surfaces unread GitHub notifications on PRs in the configured org, so email can stop being the inbox. ## Data layer - `Notification` + `NotificationReason` in core::model, with a Vec on ColumnsData (serde default so cached BoardStates pre-M10 still load). - `GhClient::list_notifications(org)` — paginated `/notifications` (unread only), filtered to `subject.type == PullRequest` and the configured owner. PR number parsed from `subject.url`. - `resolve_comment_html_url`, `mark_thread_read`, and `mark_all_notifications_read` bindings. - `fetch_board_state` gains a 5th parallel call via `tokio::join!`. Notifications are nice-to-have: if the token is missing the `notifications` scope we print a warning and carry on with the PR board instead of failing the whole fetch. ## TUI - Mentions is column 6. Per-notification card: bold (stripped) PR title, reason badge (`@me`, `💬`, `👀`, …), repo#N, age colored by the same rotting bucket as Waiting-on-me. - Enter on a notification: spawn a task that resolves `latest_comment_url → html_url`, opens it, then PATCHes the thread read. Falls back to the PR URL if the resolve fails. - `m` key: PUT /notifications → clear the local inbox. No-op when already empty. - Success/error feedback uses the existing `status` / `last_error` banners. Help popup updated. ## CLI - `tb-pr list --column=mentions` now prints a dedicated table (reason, repo#N, age, title). Mentions stay out of the flattened PR table because their shape is different. - `tb-pr prime` gains a "## Mentions (urgent first)" section and a summary count. - `list --json` gains the `columns.notifications` array automatically. Closes productive-work-6ia
Allow-list: mention, team_mention, author, comment. Drop review_requested (already a dedicated column) and state_change / subscribed (firehose of every PR close/merge/deploy event). Took my inbox from 17 → 2 entries — the actually actionable ones.
Rolls up GitHub Actions check-runs into ✓ / ✗ / ● per PR head SHA. ## Data layer - `CheckState` enum + `Pr.check_state: Option<CheckState>` (serde skip on None so cached BoardStates without it still load). - `GhClient::check_rollup(owner, repo, sha)` → `Option<CheckState>` following the failure-wins-over-pending-wins-over-success rule. Skipped/neutral alone → None, so doc-only filtered PRs don't render a misleading ✓. - Parallel fetch via `fetch_all_check_states`, scoped to my PRs only (draft_mine + review_mine + ready_to_merge_mine). Per-PR errors degrade to None instead of tanking the whole refresh. ## Display - TUI card: ✓ / ✗ / ● colored prefix before the title; card_height accounts for the extra glyph width. - CLI `list` pretty table: new 2-wide CI column; blank when no CI configured. JSON payload already covered via Pr serialization. - `show`: "Checks: ✓ passing" line (or failing / pending). Cached alongside the detail/reviews payload. Rollup rule is unit-tested (6 cases covering failure precedence, all- skipped → None, timed_out as failure, etc.). Closes productive-work-480
TUI launch used to block ~5s on the initial fetch when the cache was
empty or stale. Now it opens immediately:
- Cache fresh (<5 min): render instantly, no fetch. Unchanged.
- Cache stale (5 min – 1 h): render instantly, spawn background fetch;
UI swaps to fresh data when FetchDone arrives. Spinner in the header
during the gap.
- Cache empty (or evicted past Long TTL): open with an empty placeholder
BoardState — empty columns, blank user — and spawn the fetch. Header
renders just "tb-pr" + spinner while the user waits.
Implementation:
- `commands/tui.rs` loads with `CacheTtl::Long`, checks `fetched_at`
against a 5-minute freshness window, and passes `needs_refresh` into
`app::run`. An `empty_state()` helper builds the placeholder.
- `app::run` gains a `needs_refresh: bool` parameter; on true, the
event loop calls `spawn_fetch` right after the initial draw so the
UI is live (keys + spinner animate) throughout the fetch.
- `render_header` skips the `{user}@productiveio · refreshed Nm ago`
line when user is empty to avoid a "refreshed 56 years ago" gag
from the epoch-zero fetched_at.
Closes productive-work-hjs
The TUI's spawn_fetch updated app state but never wrote the fresh BoardState to disk — only `tb-pr refresh` and `tb-pr list`'s fallback did. Result: every relaunch showed the stale count and re-fetched, undoing the whole point of the cache for repeat users. Fix: write to the cache inside spawn_fetch before emitting FetchDone. Errors on write are swallowed (stale cache is a UX nuisance, not a correctness problem). Covers all three fetch paths — initial on cold boot, the 5-min auto-refresh tick, and the `r` manual refresh.
Author
|
@trogulja izvibeao sam si Github UI u terminalu :) želim ubiti github email notifikacije. Baci oko, sutra ću to malo testirati pa mergam ako nemaš zamjerki |
GitHub's `subject.latest_comment_url` on /notifications is unreliable — frequently null, and when set, often points at the PR itself instead of a comment. Result: Enter on a Mentions card fell through to the fallback PR URL and the browser scrolled to the PR top instead of to the comment that triggered the notification. Ignore `latest_comment_url` entirely. On Enter, fetch both feeds for the PR in parallel — issue comments and inline review comments — pick the one with the latest `updated_at`, and open its `html_url`. That always includes the proper anchor (`#issuecomment-X` or `#discussion_rX`) when anything comments-shaped exists. Fall back to the PR URL only if both feeds are empty. `Notification` now carries `owner` instead of the dropped `latest_comment_api_url`. `Intent::OpenNotification` and `spawn_open_notification` take `(owner, repo, pr_number)` so the resolver can do its own two-call fetch without pre-baked URLs. Closes productive-work-wti
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
tb-pr— a kanban-style TUI + non-interactive CLI for tracking GitHub PRs that need attention across the Productive organization.Full design and milestones live in
docs/features/tb-pr/PLAN.md.Columns
Two modes, one binary
tb-pr) — kanban with arrow/vim navigation, auto-refresh every 5 min.tb-pr list --json,tb-pr show,tb-pr prime) — structured output for the Claude Code skill and shell scripts.Per-PR display
Status
Draft PR — this commit adds only the plan. Implementation follows in milestones tracked as beads under a single epic.
Test plan
tb-pr doctorreports gh auth statustb-pr list --jsonreturns correct data for all columnsprimeoutput is useful🤖 Generated with Claude Code