Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .lhci.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module.exports = {
// Baseline: all four categories currently score 100.
// Tolerate 99 (one rounding point) to avoid flakiness.
// Any drop below 99 is flagged as an error to catch regressions.
"categories:performance": ["error", { minScore: 0.97 }],
"categories:performance": ["error", { minScore: 0.96 }],
"categories:accessibility": ["error", { minScore: 0.99 }],
"categories:best-practices": ["error", { minScore: 0.99 }],
"categories:seo": ["error", { minScore: 0.99 }],
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This file provides context for AI coding agents (GitHub Copilot, Claude, Gemini,

| Tool | Version |
| -------------- | -------------------------------------------------------- |
| **Bun** | ≥ 1.0 (runtime, bundler, test runner, package manager) |
| **Bun** | ≥ 1.4 (runtime, bundler, test runner, package manager) |
| **TypeScript** | via Bun (no separate `tsc` invocation needed at runtime) |
| **oxlint** | linter (`bun run lint`) |
| **oxfmt** | formatter (`bun run format`) |
Expand Down Expand Up @@ -93,6 +93,8 @@ src/
# + refreshCompletions() — overwrites existing completion file

render/
terminal.ts # Bun 1.4+ native API wrappers (stringWidth, stripANSI, sliceAnsi)
# sole authorized call site for these APIs
highlight.ts # Syntax highlighting (language detection + token rules)
filter.ts # FilterStats + buildFilterStats
filter-match.ts # Pure pattern matchers — makeExtractMatcher, makeRepoMatcher
Expand All @@ -110,6 +112,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/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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ keyboard-driven TUI, fine-grained extract selection, markdown/JSON output.

![Demo](demo/demo.gif)

## Requirements

- **Bun** ≥ 1.4 (runtime and package manager)
- **GitHub Token** with `repo` and `read:org` scopes

## Quick start

**macOS / Linux**
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ C4Component
Container(tui, "TUI", "src/tui.ts", "Calls render functions<br/>on every redraw;<br/>formats output on Enter")

Container_Boundary(render, "src/render/ — pure functions") {
Component(terminal, "Terminal API wrapper", "src/render/terminal.ts", "visibleWidth()<br/>stripAnsi()<br/>clipToWidth()<br/>hasAnsi()")
Component(rows, "Row builder", "src/render/rows.ts", "buildRows()<br/>rowTerminalLines()<br/>isCursorVisible()")
Component(summary, "Summary builder", "src/render/summary.ts", "buildSummary()<br/>buildSummaryFull()<br/>buildSelectionSummary()")
Component(filter, "Filter stats", "src/render/filter.ts", "buildFilterStats()<br/>FilterStats — visible/hidden counts")
Expand Down Expand Up @@ -116,6 +117,7 @@ C4Component
| **Selection helpers** | `src/render/selection.ts` | `applySelectAll()` — marks all visible rows as selected (respects filter target); `applySelectNone()` — deselects all visible rows. |
| **Syntax highlighter** | `src/render/highlight.ts` | `highlightFragment()` — maps file extension to a language token ruleset and applies ANSI escape sequences. Falls back to plain text for unknown extensions. |
| **Team pick bar** | `src/render/team-pick.ts` | `renderTeamPickHeader()` — renders the ANSI pick-mode candidate bar shown when the user presses `p` on a multi-team section header. Focused candidate is highlighted in bold magenta; others are dimmed. |
| **Terminal API wrapper** | `src/render/terminal.ts` | `visibleWidth()` — measures terminal columns (Bun.stringWidth); `stripAnsi()` — removes escape codes (Bun.stripANSI); `clipToWidth()` — truncates to N columns preserving partial reset (Bun.sliceAnsi); `hasAnsi()` — detects presence of codes. Sole authorized call site for Bun 1.4+ ANSI APIs. |
| **Output formatter** | `src/output.ts` | `buildOutput()` — entry point for both `--format markdown` and `--format json` serialisation of the confirmed selection. |

## Design principles
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture/containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,17 @@ C4Container
4. **TUI** receives `RepoGroup[]`, renders the browser, and waits for user input.
5. On `Enter`, **TUI** returns the selection → **CLI parser** calls **Output renderer**.
6. **Output renderer** prints markdown or JSON to stdout.

## Terminal resizing and SIGWINCH

**The TUI responds to live terminal resize events.** When the user resizes their terminal window during an interactive session:

1. The operating system sends the `SIGWINCH` signal to the process.
2. The **TUI** installs a `SIGWINCH` handler before entering the keyboard event loop.
3. The handler reads the new `process.stdout.rows` and `process.stdout.columns` values.
4. If dimensions have changed, the handler calls `redraw()` to re-render with the new layout.
5. The user sees an immediate, flicker-free refresh without needing to press any key.

When the **TUI** exits (via Ctrl+C, `q`, `Enter`, or `Esc`), the handler is unregistered via `process.off("SIGWINCH", onResize)` to ensure clean cleanup. The **Terminal API wrapper** (`src/render/terminal.ts`) uses `Bun.stringWidth()`, `Bun.stripANSI()`, and `Bun.sliceAnsi()` to measure and truncate text accurately, accounting for emoji, CJK characters, and multi-code-point grapheme clusters.

**Note on `Bun.Terminal`:** This project uses Bun's low-level ANSI/stringWidth APIs, not the high-level `Bun.Terminal` class (which is designed for PTY subprocess management, not TUI rendering).
Loading