Interactive TUI for browsing and managing GitHub issues across an entire organisation, written in Rust with ratatui.
Issues from every repository in the organisation are listed in one place, grouped by repo with collapsible groups. Filter, sort, inspect, comment on, close/reopen, re-assign, re-label, re-title, and move issues to another repo — or jump out to the GitHub website — without leaving the terminal.
Download a binary from the releases page, or build from source:
cargo build --release
# binary at target/release/gh-issuesgh-issues --org my-org # open issues only (default)
gh-issues --org my-org --all # include closed issues in the initial fetch
gh-issues # inside a repo clone: that repo's owner, filtered to the repo
gh-issues --refresh 60 # auto-refresh every 60 seconds (0 disables)
gh-issues --provider github # issue backend (default: github; currently the only one)--org accepts an organisation or a user account.
--provider selects the issue backend: github (default), linear, or jira.
LINEAR_API_KEY=lin_api_… gh-issues --provider linearSet provider = "linear" in the config to make it the default. Selection is explicit only — a git repo in the cwd never implies Linear.
Authentication uses a Linear personal API key: --token flag → LINEAR_API_KEY → LINEAR_TOKEN. As with GitHub, the key is never stored in the config file.
How Linear concepts map onto the UI:
| UI concept | Linear |
|---|---|
| Repo group | Team (shown by team key, e.g. ENG) |
| Issue number | Per-team issue number |
priority:* label |
Linear's native priority field, surfaced as a synthetic label so sort/colour/filter/p all work; setting it writes the native field back |
| Assignees | Single assignee (the list holds 0 or 1) |
| Close / reopen | Moves the issue to a completed / open workflow state in its team |
Move issue (m) |
Moves the issue to another team; Linear remaps its workflow state and label set to the target team itself |
--org |
Ignored — the workspace is fixed by the API key |
Not available on Linear: the linked-PR summary (P) — Linear issues have no GitHub PR links, so the key reports "not supported". Linear has no milestones or issue types in the GitHub sense; those form fields stay empty. The list view does not show a comment count for Linear issues (the count appears once you open the detail pane).
JIRA_BASE_URL=https://your-site.atlassian.net \
JIRA_EMAIL=you@example.com \
JIRA_API_TOKEN=… \
gh-issues --provider jiraJira Cloud only. Authentication is HTTP Basic with your email + an API token; all three env vars are required (--token overrides JIRA_API_TOKEN). Set provider = "jira" in the config to make it the default. As with the other backends, no secret is stored in the config file.
How Jira concepts map onto the UI:
| UI concept | Jira |
|---|---|
| Repo group | Project (shown by project key, e.g. PROJ) |
| Issue number | Numeric suffix of the issue key (PROJ-123 → 123) |
priority:* label |
Jira's native priority (Highest→urgent … Low/Lowest→low), surfaced as a synthetic label so sort/colour/filter/p all work; setting it writes the native field back |
| Body | Description is Atlassian Document Format (ADF); it's flattened to plain text for display and wrapped back to ADF when you create an issue or comment |
| Assignees | Single assignee (the list holds 0 or 1) |
| Close / reopen | Runs a workflow transition to a Done / not-Done status (needs a matching transition in the project's workflow) |
| Issue type | Required when creating an issue — the new-issue form's type picker is populated from the project |
--org |
Ignored — the site is fixed by the credentials |
Not available on Jira: the linked-PR summary (P, no GitHub PR links), milestones, and moving issues to another project (m) — Jira Cloud has no per-issue move, only a Beta bulk-move endpoint requiring an explicit issue-type/status mapping, so the key reports "not supported". Rich ADF formatting (tables, panels, media) is dropped when flattening to text.
When run from a directory inside a git repository whose origin remote points at github.com, gh-issues browses that remote's owner with the repo filter pre-set to the repository — so you see just that repo's issues immediately. Clear the filter (F → c, or empty the repo field) to see the whole organisation again.
Resolution order for what to browse:
--orgflag (the detected repo filter is applied only when the remote's owner matches)- the cwd's
originremote owner + repo filter default_orgfrom the config file
A GitHub token is resolved in this order:
--token <TOKEN>flagGITHUB_TOKENenvironment variableGH_TOKENenvironment variablegh auth token(the GitHub CLI's stored login)
On a machine with gh logged in, no configuration is needed. The token needs repo scope (read for browsing; write operations use the same token).
Optional TOML config at ~/.config/gh-issues/config.toml:
default_org = "my-org"
provider = "github" # issue backend (default: "github"; currently the only one)
default_collapsed = false # start with repo groups expanded (default: true)
refresh_interval = 300 # seconds between auto-refreshes, 0 disables (default: 300)
hide_empty_repos = true # hide repo groups with no visible issues (default: true)
copy_format = "{owner}/{repo}#{number}" # `y` clipboard format (default shown)
default_harness = "claude" # harness `A` launches (unset: `A` asks)
workspace_roots = ["~/pgmac", "~/projects"] # searched for a repo's clone
[harnesses.claude] # built in; shown here to override it
command = ["claude", "attach", "{bg_id}"]
bg_dispatch = ["claude", "--bg", "--name", "{ref}", "/pgmac-workflows:pickup-ticket {ref}"]With default_org set, plain gh-issues works without --org. By default the issue list starts with every repo group folded; groups can still be expanded as normal (Space / ]), and repos you expand stay expanded across reloads. When only one repo group is visible (for example when started inside a repo clone), that group starts expanded. Set default_collapsed = false to start with everything expanded. Tokens are never stored in the config file.
copy_format controls what y puts on the clipboard, with {owner}, {repo}, and {number} placeholders substituted from the selected issue. The default ({owner}/{repo}#{number}) is the short form GitHub tools and Claude Code understand.
default_harness, workspace_roots and [harnesses.*] configure sending an issue to a coding agent — see Coding harnesses below.
A on an issue starts a coding agent in that repo's clone, primed with the ticket, in an embedded terminal pane. Full detail: docs/harness-sessions.md.
default_harness = "claude"
workspace_roots = ["~/pgmac", "~/projects"]
[harnesses.claude]
command = ["claude", "attach", "{bg_id}"]
bg_dispatch = ["claude", "--bg", "--name", "{ref}", "/pgmac-workflows:pickup-ticket {ref}"]
[harnesses.opencode]
command = ["opencode", "run", "work on {url}"]
[harnesses.pi]
command = ["pi", "--name", "{ref}", "work on {ref}: {url}"]
[harnesses.copilot]
command = ["copilot", "--allow-tool", "write, edit, shell(git status:*), shell(git diff:*), shell(git add:*), shell(git commit:*), shell(git branch:*), shell(git checkout:*), shell(git switch:*), shell(git rebase:*), shell(git push origin:*), shell(git log:*), shell(git stash:*)", "--deny-tool", "write(.git/**), edit(.git/**), shell(git config:*), shell(git remote:*), shell(git clone:*)", "--no-ask-user", "-p", "work on {ref}: {url}"]
[harnesses.codex]
command = ["codex", "work on {ref}: {url}"]command is an argv array, never a shell string — placeholders expand into individual arguments, so issue text containing quotes or $(…) is inert. Available placeholders are {owner}, {repo}, {number}, {ref} (owner/repo#number, always canonical), {url}, and {bg_id} (only set when bg_dispatch is — see below).
claude, opencode, pi, copilot and codex ship built in; defining a harness of the same name overrides it, and defining a new one leaves the others in place. A harness may set its own workspace_roots.
bg_dispatch is optional, and only meaningful for a harness with a background supervisor like Claude Code's to dispatch to: it runs first (off the PTY) to start a background session, then command attaches a PTY viewer onto the {bg_id} it resolved to — the split that lets a session survive gh-issues-tui quitting. None of the other four have that concept, so command runs directly on the PTY for them, same as claude did before background dispatch. opencode and copilot run one prompt non-interactively and exit (-p/run); pi and codex, like claude, start interactive and stay attached. copilot -p can't pause for a permission prompt or a clarifying question, and an issue's title/body is attacker-controlled in a public repo. --allow-tool patterns match the full command line, not just the tool name, so shell(git:*) alone is not actually a safe boundary — git config alias.x '!…', ext::/--upload-pack transports and repo hooks are all just "git." The grant instead names specific subcommands, scopes push to the origin remote so an injected instruction can't exfiltrate to an arbitrary URL, and --deny-tool (which beats any broader allow) blocks config/remote/clone plus writing anywhere under .git/ — closing the "plant a hook via write, then any git subcommand triggers it" path that no subcommand allowlist alone reaches. --no-ask-user applies for the same reason it needs a tool grant at all. codex needs neither since it stays attachable, so a human can catch a bad action before it runs. codex, pi and copilot's argv were each verified against docs rather than run locally (codex/copilot aren't installed on the machine this was written on; pi was run live, just with no model configured).
Every one of these agents (claude, codex and copilot confirmed; pi only via a separate opt-in extension, pi-subagents) can spawn its own subagents for a task — that's a property of the harness's own runtime, not something this tool configures.
A harness runs in the current directory's repo when that is the issue's repo; otherwise in the first <root>/<repo> that exists across workspace_roots. If none does, nothing launches and the message names every path tried.
The issue list refetches from GitHub every refresh_interval seconds (default 5 minutes) so new and updated issues appear without pressing r. The --refresh <SECS> flag overrides the config value; 0 disables it. A background refresh keeps your selection on the same issue and skips a beat while a fetch is already running, the API is rate-limited, or you are mid-edit (typing in an input, a menu, or a confirmation).
y copies the selected issue's short reference via an OSC 52 terminal escape sequence rather than talking to a system clipboard library, so it works the same locally and over SSH (tmux passthrough is handled automatically). It needs a terminal emulator with OSC 52 support — true of most modern terminals (iTerm2, Alacritty, Kitty, WezTerm, Windows Terminal) — and, if you're not local, an SSH client/terminal combination that lets OSC 52 through.
Define any number of [color_profiles.<name>] tables and pick one with color_profile:
color_profile = "gruvbox"
[color_profiles.gruvbox]
accent = "#83a598" # repo headers, prompts, help keys
dim = "#928374" # issue numbers, dates, metadata
selected_bg = "#3c3836" # selection bar (list + pickers + calendar)
open = "#b8bb26" # open-issue dot and label
closed = "#d3869b" # closed-issue dot and label
assignee = "#fabd2f" # assignee badges / detail meta line
warning = "#fe8019" # rate-limit warnings, y/n prompts
error = "#fb4934" # errors
label_fallback = "blue" # labels with unparsable GitHub colours
code_bg = "#1d2021" # fenced/inline code background
code_fg = "#ebdbb2" # fenced/inline code text, and unclassified tokens
code_keyword = "#fb4934" # keywords, and json/yaml/toml keys
code_string = "#b8bb26" # string literals
code_comment = "#928374" # comments
code_number = "#d3869b" # numeric literals
[color_profiles.mono]
accent = "white"
selected_bg = "8"Every entry is optional — unset entries keep the built-in colour. Values accept ratatui colour names ("cyan", "lightgreen", "dark gray"), hex ("#2d5aa0"), or ANSI indexes ("8"). Naming a profile that isn't defined is a startup error listing the profiles that are.
Fenced code blocks are token-highlighted for sh, yaml, json, toml, rust, python, go and js/ts (with the usual aliases — bash, yml, rs, py, golang, typescript, …). Any other info string, or none, renders the block flat in code_fg exactly as before.
There's no on/off switch: point the four code_* token colours at code_fg to turn highlighting off.
[color_profiles.flat]
code_fg = "#dcdcdc"
code_keyword = "#dcdcdc"
code_string = "#dcdcdc"
code_comment = "#dcdcdc"
code_number = "#dcdcdc"| Key | Action |
|---|---|
j/k, ↑/↓ |
move selection in the list; in the detail pane, scroll the selected region (issue body or the selected comment) |
PgUp/PgDn, g/G |
page / jump to top / bottom |
Space |
collapse/expand the selected repo group |
← / → |
on a repo header: collapse / expand the group. On an issue: → moves into the detail pane (opening it if closed), ← backs out to the list |
[ / ] |
collapse all / expand all groups |
Enter |
open the issue in a right-hand detail pane (loads the comment thread) |
Tab / Shift+Tab |
from the list: switch into the detail pane. In the detail pane: move to the next / previous comment |
← |
in the detail pane: return focus to the list |
Esc / q |
close the detail pane (from either pane) |
o / O |
open issue / repo in the browser |
y |
copy the selected issue's short reference (owner/repo#number) to the clipboard, via OSC 52 |
/ |
free-text search (title, body, #number) |
# |
jump the selection to a loaded issue by number (moves the selector bar; does not filter — reveals a hidden match by clearing filters and relaxing the state filter, searches the current repo group first) |
f |
cycle state filter: open → closed → all |
F |
filter editor (repo, assignee, author, priority, status, created/updated/closed date bounds) |
s / S |
cycle sort key / toggle direction |
w |
switch org/owner (free-text; resets filters and view state) |
c |
add a comment: opens the detail pane (if closed) and its inline editor section (Ctrl+S submits, Esc discards) |
e |
edit the selected detail region inline — the issue description (body) or the selected comment — in the same editor section |
x |
close or reopen the issue (confirmation popup: ←/→/Tab moves focus, Enter picks, or y/n/Esc shortcuts) |
a |
edit assignees (comma-separated logins) |
l |
edit labels (picker of the repo's labels, current labels pre-checked) |
t |
edit the title |
p |
set the priority (picker of the repo's priority:* labels, — clears) |
P |
summarise a linked PR (detail pane only; picker if several links are found) |
m |
move the issue to another repo (picker of the org's other repos, then a confirmation popup) |
A |
send the issue to a coding harness — starts it in that repo's clone in an embedded terminal pane; attaches if the issue already has a session |
Z |
switch between harness sessions |
n |
create a new issue in the selected repo (opens the form) |
r |
reload all data |
? |
help |
q |
quit |
Sort keys: updated, created, closed, state, assignee, author, priority.
Every text input (search, filters, assignees, title, org, and the comment/description editor) opens as a small popup box and supports readline-style editing; the new-issue form's title and description fields use the same editing keys but inline, in the form itself. The cursor is a block sitting on a character:
| Key | Action |
|---|---|
←/→, Home/End |
move by char / to line start / to line end |
Ctrl+← / Ctrl+→ |
move left / right by word (whitespace-delimited) |
Ctrl+A / Ctrl+E |
line start / line end |
Ctrl+W |
delete the word before the cursor |
Ctrl+U / Ctrl+K |
delete to line start / to line end |
Ctrl+D / Delete |
delete the char under the cursor |
Single-line popups (search, filters, assignees, title, org) and the new-issue form's inline title field scroll horizontally to keep the cursor visible when the value is wider than the box; Enter submits (on the new-issue title, Enter moves focus to the next field instead), Esc cancels.
In the multi-line comment and description editors — including the new-issue form's inline description — text word-wraps at the editor's width, ↑/↓ move by visual (wrapped) row, Enter inserts a newline, and Delete at the end of a line joins the next line on.
c adds a comment on the selected issue: it opens the detail pane if it's closed (loading the comment thread) and adds an inline section at the bottom, about a third of the pane's height, with a multi-line editor and a [ Save ] [ Cancel ] button row. Tab/Shift+Tab cycle focus between the editor and the two buttons; Enter/Space activates whichever is focused. Ctrl+S saves and Esc cancels from anywhere in the section, regardless of focus. Saving posts the comment and refreshes the thread; cancelling discards the draft and returns to the plain detail pane.
The detail pane splits into two independently scrolling regions. The top region pins the issue metadata and description so it never scrolls away with the comments. The bottom region holds the comment thread, rendered as cards — each comment is bounded by a header rule showing its author and timestamp and a matching bottom rule, so where one comment ends and the next begins is obvious. Each region shows a scrollbar on its right edge when its content is taller than the region, marking your position through the text.
Tab / Shift+Tab move the selection through the body and each comment (wrapping around); ← hands focus back to the list. j/k (or ↑/↓) scroll the selected region only — the description when the body is selected, or, once you select a comment, that comment's own text (its header scrolls up out of the way as you read, and the scrollbar tracks your position within that one comment). PageUp/PageDown step by a screenful. e opens the same inline editor section on the selected region, prefilled with its current text — the issue description when the body is selected, or the comment otherwise. Editing uses the identical editor controls as adding a comment (Ctrl+S saves, Esc discards). Saving an edited description or comment refreshes the pane; an empty save discards a comment edit but is accepted for the description (clearing it is valid). All three backends (GitHub, Linear, Jira) support editing.
The body and each comment render as lightweight markdown: # headings, **bold**/*italic*, `inline code`, fenced ``` code blocks, > blockquotes, -/*/+ and numbered lists, --- rules, and [text](url) links. Rendering is line-for-line — one screen line per source line — with wrapping measured accurately so scroll positions and scrollbars stay correct; tables, nested-list re-indent, and syntax highlighting inside code fences aren't supported.
URLs in the description and comments — both bare http(s):// URLs and the labels of [text](url) links — are clickable. They're emitted as terminal OSC 8 hyperlinks, so Ctrl+Click (Cmd+Click on macOS) opens them in your default browser. This uses no mouse capture, so ordinary mouse text-selection still works; terminals that don't support OSC 8 simply show the URLs as plain underlined text.
n opens a New-Issue form for the selected repo (from its header or any of its issue rows) as a single inline form — one box, no per-field popups except for the pickers below. Tab/Shift+Tab move between fields and the [ Create ]/[ Cancel ] buttons at the bottom, wrapping at both ends. Title and description edit directly in the form (description is a small multi-line box: Enter inserts a newline; Tab leaves it). Assignees and labels (multi-select pickers — Space toggles, Enter accepts) and type, priority, project, milestone (single-select pickers, — clears) still open a picker popup on Enter — the one modal exception, since these option lists benefit from the pickers' type-ahead filter. Picker options load per repo when the form opens: assignable users, repo labels, issue types (where the org has them), the repo's Projects (V2), and open milestones. Priority follows the priority:<value> label convention — the chosen label is added to the issue's labels. Enter/Space on [ Create ] submits; the status line reports created #N and the list refetches. Esc anywhere, or Enter/Space on [ Cancel ], discards the form.
To create the first issue in a repo that shows no issues, flip the hide empty repos filter to no (F → last row → Enter) — the repo's (0) header appears and n works on it.
m moves the selected issue to a different repo in the same org: it opens a picker of every other loaded repo (type-ahead filters it), then a confirmation popup naming the destination and the move's side effects — the issue gets a new number, and everyone mentioned in it is notified. Esc/n cancels either step without mutating anything.
GitHub only allows same-owner transfers — the picker's targets are exactly the repos already loaded for this org, so there's nothing to fetch. Labels missing from the target repo are recreated there (priority:*/status:* included) so the app's sort/colour/filter keeps working on the moved issue; recreated labels get GitHub's default colour rather than the source's. On Linear, m moves the issue to another team instead — see the Linear backend table for what that remaps. Not available on Jira — see the Jira backend section.
The selection does not follow the moved issue after the refetch — GitHub does not document whether a transferred issue keeps its id, so the cursor falls back to the same clamped-index behaviour as any issue that vanishes from view. The status line names the destination instead (moved to <repo>).
With the detail pane open, P scans the issue's body and its loaded comment thread for references in three forms: github.com/<owner>/<repo>/pull/<N> links, <owner>/<repo>#<N> shorthand, and bare #<N> (resolved against the repo the issue belongs to). One reference opens its summary directly; several open a picker to choose. The summary popup shows the PR's title/description, state (open/closed/merged/draft), base←head branches and diffstat, review status (GitHub's overall decision plus per-reviewer approve/changes-requested/comment counts), issue-comment and review-thread counts, the head commit's checks, the PR's own Actions runs, and recent Actions runs on the repo's default branch (the "merge to main" runs). j/k scroll it; Esc/q closes back to the detail pane.
The shorthands cannot say whether a number is a PR or an issue — GitHub draws both from one per-repo sequence — so a candidate is only resolved when you open it. If it turns out to be an issue, the popup says so and o/Enter jumps the selector to it, falling back to opening it in a browser when it isn't in the loaded data. References inside code fences and inline code spans are ignored. See docs/pr-url-matching.md.
A starts the configured coding agent for the selected issue, in that repo's clone, on a real PTY drawn full-frame inside the TUI. Several sessions run at once, one per issue.
Inside a session every key goes to the agent — arrows, Esc, Ctrl+C and Shift+Tab included, since agent CLIs bind them. F12 is the single key the TUI keeps back, as a tmux-style prefix:
| Chord | Effect |
|---|---|
F12 d |
detach — back to the list, the agent keeps running |
F12 s |
switch session |
F12 k |
kill (confirmed while the agent is alive) |
F12 n |
start another harness for the current issue |
F12 ? |
help |
F12 F12 |
send a literal F12 to the agent |
F12 ? shows that table in-app. Help opened from inside a session lists these chords; ? from the issue list keeps the list's own keys. The two tables are disjoint on purpose — a session forwards n and / to the agent, so offering them as app keys there would be actively wrong.
Detaching is not killing: the agent keeps working and Z returns to it. When an agent exits, its session stays with the final screen frozen so the closing summary is still readable — k/j scroll, G jumps to the end, q leaves it in place, x dismisses it. The bottom status line shows n running, m exited (Z) whenever any session exists, and q with agents still running asks first, naming them.
An agent's own TUI fills the screen, so a session carries two rows of chrome that belong to the app rather than the agent:
█ gh-issues-tui █ pgmac-net/gh-issues-tui#132 · Send to Claude/harness · claude · ● running
(the agent's own full-screen UI)
F12 d detach · s switch · k kill · n new · F12 F12 literal · ? help
The top row is the identity row: a reverse-video badge, the issue, its title, which harness, and whether the child is alive. It costs the agent one row of its PTY, so on a very short terminal it is shed before the key row is. Under width pressure the title elides first, then the owner drops off the reference (…/gh-issues-tui#132); the badge and the running state never elide.
The window/tab title is set to gh-issues-tui · <issue> while attached and back to gh-issues-tui on detach, so provenance survives the TUI not being the focused pane. An agent that emits its own title escape will win — the identity row is the signal that cannot be overwritten.
Every harness child gets these on top of the inherited environment:
| Variable | Example |
|---|---|
GH_ISSUES_TUI |
0.12.0 — presence marks the launcher, the value is its version |
GH_ISSUES_TUI_HARNESS |
claude |
GH_ISSUES_TUI_OWNER |
pgmac-net |
GH_ISSUES_TUI_REPO |
gh-issues-tui |
GH_ISSUES_TUI_NUMBER |
132 |
GH_ISSUES_TUI_ISSUE |
pgmac-net/gh-issues-tui#132 |
GH_ISSUES_TUI_URL |
https://github.com/pgmac-net/gh-issues-tui/issues/132 |
GH_ISSUES_TUI_TITLE |
Send to Claude/harness |
The agent itself already learns its ticket from the prompt in its command; these are for everything that cannot read that — hooks, statuslines and scripts, including ones the agent spawns. Scraping the parent's argv instead does not generalise: every harness formats its command differently, and the opencode default carries a URL with no #N in it at all.
To render provenance inside Claude Code's own UI, in ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "[ -n \"$GH_ISSUES_TUI\" ] && echo \"⬡ gh-issues-tui · $GH_ISSUES_TUI_ISSUE\" || true"
}
}Full detail: docs/harness-sessions.md.
- Issues are fetched per-repository over the GraphQL API with cursor pagination, so organisations with more than 1000 issues are not truncated (the search API cap does not apply).
- Only open issues are fetched at startup unless
--allis given; switching the state filter to closed/all triggers a one-time refetch that includes closed issues. - Assignee edits replace the full set with what you type; label edits replace the full set with what's checked in the picker; comment/close/reopen/edit operations refresh the data on completion. With the detail pane open, the comment thread refreshes too, so a just-added comment appears immediately without moving the selection.
pfetches the repo's labels and offers thepriority:*ones (ordered low → urgent, current priority pre-highlighted). Picking replaces any existing priority label and keeps the rest;—removes the priority. Repos with nopriority:*labels report that in the status line instead of opening the picker.lfetches the repo's labels and offers all of them as a multi-select (Space toggles, Enter accepts), with the issue's current labels pre-checked. Accepting replaces the issue's full label set with the checked ones. Repos with no labels report that in the status line instead of opening the picker.- In the filter editor, repo/assignee/author open a single-select picker built from the loaded data (first entry clears the filter); priority/status open a multi-select picker (Space toggles, Enter accepts, deselecting everything clears the filter — priority options ordered low → urgent); date fields open a calendar; text remains free-input.
- Repo groups with zero visible issues are hidden by default. The
hide empty reposrow in the filter editor toggles this in place (Enter flips yes/no): set tono, every repo appears — including repos with no issues at all and groups emptied by the current filters — as a(0)header. Clearing filters (F→c) and switching org reset the toggle to thehide_empty_reposconfig default. Archived repos and repos with issues disabled are never shown. - Every option picker (filter editor and new-issue form) supports type-ahead: just start typing to narrow the list (case-insensitive substring, shown as a
/ <text>row).Backspaceedits the filter,Ctrl+Uclears it,↑/↓navigate the matches,Enterpicks,Esccloses. Because typing filters,j/k/qdon't navigate/close inside pickers. - Priority and status filters match
priority:<value>/status:<value>labels (bare value or full label name, case-insensitive). Several values can be selected at once — an issue matches when it carries any of them. - Issues carrying a
priority:<value>label have their title drawn in that label's GitHub colour (in both the list and the detail pane); issues without one keep the default colour. The first priority label wins if an issue somehow has several. - The
prioritysort key ranks urgent > high > medium > low > no priority (descending shows urgent first). Priority values other than those four sort with the no-priority group, and equal priorities order by most recently updated regardless of sort direction. - The repo filter is exact when its text exactly names a loaded repo (case-insensitive), so
apiwon't also matchapi-gateway; otherwise it matches as a substring. - The detail pane splits the window 40/60 beside the list and live-follows the list selection: moving with
j/kshows the newly selected issue (comments refetch per issue; landing on a repo header shows "no issue selected"). The focused pane has an accent-coloured border. - The info bar shows the API rate-limit budget (
API remaining/limit); after a mutation the refetch is skipped if the budget is exhausted, and rate-limit errors stay visible until a fetch succeeds.
cargo test # unit tests
cargo clippy --all-targets -- -D warnings
cargo fmt --checkSee docs/ for architecture and design notes.
MIT