github-search-cli 9.2.1 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -203,11 +203,11 @@ npm run build # build dist/
203
203
  ```
204
204
 
205
205
  ---
206
- ### Unreleased
207
- - Fixed trending tabs (Today / This Week / This Month) returning the same repos — cache key now includes the `since` parameter so each period fetches independently
208
- - Added cyan underline indicator beneath the active trending tab for clearer visual selection
209
- - Added `ghfind --compare <repo1> <repo2> [repo3]` CLI command to compare repos side-by-side
210
- - Added `c` keybinding in TUI to toggle compare mode on selected repo
206
+ ### v0.9.3
207
+ - Fixed `c` keybinding in TUI not adding repos to comparison — missing closing brace in space-key handler broke all subsequent key handlers (`c`, `t`, `?`)
208
+ - Widened compare table columns (min 35 chars) and added Description + URL rows for richer side-by-side comparison
209
+ - Compare table now shows up to 5 topics per repo (was 3)
210
+ - README viewer now renders full markdown (headings, code blocks, lists, tables, blockquotes, rules) instead of stripping formatting
211
211
  ---
212
212
  ### v0.9.2
213
213
 
package/dist/cli.js CHANGED
@@ -1144,9 +1144,10 @@ function buildComparisonTable(repos) {
1144
1144
  rows.push({ label: "Language", values: repos.map((r) => r.language ?? "\u2014") });
1145
1145
  rows.push({ label: "Created", values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "\u2014") });
1146
1146
  rows.push({ label: "Updated", values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "\u2014") });
1147
- rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 3).join(", ") ?? "\u2014") || "\u2014") });
1148
- const colW = Math.max(15, ...names.map((n) => n.length));
1149
- const valW = Math.max(8, ...rows.flatMap((r) => r.values.map((v) => v.length)));
1147
+ rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 5).join(", ") ?? "\u2014") || "\u2014") });
1148
+ rows.push({ label: "Description", values: repos.map((r) => r.description?.slice(0, 50) ?? "\u2014") });
1149
+ rows.push({ label: "URL", values: repos.map((r) => r.url || "\u2014") });
1150
+ const colW = Math.max(35, ...names.map((n) => n.length));
1150
1151
  const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
1151
1152
  const sep = ["".padEnd(14, "\u2500"), ...names.map(() => "".padEnd(colW, "\u2500"))];
1152
1153
  const lines = [
@@ -1575,6 +1576,177 @@ function buildDeepDiveText(data) {
1575
1576
  ].join("\n");
1576
1577
  }
1577
1578
 
1579
+ // src/markdown-render.ts
1580
+ function renderMarkdown(md, opts = {}) {
1581
+ const width = opts.width ?? 80;
1582
+ const lines = md.split("\n");
1583
+ const out = [];
1584
+ let inCodeBlock = false;
1585
+ let codeLang = "";
1586
+ let codeLines = [];
1587
+ let inTable = false;
1588
+ let tableRows = [];
1589
+ function flushCode() {
1590
+ if (codeLines.length === 0) return;
1591
+ const maxLen = Math.max(...codeLines.map((l) => l.length), codeLang.length);
1592
+ const barW = Math.min(maxLen + 4, width - 2);
1593
+ out.push("\u250C" + "\u2500".repeat(barW) + "\u2510");
1594
+ if (codeLang) out.push("\u2502 " + codeLang.padEnd(barW - 1) + "\u2502");
1595
+ for (const cl of codeLines) {
1596
+ out.push("\u2502 " + cl.padEnd(barW - 1) + "\u2502");
1597
+ }
1598
+ out.push("\u2514" + "\u2500".repeat(barW) + "\u2518");
1599
+ out.push("");
1600
+ codeLines = [];
1601
+ codeLang = "";
1602
+ }
1603
+ function flushTable() {
1604
+ if (tableRows.length === 0) return;
1605
+ const dataRows = tableRows.filter(
1606
+ (r) => !r.every((c) => /^[\s\-:|]+$/.test(c))
1607
+ );
1608
+ if (dataRows.length === 0) {
1609
+ tableRows = [];
1610
+ return;
1611
+ }
1612
+ const colCount = Math.max(...dataRows.map((r) => r.length));
1613
+ const colW = [];
1614
+ for (let ci = 0; ci < colCount; ci++) {
1615
+ colW.push(
1616
+ Math.max(
1617
+ 3,
1618
+ ...dataRows.map((r) => (r[ci] ?? "").length)
1619
+ )
1620
+ );
1621
+ }
1622
+ const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
1623
+ if (totalW > width) {
1624
+ for (const row of dataRows) {
1625
+ out.push(" " + row.map((c) => c.trim()).join(" \u2502 "));
1626
+ }
1627
+ } else {
1628
+ const sep = "\u250C" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
1629
+ const mid = "\u251C" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
1630
+ const end = "\u2514" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
1631
+ out.push(sep);
1632
+ dataRows.forEach((row, i) => {
1633
+ const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
1634
+ out.push("\u2502 " + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
1635
+ if (i === 0) out.push(mid);
1636
+ });
1637
+ out.push(end);
1638
+ }
1639
+ out.push("");
1640
+ tableRows = [];
1641
+ }
1642
+ function renderInline(line) {
1643
+ line = line.replace(/\*\*(.+?)\*\*/g, "$1");
1644
+ line = line.replace(/\*([^*]+?)\*/g, "$1");
1645
+ line = line.replace(/`([^`]+)`/g, "\u27E8$1\u27E9");
1646
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
1647
+ line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "\u{1F5BC} $1");
1648
+ return line;
1649
+ }
1650
+ function wrapLine(line, maxW) {
1651
+ if (line.length <= maxW) return [line];
1652
+ const parts = [];
1653
+ let remaining = line;
1654
+ while (remaining.length > maxW) {
1655
+ let breakAt = maxW;
1656
+ const spaceIdx = remaining.lastIndexOf(" ", maxW);
1657
+ if (spaceIdx > maxW / 2) breakAt = spaceIdx;
1658
+ parts.push(remaining.slice(0, breakAt));
1659
+ remaining = remaining.slice(breakAt).replace(/^ /, "");
1660
+ }
1661
+ if (remaining) parts.push(remaining);
1662
+ return parts;
1663
+ }
1664
+ for (let i = 0; i < lines.length; i++) {
1665
+ let line = lines[i];
1666
+ if (/^```/.test(line)) {
1667
+ if (inCodeBlock) {
1668
+ flushCode();
1669
+ inCodeBlock = false;
1670
+ } else {
1671
+ inCodeBlock = true;
1672
+ codeLang = line.slice(3).trim();
1673
+ }
1674
+ continue;
1675
+ }
1676
+ if (inCodeBlock) {
1677
+ codeLines.push(line);
1678
+ continue;
1679
+ }
1680
+ if (line.includes("|")) {
1681
+ const cells = line.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
1682
+ if (cells.length >= 2) {
1683
+ inTable = true;
1684
+ tableRows.push(cells);
1685
+ continue;
1686
+ }
1687
+ } else if (inTable) {
1688
+ flushTable();
1689
+ inTable = false;
1690
+ }
1691
+ if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
1692
+ out.push("\u2500".repeat(Math.min(width, 40)));
1693
+ out.push("");
1694
+ continue;
1695
+ }
1696
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
1697
+ if (headingMatch) {
1698
+ const level = headingMatch[1].length;
1699
+ const text = renderInline(headingMatch[2]);
1700
+ const prefix = "\u2588 ".repeat(level);
1701
+ out.push("");
1702
+ out.push(prefix + text);
1703
+ out.push("");
1704
+ continue;
1705
+ }
1706
+ const bqMatch = line.match(/^>\s?(.*)/);
1707
+ if (bqMatch) {
1708
+ const inner = renderInline(bqMatch[1]);
1709
+ for (const wl of wrapLine(inner, width - 4)) {
1710
+ out.push("\u2502 " + wl);
1711
+ }
1712
+ continue;
1713
+ }
1714
+ const ulMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
1715
+ if (ulMatch) {
1716
+ const indent = ulMatch[1].length;
1717
+ const depth = Math.floor(indent / 2);
1718
+ const prefix = " ".repeat(depth) + "\u2022 ";
1719
+ const inner = renderInline(ulMatch[2]);
1720
+ for (const wl of wrapLine(inner, width - prefix.length)) {
1721
+ out.push(prefix + wl);
1722
+ }
1723
+ continue;
1724
+ }
1725
+ const olMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
1726
+ if (olMatch) {
1727
+ const indent = olMatch[1].length;
1728
+ const depth = Math.floor(indent / 2);
1729
+ const prefix = " ".repeat(depth) + "\u25E6 ";
1730
+ const inner = renderInline(olMatch[2]);
1731
+ for (const wl of wrapLine(inner, width - prefix.length)) {
1732
+ out.push(prefix + wl);
1733
+ }
1734
+ continue;
1735
+ }
1736
+ if (line.trim() === "") {
1737
+ out.push("");
1738
+ continue;
1739
+ }
1740
+ const rendered = renderInline(line);
1741
+ for (const wl of wrapLine(rendered, width)) {
1742
+ out.push(wl);
1743
+ }
1744
+ }
1745
+ if (inCodeBlock) flushCode();
1746
+ if (inTable) flushTable();
1747
+ return out.join("\n");
1748
+ }
1749
+
1578
1750
  // src/explore.ts
1579
1751
  async function fetchTopics() {
1580
1752
  const res = await fetch("https://api.github.com/search/topics?q=popular&per_page=30", {
@@ -3265,8 +3437,8 @@ async function launchBrowser(theme_override) {
3265
3437
  if (!text) {
3266
3438
  readmeText.content = ` (no README found for ${repo.fullName})`;
3267
3439
  } else {
3268
- const stripped = text.replace(/```[\s\S]*?```/g, "[code block]").replace(/#{1,6}\s/g, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").replace(/`([^`]+)`/g, "$1").split("\n").map((l) => l.trimEnd()).join("\n");
3269
- readmeText.content = stripped;
3440
+ const termW = Math.max(40, (process.stdout.columns || 80) - 4);
3441
+ readmeText.content = renderMarkdown(text, { width: termW });
3270
3442
  }
3271
3443
  } catch {
3272
3444
  readmeText.content = " Failed to load README";
@@ -4050,18 +4222,18 @@ ${pack.description ?? ""}`;
4050
4222
  showLeaderMenu();
4051
4223
  }
4052
4224
  return;
4053
- if (key.name === "t" && !searchInput.focused) {
4054
- const current = config.theme;
4055
- const themes = listThemes();
4056
- const idx = themes.indexOf(current);
4057
- const next = themes[(idx + 1) % themes.length];
4058
- const nextTheme = loadTheme(next);
4059
- Object.assign(colors, nextTheme);
4060
- config.theme = next;
4061
- saveConfig(config);
4062
- renderer.requestRender();
4063
- return;
4064
- }
4225
+ }
4226
+ if (key.name === "t" && !searchInput.focused) {
4227
+ const current = config.theme;
4228
+ const themes = listThemes();
4229
+ const idx = themes.indexOf(current);
4230
+ const next = themes[(idx + 1) % themes.length];
4231
+ const nextTheme = loadTheme(next);
4232
+ Object.assign(colors, nextTheme);
4233
+ config.theme = next;
4234
+ saveConfig(config);
4235
+ renderer.requestRender();
4236
+ return;
4065
4237
  }
4066
4238
  if (key.name === "c" && !searchInput.focused) {
4067
4239
  if (currentOverlay === "compare") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-search-cli",
3
- "version": "9.2.1",
3
+ "version": "9.3.0",
4
4
  "description": "Terminal-native GitHub search — search, trending, bookmarks, history, deep-dive, compare, export, and more. All keyboard-driven, no browser needed.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1",
package/src/compare.ts CHANGED
@@ -20,11 +20,12 @@ export function buildComparisonTable(repos: Repo[]): string {
20
20
  rows.push({ label: "Language", values: repos.map((r) => r.language ?? "—") });
21
21
  rows.push({ label: "Created", values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "—") });
22
22
  rows.push({ label: "Updated", values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "—") });
23
- rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 3).join(", ") ?? "—") || "—") });
23
+ rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 5).join(", ") ?? "—") || "—") });
24
+ rows.push({ label: "Description", values: repos.map((r) => (r.description?.slice(0, 50) ?? "—")) });
25
+ rows.push({ label: "URL", values: repos.map((r) => r.url || "—") });
24
26
 
25
27
  // Calculate the max width for each column
26
- const colW = Math.max(15, ...names.map((n) => n.length));
27
- const valW = Math.max(8, ...rows.flatMap((r) => r.values.map((v) => v.length)));
28
+ const colW = Math.max(35, ...names.map((n) => n.length));
28
29
 
29
30
  // Header row
30
31
  const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Minimal markdown-to-terminal-text renderer.
3
+ *
4
+ * Renders headings, code blocks, lists, tables, blockquotes, horizontal rules,
5
+ * bold/italic, and links into a plain-text representation suitable for a
6
+ * TextRenderable in the TUI.
7
+ */
8
+
9
+ interface RenderOptions {
10
+ /** Terminal width for wrapping and table layout. Default: 80. */
11
+ width?: number;
12
+ }
13
+
14
+ /** Render markdown text to terminal-friendly plain text. */
15
+ export function renderMarkdown(md: string, opts: RenderOptions = {}): string {
16
+ const width = opts.width ?? 80;
17
+ const lines = md.split("\n");
18
+ const out: string[] = [];
19
+ let inCodeBlock = false;
20
+ let codeLang = "";
21
+ let codeLines: string[] = [];
22
+ let inTable = false;
23
+ let tableRows: string[][] = [];
24
+
25
+ function flushCode() {
26
+ if (codeLines.length === 0) return;
27
+ const maxLen = Math.max(...codeLines.map((l) => l.length), codeLang.length);
28
+ const barW = Math.min(maxLen + 4, width - 2);
29
+ out.push("┌" + "─".repeat(barW) + "┐");
30
+ if (codeLang) out.push("│ " + codeLang.padEnd(barW - 1) + "│");
31
+ for (const cl of codeLines) {
32
+ out.push("│ " + cl.padEnd(barW - 1) + "│");
33
+ }
34
+ out.push("└" + "─".repeat(barW) + "┘");
35
+ out.push("");
36
+ codeLines = [];
37
+ codeLang = "";
38
+ }
39
+
40
+ function flushTable() {
41
+ if (tableRows.length === 0) return;
42
+ const dataRows = tableRows.filter(
43
+ (r) => !r.every((c) => /^[\s\-:|]+$/.test(c)),
44
+ );
45
+ if (dataRows.length === 0) {
46
+ tableRows = [];
47
+ return;
48
+ }
49
+ const colCount = Math.max(...dataRows.map((r) => r.length));
50
+ const colW: number[] = [];
51
+ for (let ci = 0; ci < colCount; ci++) {
52
+ colW.push(
53
+ Math.max(
54
+ 3,
55
+ ...dataRows.map((r) => (r[ci] ?? "").length),
56
+ ),
57
+ );
58
+ }
59
+ const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
60
+ if (totalW > width) {
61
+ for (const row of dataRows) {
62
+ out.push(" " + row.map((c) => c.trim()).join(" │ "));
63
+ }
64
+ } else {
65
+ const sep = "┌" + colW.map((w) => "─".repeat(w + 2)).join("┬") + "┐";
66
+ const mid = "├" + colW.map((w) => "─".repeat(w + 2)).join("┼") + "┤";
67
+ const end = "└" + colW.map((w) => "─".repeat(w + 2)).join("┴") + "┘";
68
+ out.push(sep);
69
+ dataRows.forEach((row, i) => {
70
+ const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
71
+ out.push("│ " + cells.map((c) => ` ${c} `).join("│") + "│");
72
+ if (i === 0) out.push(mid);
73
+ });
74
+ out.push(end);
75
+ }
76
+ out.push("");
77
+ tableRows = [];
78
+ }
79
+
80
+ function renderInline(line: string): string {
81
+ // Bold
82
+ line = line.replace(/\*\*(.+?)\*\*/g, "$1");
83
+ // Italic (no asterisks)
84
+ line = line.replace(/\*([^*]+?)\*/g, "$1");
85
+ // Inline code
86
+ line = line.replace(/`([^`]+)`/g, "⟨$1⟩");
87
+ // Links [text](url)
88
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
89
+ // Images
90
+ line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "🖼 $1");
91
+ return line;
92
+ }
93
+
94
+ function wrapLine(line: string, maxW: number): string[] {
95
+ if (line.length <= maxW) return [line];
96
+ const parts: string[] = [];
97
+ let remaining = line;
98
+ while (remaining.length > maxW) {
99
+ let breakAt = maxW;
100
+ const spaceIdx = remaining.lastIndexOf(" ", maxW);
101
+ if (spaceIdx > maxW / 2) breakAt = spaceIdx;
102
+ parts.push(remaining.slice(0, breakAt));
103
+ remaining = remaining.slice(breakAt).replace(/^ /, "");
104
+ }
105
+ if (remaining) parts.push(remaining);
106
+ return parts;
107
+ }
108
+
109
+ for (let i = 0; i < lines.length; i++) {
110
+ let line = lines[i];
111
+
112
+ // Code block boundaries
113
+ if (/^```/.test(line)) {
114
+ if (inCodeBlock) {
115
+ flushCode();
116
+ inCodeBlock = false;
117
+ } else {
118
+ inCodeBlock = true;
119
+ codeLang = line.slice(3).trim();
120
+ }
121
+ continue;
122
+ }
123
+ if (inCodeBlock) {
124
+ codeLines.push(line);
125
+ continue;
126
+ }
127
+
128
+ // Table row
129
+ if (line.includes("|")) {
130
+ const cells = line
131
+ .split("|")
132
+ .map((c) => c.trim())
133
+ .filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
134
+ if (cells.length >= 2) {
135
+ inTable = true;
136
+ tableRows.push(cells);
137
+ continue;
138
+ }
139
+ } else if (inTable) {
140
+ flushTable();
141
+ inTable = false;
142
+ }
143
+
144
+ // Horizontal rule
145
+ if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
146
+ out.push("─".repeat(Math.min(width, 40)));
147
+ out.push("");
148
+ continue;
149
+ }
150
+
151
+ // Headings
152
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
153
+ if (headingMatch) {
154
+ const level = headingMatch[1].length;
155
+ const text = renderInline(headingMatch[2]);
156
+ const prefix = "█ ".repeat(level);
157
+ out.push("");
158
+ out.push(prefix + text);
159
+ out.push("");
160
+ continue;
161
+ }
162
+
163
+ // Blockquote
164
+ const bqMatch = line.match(/^>\s?(.*)/);
165
+ if (bqMatch) {
166
+ const inner = renderInline(bqMatch[1]);
167
+ for (const wl of wrapLine(inner, width - 4)) {
168
+ out.push("│ " + wl);
169
+ }
170
+ continue;
171
+ }
172
+
173
+ // Unordered list
174
+ const ulMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
175
+ if (ulMatch) {
176
+ const indent = ulMatch[1].length;
177
+ const depth = Math.floor(indent / 2);
178
+ const prefix = " ".repeat(depth) + "• ";
179
+ const inner = renderInline(ulMatch[2]);
180
+ for (const wl of wrapLine(inner, width - prefix.length)) {
181
+ out.push(prefix + wl);
182
+ }
183
+ continue;
184
+ }
185
+
186
+ // Ordered list
187
+ const olMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
188
+ if (olMatch) {
189
+ const indent = olMatch[1].length;
190
+ const depth = Math.floor(indent / 2);
191
+ const prefix = " ".repeat(depth) + "◦ ";
192
+ const inner = renderInline(olMatch[2]);
193
+ for (const wl of wrapLine(inner, width - prefix.length)) {
194
+ out.push(prefix + wl);
195
+ }
196
+ continue;
197
+ }
198
+
199
+ // Empty line
200
+ if (line.trim() === "") {
201
+ out.push("");
202
+ continue;
203
+ }
204
+
205
+ // Paragraph
206
+ const rendered = renderInline(line);
207
+ for (const wl of wrapLine(rendered, width)) {
208
+ out.push(wl);
209
+ }
210
+ }
211
+
212
+ if (inCodeBlock) flushCode();
213
+ if (inTable) flushTable();
214
+
215
+ return out.join("\n");
216
+ }
package/src/tui.ts CHANGED
@@ -79,6 +79,7 @@ import {
79
79
  import { saveSession, restoreSession } from "./session";
80
80
  import { fetchDeepDive, buildDeepDiveText } from "./deepdive";
81
81
  import { buildComparisonTable } from "./compare";
82
+ import { renderMarkdown } from "./markdown-render";
82
83
  import { fetchTopics, type TopicItem } from "./explore";
83
84
  import { exportToFile, type ExportFormat } from "./output";
84
85
  import { listThemes, loadTheme } from "./themes";
@@ -1709,17 +1710,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1709
1710
  if (!text) {
1710
1711
  readmeText.content = ` (no README found for ${repo.fullName})`;
1711
1712
  } else {
1712
- // ponytail: just strip markdown formatting, show full text
1713
- const stripped = text
1714
- .replace(/```[\s\S]*?```/g, "[code block]")
1715
- .replace(/#{1,6}\s/g, "")
1716
- .replace(/\*\*(.+?)\*\*/g, "$1")
1717
- .replace(/\[(.+?)\]\(.+?\)/g, "$1")
1718
- .replace(/`([^`]+)`/g, "$1")
1719
- .split("\n")
1720
- .map((l) => l.trimEnd())
1721
- .join("\n");
1722
- readmeText.content = stripped;
1713
+ const termW = Math.max(40, (process.stdout.columns || 80) - 4);
1714
+ readmeText.content = renderMarkdown(text, { width: termW });
1723
1715
  }
1724
1716
  } catch {
1725
1717
  readmeText.content = " Failed to load README";
@@ -2646,6 +2638,8 @@ ${pack.description ?? ""}`;
2646
2638
  showLeaderMenu();
2647
2639
  }
2648
2640
  return;
2641
+ }
2642
+
2649
2643
  // 't' toggles theme and persists to config
2650
2644
  if (key.name === "t" && !searchInput.focused) {
2651
2645
  const current = config.theme;
@@ -2659,7 +2653,6 @@ ${pack.description ?? ""}`;
2659
2653
  renderer.requestRender();
2660
2654
  return;
2661
2655
  }
2662
- }
2663
2656
 
2664
2657
  // 'c' toggles compare on selected repo (when not typing in search)
2665
2658
  if (key.name === "c" && !searchInput.focused) {