Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
71ac504
Support mouse wheel scrolling in the TUI
shouze Aug 23, 2026
8f59975
Fix: 'rows' variable initialization error in mouse event handler
shouze Aug 23, 2026
dece319
Add test for dimmed checkbox rendering
shouze Aug 23, 2026
2e28500
Fix lint errors
shouze Aug 23, 2026
2cf97f4
Fix mouse click coordinate offset and release event handling
shouze Aug 23, 2026
c77c836
Fix: correct header line calculation for mouse hit-testing
shouze Aug 23, 2026
1adbae3
Implement double-click to toggle select/fold, single-click to navigate
shouze Aug 23, 2026
00dd10d
Fix mouse coordinate system and multi-click behavior
shouze Aug 23, 2026
fbce44a
Fix mouse Y-coordinate calculation for repos and extracts
shouze Aug 23, 2026
e100450
Disable selection during scroll cooldown to prevent accidental toggling
shouze Aug 23, 2026
847fa81
Fix scroll cooldown using timestamp instead of async timer
shouze Aug 23, 2026
e8ebf0f
Increase scroll cooldown to 500ms for trackpad momentum scrolling
shouze Aug 23, 2026
939ac22
Refactor scroll cooldown to pure, testable module
shouze Aug 23, 2026
bd2c7b0
Simplify scroll cooldown: block all clicks during scroll, not just se…
shouze Aug 23, 2026
b077360
refactor: extract layout constants and eliminate magic numbers
shouze Aug 23, 2026
f3d229e
refactor: eliminate remaining magic numbers in mouse handling
shouze Aug 23, 2026
8157504
docs: add mouse support documentation and architecture details
shouze Aug 23, 2026
4585fb5
docs: update homepage to highlight keyboard and mouse-driven TUI
shouze Aug 23, 2026
1f720f1
fix: address Copilot review comments from PR #174
shouze Aug 23, 2026
5c7b881
fix: properly disable mouse reporting on TUI exit
shouze Aug 23, 2026
18edea7
docs: document mouse support in README and agentic reference files
shouze Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions .github/skills/bug-fixing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,31 @@ This skill complements `.github/instructions/bug-fixing.instructions.md`.

## Symptom → module diagnostic table

