Skip to content

feat: add interactive review TUI for feedback triage#153

Merged
igmagollo merged 3 commits intomainfrom
worktree-agent-a1720eff
Mar 11, 2026
Merged

feat: add interactive review TUI for feedback triage#153
igmagollo merged 3 commits intomainfrom
worktree-agent-a1720eff

Conversation

@igmagollo
Copy link
Copy Markdown
Owner

Adds suggestion-box review — a card-by-card TUI for going through open feedback without leaving the terminal.

Keyboard shortcuts:

  • p publish to GitHub
  • e edit title inline
  • d dismiss
  • s skip
  • q quit
  • go back

Categories get colors (friction=red, feature_request=blue, observation=yellow). Content wraps to terminal width with an overflow indicator when it doesn't fit. All built on raw stdin keypress handling and ANSI escape codes, so no new deps.

Publishing requires a github_repo on the feedback entry. If it's missing, you get a clear error instead of a silent failure.

Closes #92

*/

import { createInterface } from "readline";
import type { Feedback } from "./types.js";
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these imports are never used — checkGhAuth and createGithubIssue are injected via ReviewDeps and the direct imports from ./github.js are dead code

rl.question(
`\nNew title (empty to clear, Ctrl-C to cancel): [${current ?? ""}] `,
(answer) => {
rl.close();
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

promptTitle is never called. the e key handler re-implements its own inline readline prompt. either delete this function or use it in the handler

if (key === "p" || key === "P") {
if (!deps.checkAuth()) {
statusLine = `${RED}gh CLI not authenticated. Run 'gh auth login' first.${RESET}`;
renderCurrent();
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug: after splicing the last item, feedbacks is empty so feedbacks[0] is undefined and you fall back to current. but the card header will show "1 / 0" because total is feedbacks.length (0) and index + 1 is 1. just skip the render entirely here — you already have the status line and are about to call cleanup()

feedback: Feedback,
index: number,
total: number,
statusLine: string,
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pendingPublish is always passed as false and never read inside the function. drop the parameter

src/review.ts Outdated
process.stdout.write("\x1b[2J\x1b[H");
}

function moveTo(row: number, col: number): void {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moveTo is defined but never called. dead code, just remove it

const voteRows = await outerDb.prepare(
"SELECT session_id, evidence, estimated_tokens_saved, estimated_time_saved_minutes, created_at FROM vote_log WHERE feedback_id = ?"
).all(feedback.id) as any[];

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the DB update here throws, the GitHub issue is already created but the feedback stays open in the DB. user would end up publishing a duplicate on next attempt. worth wrapping this in a try/catch and at least logging the URL if the update fails, so it's not silently lost

Adds `suggestion-box review` — a card-by-card terminal UI for triaging
open feedback. Keyboard shortcuts:
  p — publish to GitHub (requires github_repo on entry)
  e — edit title inline
  d — dismiss
  s — skip to next
  q — quit (left arrow goes back)

The TUI renders each feedback card with ANSI colours, category badges,
vote counts, and wrapped content. Implemented in src/review.ts using raw
stdin keypress handling and terminal escape codes — no new dependencies.

Closes #92
- Remove dead imports (checkGhAuth, createGithubIssue) from review.ts
- Remove unused moveTo function and ESC constant
- Drop unused pendingPublish parameter from renderCard
- Deduplicate inline readline logic in 'e' handler by calling promptTitle
- Fix "1 / 0" header bug when publishing the last item — skip render entirely
- Wrap post-publish DB update in try/catch in both publish command and
  review TUI so a failed DB update after a successful GitHub issue creation
  logs the URL rather than silently losing it
The close event fired synchronously when rl.close() was called inside
the question callback, causing resolve(null) from the close handler to
settle the Promise before resolve(answer) ran — so typed titles were
always discarded.

Drop the close event handler and resolve entirely in the question
callback. On Ctrl-C readline calls the callback with an empty string,
which is coerced to null via `answer || null`.
@igmagollo igmagollo force-pushed the worktree-agent-a1720eff branch from 0256ed6 to d3a34f3 Compare March 11, 2026 16:00
@igmagollo igmagollo merged commit 5980ae8 into main Mar 11, 2026
1 check failed
@igmagollo igmagollo deleted the worktree-agent-a1720eff branch March 11, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Build an interactive suggestion-box review TUI with keyboard shortcuts for tr...

1 participant