| Symptom | Primary suspect | Secondary suspect |
| ----------------------------------------------------------- | ----------------------------------------------- | --------------------------------- |
| Results missing or duplicated | `src/aggregate.ts` | `src/api.ts` (pagination) |
| Wrong repository grouping | `src/group.ts` | `src/aggregate.ts` |
| `--exclude-repositories` / `--exclude-extracts` not working | `src/aggregate.ts` | `github-code-search.ts` (parsing) |
| Markdown output malformed | `src/output.ts` | — |
| JSON output missing fields or wrong shape | `src/output.ts` | `src/types.ts` (interface) |
| Syntax highlighting wrong colour / wrong language | `src/render/highlight.ts` | — |
| Row navigation skips or wraps incorrectly | `src/render/rows.ts` | `src/tui.ts` (key handler) |
| Select-all / select-none inconsistent | `src/render/selection.ts` | `src/tui.ts` |
| Filter count / stats incorrect | `src/render/filter.ts`, `src/render/summary.ts` | — |
| Path filter (`/regex/`) doesn't match expected | `src/render/filter-match.ts` | `src/tui.ts` (filter state) |
| API returns 0 results or stops paginating | `src/api.ts` | `src/api-utils.ts` |
| Rate limit hit / 429 not retried | `src/api-utils.ts` (`fetchWithRetry`) | — |
| TUI shows blank screen or wrong row | `src/tui.ts` | `src/render/rows.ts` |
| Help overlay doesn't appear / has wrong keys | `src/render.ts` (`renderHelpOverlay`) | `src/tui.ts` |
| Upgrade fails or replaces wrong binary | `src/upgrade.ts` | — |
| Completion script wrong content | `src/completions.ts` | — |
| Completion file written to wrong path | `src/completions.ts` (`getCompletionFilePath`) | env vars (`XDG_*`, `ZDOTDIR`) |
| Completion not refreshed after upgrade | `src/upgrade.ts` (`refreshCompletions`) | — |
| `--version` shows wrong info | `build.ts` (SHA injection) | — |
| CLI option ignored or parsed wrong | `github-code-search.ts` | `src/types.ts` (`OutputType`) |
| Symptom | Primary suspect | Secondary suspect |
| ----------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------- |
| Results missing or duplicated | `src/aggregate.ts` | `src/api.ts` (pagination) |
| Wrong repository grouping | `src/group.ts` | `src/aggregate.ts` |
| `--exclude-repositories` / `--exclude-extracts` not working | `src/aggregate.ts` | `github-code-search.ts` (parsing) |
| Markdown output malformed | `src/output.ts` | — |
| JSON output missing fields or wrong shape | `src/output.ts` | `src/types.ts` (interface) |
| Syntax highlighting wrong colour / wrong language | `src/render/highlight.ts` | — |
| Row navigation skips or wraps incorrectly | `src/render/rows.ts` | `src/tui.ts` (key handler) |
| Select-all / select-none inconsistent | `src/render/selection.ts` | `src/tui.ts` |
| Filter count / stats incorrect | `src/render/filter.ts`, `src/render/summary.ts` | — |
| Path filter (`/regex/`) doesn't match expected | `src/render/filter-match.ts` | `src/tui.ts` (filter state) |
| API returns 0 results or stops paginating | `src/api.ts` | `src/api-utils.ts` |
| Rate limit hit / 429 not retried | `src/api-utils.ts` (`fetchWithRetry`) | — |
| TUI shows blank screen or wrong row | `src/tui.ts` | `src/render/rows.ts` |
| Mouse clicks land on the wrong row/zone | `src/render/mouse-hit.ts` | `src/tui.ts` (click dispatch) |
| Mouse escape sequences not parsed / terminal stuck in mouse mode | `src/render/mouse.ts` | `src/tui.ts` (enable/disable mouse reporting) |
| Wheel scroll wrong step size or accidental clicks after scrolling | `src/render/layout-constants.ts` (`MOUSE_SCROLL_STEP`) | `src/scroll-cooldown.ts` |
| Help overlay doesn't appear / has wrong keys | `src/render.ts` (`renderHelpOverlay`) | `src/tui.ts` |
| Upgrade fails or replaces wrong binary | `src/upgrade.ts` | — |
| Completion script wrong content | `src/completions.ts` | — |
| Completion file written to wrong path | `src/completions.ts` (`getCompletionFilePath`) | env vars (`XDG_*`, `ZDOTDIR`) |
| Completion not refreshed after upgrade | `src/upgrade.ts` (`refreshCompletions`) | — |
| `--version` shows wrong info | `build.ts` (SHA injection) | — |
| CLI option ignored or parsed wrong | `github-code-search.ts` | `src/types.ts` (`OutputType`) |

---

Expand Down
9 changes: 8 additions & 1 deletion .github/skills/feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github-code-search.ts CLI (Commander) — parsing, program flow, output pipe
│ └── src/cache.ts Disk cache for team list (used only by api.ts)
├── src/tui.ts Interactive TTY — the only allowed stdin/stdout I/O
│ └── src/scroll-cooldown.ts Pure: scroll-cooldown state machine (debounces clicks
│ during trackpad momentum scrolling)
├── src/aggregate.ts Pure: filter + exclusion logic
├── src/group.ts Pure: team-prefix grouping
Expand All @@ -27,7 +29,12 @@ github-code-search.ts CLI (Commander) — parsing, program flow, output pipe
│ ├── filter-match.ts Pure: makeExtractMatcher, makeRepoMatcher
│ ├── rows.ts Pure: buildRows, rowTerminalLines, isCursorVisible
│ ├── summary.ts Pure: buildSummary, buildSummaryFull, buildSelectionSummary
│ └── selection.ts Pure: applySelectAll, applySelectNone
│ ├── selection.ts Pure: applySelectAll, applySelectNone
│ ├── layout-constants.ts Pure: header-line counts, mouse button codes, MOUSE_SCROLL_STEP
│ ├── mouse.ts Pure: parseMouseEvent() — SGR mouse-escape-sequence parser
│ │ (imported directly by `tui.ts`, not re-exported from `render.ts`)
│ └── mouse-hit.ts Pure: hitTestClick() — maps a click + scrollOffset to a Row/zone
│ (imported directly by `tui.ts`, not re-exported from `render.ts`)
└── src/types.ts Single source of truth for all shared interfaces
```
Expand Down
15 changes: 12 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@ src/
# returns RegExp for local client-side filtering — no I/O
render.ts # Façade re-exporting sub-modules + top-level
# renderGroups() / renderHelpOverlay()
tui.ts # Interactive keyboard-driven UI (navigation, filter mode,
# help overlay, selection)
scroll-cooldown.ts # Pure scroll-cooldown state machine (createScrollCooldownState,
# isScrollCooldownActive, updateScrollCooldown) — debounces
# clicks during trackpad momentum scrolling — no I/O
tui.ts # Interactive keyboard- and mouse-driven UI (navigation, filter
# mode, help overlay, selection, SGR mouse tracking)
output.ts # Text (markdown) and JSON output formatters
upgrade.ts # Auto-upgrade logic (fetch latest GitHub release, replace binary)
# + refreshCompletions() — overwrites existing completion file
Expand All @@ -102,6 +105,11 @@ src/
summary.ts # buildSummary, buildSummaryFull, buildSelectionSummary
selection.ts # applySelectAll, applySelectNone
team-pick.ts # renderTeamPickHeader — pick-mode candidate bar (pure, no I/O)
layout-constants.ts # Shared header-line counts, mouse button codes and
# MOUSE_SCROLL_STEP, hit-test column math — no I/O
mouse.ts # Pure SGR mouse-escape-sequence parser: parseMouseEvent() — no I/O
mouse-hit.ts # Pure hit-testing: hitTestClick() maps a click (x, y) + scrollOffset
# to a Row and the zone that was clicked — no I/O

*.test.ts # Unit tests co-located with source files
test-setup.ts # Global test setup (Bun preload)
Expand All @@ -111,7 +119,7 @@ src/

- **Pure functions first.** All business logic lives in pure, side-effect-free functions (`aggregate.ts`, `group.ts`, `output.ts`, `render/` sub-modules). This makes them straightforward to unit-test.
- **Side effects are isolated.** API calls (`api.ts`, `api-utils.ts`), TTY interaction (`tui.ts`) and CLI parsing (`github-code-search.ts`) are the only side-effectful surfaces. `api-utils.ts` hosts shared retry/pagination helpers that perform network I/O and must not be used outside `api.ts`. `cache.ts` hosts disk-cache helpers that perform filesystem I/O and must not be used outside `api.ts`.
- **`render.ts` is a façade.** It re-exports everything from `render/` and adds two top-level rendering functions. Consumers import from `render.ts`, not directly from sub-modules.
- **`render.ts` is a façade.** It re-exports everything from `render/` and adds two top-level rendering functions. Consumers import from `render.ts`, not directly from sub-modules. Exceptions: `render/team-pick.ts`, `render/mouse.ts` and `render/mouse-hit.ts` are pure modules imported **directly** by their sole consumer (`render.ts` for `team-pick.ts`, `tui.ts` for the mouse modules) and are not re-exported publicly (knip would flag unused re-exports otherwise).
- **`render/terminal.ts` is the sole Bun API call site.** All calls to `Bun.stringWidth()`, `Bun.stripANSI()`, and `Bun.sliceAnsi()` must go through the `terminal.ts` wrapper functions (`visibleWidth()`, `stripAnsi()`, `clipToWidth()`, `hasAnsi()`). This centralizes terminal handling logic and makes it easy to verify correct Unicode handling (graphemes, emoji, CJK, ZWJ sequences).
- **`types.ts` is the single source of truth** for all shared interfaces. Any new shared type must go there.
- **No classes** — the codebase uses plain TypeScript interfaces and functions throughout.
Expand Down Expand Up @@ -263,3 +271,4 @@ For minor/major releases update `docs/blog/index.md` to add a row in the version
- The `--pick-team` option is repeatable (Commander collect function); each assignment resolves one combined section label to a single team. A warning is emitted on stderr when a label is not found.
- `src/render/team-pick.ts` is a pure module (no I/O) and must be consumed only via the `src/render.ts` façade — it is imported **directly** inside `render.ts` for internal use but is not re-exported publicly (knip would flag it).
- `RepoGroup.pickedFrom` (optional field in `src/types.ts`) tracks the combined label a repo was moved from; future split-mode features will use this to offer re-assignment.
- **Mouse support** uses the terminal's SGR mouse-reporting protocol (`\x1b[?1000h\x1b[?1006h`), enabled in `runInteractive()` (`tui.ts`) next to `process.stdin.setRawMode(true)` and disabled on **every** exit path (normal exit, `q`, Ctrl+C, unhandled error) so a crashed process never leaves the user's terminal stuck in mouse-reporting mode. Escape sequences are parsed by `parseMouseEvent()` (`src/render/mouse.ts`), clicks are mapped to a row/zone by `hitTestClick()` (`src/render/mouse-hit.ts`), and wheel scroll steps use `MOUSE_SCROLL_STEP` (`src/render/layout-constants.ts`). `src/scroll-cooldown.ts` debounces clicks for a short window after a wheel scroll to avoid accidental selection during trackpad momentum scrolling. Documented for users in `docs/reference/keyboard-shortcuts.md` § Mouse support and `docs/usage/interactive-mode.md`.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Latest release](https://img.shields.io/github/v/release/fulll/github-code-search)](https://github.com/fulll/github-code-search/releases/latest)

Interactive CLI to search GitHub code across an organization — per-repository aggregation,
keyboard-driven TUI, fine-grained extract selection, markdown/JSON output.
keyboard- and mouse-driven TUI, fine-grained extract selection, markdown/JSON output.

→ **Full documentation: https://fulll.github.io/github-code-search/**

Expand Down Expand Up @@ -39,7 +39,7 @@ github-code-search query "TODO" --org my-org

- **Org-wide search** — queries all repositories in a GitHub organization in one command, with automatic pagination up to 1 000 results
- **Per-repository aggregation** — results grouped by repo, not as a flat list; fold/unfold each repo to focus on what matters
- **Keyboard-driven TUI** — navigate with arrow keys, toggle selections, filter by file path, confirm with Enter — without leaving the terminal
- **Keyboard- and mouse-driven TUI** — navigate with arrow keys or mouse clicks, toggle selections, scroll with the wheel, filter by file path, confirm with Enter — without leaving the terminal
- **Fine-grained selection** — pick exactly the repos and extracts you want; deselected items are recorded as exclusions in the replay command
- **Structured output** — Markdown document with a `# Results for` query heading, GitHub deeplinks and the exact matched token per extract; or machine-readable JSON that, when segment data is available, includes `matchedText`, `line` and `col` fields — ready to paste into docs, issues or scripts
- **Team-prefix grouping** — group results by team prefix (e.g. `platform/`, `data/`) using `--group-by-team-prefix`
Expand Down Expand Up @@ -109,16 +109,16 @@ Use `/pattern/` syntax to run a regex search. The CLI automatically derives a sa

The official [`gh` CLI](https://cli.github.com/) does support `gh search code`, but it returns a **flat paginated list** — one result per line, no grouping, no interactive selection, no structured output.

| | `gh search code` | `github-code-search` |
| ------------------------------------------ | :--------------: | :------------------: |
| Results grouped by repo | ✗ | ✓ |
| Interactive TUI (navigate, select, filter) | ✗ | ✓ |
| Fine-grained extract selection | ✗ | ✓ |
| Markdown / JSON output | ✗ | ✓ |
| Replay / CI command | ✗ | ✓ |
| Team-prefix grouping | ✗ | ✓ |
| Regex search | ✗ | ✓ |
| Syntax highlighting in terminal | ✗ | ✓ |
| Pagination (up to 1 000 results) | ✓ | ✓ |
| | `gh search code` | `github-code-search` |
| --------------------------------------------------------- | :--------------: | :------------------: |
| Results grouped by repo | ✗ | ✓ |
| Interactive TUI (navigate, select, filter, mouse support) | ✗ | ✓ |
| Fine-grained extract selection | ✗ | ✓ |
| Markdown / JSON output | ✗ | ✓ |
| Replay / CI command | ✗ | ✓ |
| Team-prefix grouping | ✗ | ✓ |
| Regex search | ✗ | ✓ |
| Syntax highlighting in terminal | ✗ | ✓ |
| Pagination (up to 1 000 results) | ✓ | ✓ |

`github-code-search` is purpose-built for **org-wide code audits and interactive triage** — not just a search wrapper.
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/ComparisonTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const ROWS: Row[] = [
},
{
feature: "Interactive TUI \u2014 navigate, select, filter",
desc: "Arrow-key navigation, path-based filter and live selection without leaving the terminal.",
desc: "Keyboard and mouse support: arrow keys or clicks for navigation, path-based filter, live selection — all without leaving the terminal.",
gh: false,
gcs: true,
docLink: "/usage/interactive-mode",
Expand Down
5 changes: 3 additions & 2 deletions docs/.vitepress/theme/HowItWorks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@
<span class="hiw-step-badge">Step 2</span>
<h3 class="hiw-step-title">Triage interactively</h3>
<p class="hiw-step-desc">
A keyboard-driven TUI opens. Navigate repos, expand extracts, filter by file path.
Select exactly what matters — deselect noise. Works without leaving the terminal.
A keyboard and mouse-driven TUI opens (mouse works in compatible terminals). Navigate
repos, expand extracts, filter by file path. Select exactly what matters — deselect
noise. Works without leaving the terminal.
</p>
</div>
</div>
Expand Down
Loading
Loading