github-search-cli 9.4.2 → 9.4.4

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/README.md +8 -3
  3. package/bin/ghfind.js +98 -89
  4. package/completions/ghfind.bash +1 -1
  5. package/completions/ghfind.fish +3 -1
  6. package/completions/ghfind.zsh +6 -0
  7. package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
  8. package/dist/cli.js +761 -342
  9. package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
  10. package/package.json +6 -3
  11. package/release-notes/9.3.0.md +32 -29
  12. package/scripts/build.ts +34 -19
  13. package/scripts/generate-svg-demo.js +218 -168
  14. package/scripts/postinstall.js +176 -176
  15. package/src/bookmarks.ts +69 -69
  16. package/src/cli.ts +17 -1
  17. package/src/compare.ts +69 -47
  18. package/src/config.ts +70 -65
  19. package/src/deepdive.ts +149 -118
  20. package/src/doctor.ts +287 -287
  21. package/src/errors.ts +62 -59
  22. package/src/explore.ts +34 -29
  23. package/src/export.ts +73 -63
  24. package/src/help.ts +157 -157
  25. package/src/history.ts +69 -55
  26. package/src/init.ts +60 -55
  27. package/src/landing.ts +106 -104
  28. package/src/login.ts +186 -0
  29. package/src/markdown-render.ts +1 -6
  30. package/src/notifications.ts +75 -59
  31. package/src/open-url.ts +16 -16
  32. package/src/org.ts +43 -15
  33. package/src/output.ts +153 -137
  34. package/src/package-browser.ts +317 -305
  35. package/src/package.ts +149 -137
  36. package/src/pipe.ts +60 -54
  37. package/src/releases.ts +182 -144
  38. package/src/saved-searches.ts +72 -50
  39. package/src/search-state.ts +283 -229
  40. package/src/search.ts +678 -653
  41. package/src/session.ts +26 -24
  42. package/src/share.ts +61 -54
  43. package/src/status.ts +68 -61
  44. package/src/storage.ts +136 -106
  45. package/src/themes.ts +259 -116
  46. package/src/tips.ts +42 -42
  47. package/src/toast.ts +70 -69
  48. package/src/trending-parser.ts +104 -0
  49. package/src/trending.ts +481 -462
  50. package/src/tui.ts +149 -153
  51. package/src/types.ts +160 -158
  52. package/src/watch.ts +95 -74
package/src/tui.ts CHANGED
@@ -31,7 +31,14 @@ import {
31
31
  SelectRenderable,
32
32
  ScrollBoxRenderable,
33
33
  } from "@opentui/core";
34
- import type { Repo, SearchOptions, SortStrategy } from "./types";
34
+ import type { CliRenderer } from "@opentui/core";
35
+ import type {
36
+ Repo,
37
+ SearchOptions,
38
+ SortStrategy,
39
+ Package,
40
+ SessionState,
41
+ } from "./types";
35
42
  import { fetchOrgProfile, type OrgProfile } from "./org";
36
43
  import {
37
44
  parseQuery,
@@ -79,12 +86,16 @@ import { buildComparisonTable } from "./compare";
79
86
  import { renderMarkdown } from "./markdown-render";
80
87
  import { fetchTopics, type TopicItem } from "./explore";
81
88
  import { exportToFile, type ExportFormat } from "./output";
82
- import { listThemes, loadTheme } from "./themes";
89
+ import {
90
+ listThemes,
91
+ loadTheme,
92
+ detectTerminalBackground,
93
+ deriveSurfaceLayers,
94
+ } from "./themes";
83
95
  import {
84
96
  createPackageSearch,
85
97
  sortPackages,
86
98
  PACKAGE_SORT_MODES,
87
- type Package,
88
99
  type PackageSortMode,
89
100
  } from "./package";
90
101
  import { StatusManager } from "./status";
@@ -111,7 +122,7 @@ import {
111
122
  recordPreUpdateState,
112
123
  } from "./update-check";
113
124
  import { debugLog } from "./storage";
114
- const colors = {
125
+ const colors: Record<string, string> = {
115
126
  bg: "#3D3B3B",
116
127
  surface: "#4a4848",
117
128
  surfaceAlt: "#525050",
@@ -153,7 +164,11 @@ const SORT_MODES: { key: SortStrategy; label: string }[] = [
153
164
 
154
165
  // ─── Main ─────────────────────────────────────────────────────────────
155
166
  export async function launchBrowser(theme_override?: string): Promise<void> {
156
- let renderer;
167
+ // Ask the terminal for its real background color (OSC 11) BEFORE the
168
+ // renderer takes over stdin — after that, reading the reply gets messy.
169
+ // Used below so the app backdrop matches the user's terminal theme.
170
+ const terminalBg = await detectTerminalBackground();
171
+ let renderer: CliRenderer;
157
172
  try {
158
173
  const isTTY = process.stdout.isTTY && process.stdin.isTTY;
159
174
  if (isTTY) {
@@ -188,13 +203,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
188
203
 
189
204
  const root = renderer.root;
190
205
  root.flexDirection = "column";
191
- root.border = true;
192
- root.borderTop = true;
193
- root.borderBottom = true;
194
- root.borderLeft = true;
195
- root.borderRight = true;
196
- root.borderColor = colors.borderAccent;
197
- root.backgroundColor = colors.bg;
198
206
 
199
207
  // ── Config ──
200
208
  const config = loadConfig();
@@ -203,6 +211,18 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
203
211
  // Apply theme
204
212
  const theme = loadTheme(theme_override || config.theme);
205
213
  Object.assign(colors, theme);
214
+ // Prefer the terminal's own background so the app blends into the terminal
215
+ // instead of painting a rectangle of theme-bg over it. Falls back to the
216
+ // theme bg when the terminal can't be queried (conhost, piped, etc.).
217
+ if (terminalBg) {
218
+ colors.bg = terminalBg;
219
+ // Blend card/surface/border layers toward that background so panels are
220
+ // subtle tints of the terminal color rather than foreign theme chips.
221
+ Object.assign(colors, deriveSurfaceLayers(terminalBg));
222
+ }
223
+ // Must come after the theme is applied — this paints the whole terminal
224
+ // backdrop, and doing it earlier bakes in the hardcoded default palette.
225
+ renderer.setBackgroundColor(colors.bg);
206
226
 
207
227
  // ── Session restore ──
208
228
  const session = restoreSession();
@@ -214,7 +234,9 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
214
234
  let currentQueryInput = session?.query ?? "";
215
235
  let isLoading = false;
216
236
  let currentMode: "landing" | "search" | "trending" | "packages" =
217
- session?.mode ?? "search";
237
+ session?.mode === "trending" || session?.mode === "packages"
238
+ ? session.mode
239
+ : "search";
218
240
  let packages: Package[] = [];
219
241
  let currentPackageSort: PackageSortMode = "best-match";
220
242
  let packageResultsRaw: Package[] = [];
@@ -248,11 +270,9 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
248
270
  const header = new TextRenderable(renderer, {
249
271
  content:
250
272
  " ghfind — GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
251
- backgroundColor: colors.bg,
252
- color: colors.muted,
273
+ bg: colors.bg,
274
+ fg: colors.muted,
253
275
  height: 1,
254
- borderBottom: true,
255
- borderBottomColor: colors.border,
256
276
  });
257
277
 
258
278
  // ── Trending tab bar (hidden in search mode) ────────────────────────
@@ -275,8 +295,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
275
295
  const label = ` ${name} `;
276
296
  const tt = new TextRenderable(renderer, {
277
297
  content: label,
278
- color: isActive ? colors.bg : colors.muted,
279
- backgroundColor: isActive ? colors.blue : colors.bg,
298
+ fg: isActive ? colors.bg : colors.muted,
299
+ bg: isActive ? colors.blue : colors.bg,
280
300
  height: 1,
281
301
  });
282
302
  trendingTabTexts.push(tt);
@@ -284,8 +304,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
284
304
  if (i < TAB_NAMES.length - 1) {
285
305
  const sp = new TextRenderable(renderer, {
286
306
  content: " ",
287
- color: colors.muted,
288
- backgroundColor: colors.bg,
307
+ fg: colors.muted,
308
+ bg: colors.bg,
289
309
  height: 1,
290
310
  });
291
311
  trendingTabTexts.push(sp);
@@ -295,23 +315,15 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
295
315
  }
296
316
  root.add(header);
297
317
  root.add(trendingTabBox);
298
- // ── Gap between header/tabs and search ────────────────────────────────
299
- const gapBox = new BoxRenderable(renderer, {
300
- height: 1,
301
- visible: true,
302
- });
303
- root.add(gapBox);
304
318
 
305
319
  // ── Search input row ────────────────────────────────────────────────
306
320
  const searchBox = new BoxRenderable(renderer, {
307
321
  visible: true,
308
- bordered: true,
309
- borderColor: colors.border,
310
- title: "",
311
- titleColor: colors.blue,
312
322
  width: "100%",
313
323
  flexDirection: "row",
314
324
  marginTop: 1,
325
+ paddingY: 1,
326
+ paddingLeft: 1,
315
327
  backgroundColor: colors.surface,
316
328
  });
317
329
  const searchInput = new InputRenderable(renderer, {
@@ -320,8 +332,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
320
332
  value: "",
321
333
  backgroundColor: colors.surface,
322
334
  textColor: colors.text,
323
- borderColor: colors.blue,
324
- selectedBorderColor: colors.blue,
325
335
  paddingX: 0,
326
336
  flexGrow: 1,
327
337
  });
@@ -332,11 +342,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
332
342
  const toolbarText = new TextRenderable(renderer, {
333
343
  content: formatToolbar(currentSort, currentLimit, totalCount),
334
344
  visible: true, // hidden in trending mode
335
- backgroundColor: colors.surface, // Surface panel
336
- color: colors.muted,
345
+ fg: colors.muted,
337
346
  height: 1,
338
- borderBottom: true,
339
- borderBottomColor: colors.border,
340
347
  paddingX: 1,
341
348
  });
342
349
  root.add(toolbarText);
@@ -352,9 +359,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
352
359
 
353
360
  // Results pane
354
361
  const resultsBox = new BoxRenderable(renderer, {
355
- bordered: true,
362
+ border: true,
356
363
  borderColor: colors.border,
357
- borderBottomColor: colors.border,
358
364
  title: " Results ",
359
365
  titleColor: colors.blue,
360
366
  width: "50%",
@@ -384,11 +390,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
384
390
 
385
391
  // Detail / Graph pane
386
392
  const detailBox = new BoxRenderable(renderer, {
387
- bordered: true,
388
- borderColor: colors.border,
389
- borderBottomColor: colors.border,
390
- title: " Details ",
391
- titleColor: colors.green,
392
393
  width: "50%",
393
394
  flexDirection: "column",
394
395
  paddingX: 1,
@@ -398,7 +399,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
398
399
  });
399
400
  const detailText = new TextRenderable(renderer, {
400
401
  content: "",
401
- color: colors.text,
402
+ fg: colors.text,
402
403
  wrapMode: "none",
403
404
  });
404
405
  detailBox.add(detailText);
@@ -424,8 +425,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
424
425
  "╚██████╔╝██║ ██║██║ ██║██║ ╚████║██████╔",
425
426
  " ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝",
426
427
  ].join("\n"),
427
- color: colors.accent,
428
- backgroundColor: colors.bg,
428
+ fg: colors.accent,
429
+ bg: colors.bg,
429
430
  height: 6,
430
431
  });
431
432
  landingBox.add(landingBanner);
@@ -483,7 +484,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
483
484
  const spacerTop = new TextRenderable(renderer, { content: "", height: 1 });
484
485
  const titleLine = new TextRenderable(renderer, {
485
486
  content: opt.title,
486
- color: i === 0 ? colors.accent : colors.text,
487
+ fg: i === 0 ? colors.accent : colors.text,
487
488
  height: 1,
488
489
  });
489
490
  const spacerBot = new TextRenderable(renderer, { content: "", height: 1 });
@@ -494,8 +495,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
494
495
 
495
496
  const landingHint = new TextRenderable(renderer, {
496
497
  content: " \u2191\u2193 Navigate \u21E9 Select [?]help [q]uit",
497
- color: colors.muted,
498
- backgroundColor: colors.bg,
498
+ fg: colors.muted,
499
+ bg: colors.bg,
499
500
  height: 1,
500
501
  marginTop: 1,
501
502
  });
@@ -516,10 +517,10 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
516
517
  for (let i = 0; i < landingCards.length; i++) {
517
518
  const isSel = i === landingSelected;
518
519
  landingCards[i].borderColor = isSel ? colors.accent : colors.border;
519
- const children = landingCards[i].children ?? [];
520
+ const children = landingCards[i].getChildren();
520
521
  const titleChild = children[1] as TextRenderable;
521
522
  if (titleChild) {
522
- titleChild.color = isSel ? colors.accent : colors.text;
523
+ titleChild.fg = isSel ? colors.accent : colors.text;
523
524
  }
524
525
  }
525
526
  }
@@ -552,7 +553,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
552
553
  // Hide main content so overlays can take 100% of the content area
553
554
  function hideMainContent() {
554
555
  trendingTabBox.visible = false;
555
- gapBox.visible = false;
556
556
  searchBox.visible = false;
557
557
  toolbarText.visible = false;
558
558
  body.visible = false;
@@ -563,7 +563,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
563
563
  trendingTabBox.visible = true;
564
564
  } else {
565
565
  searchBox.visible = true;
566
- gapBox.visible = true;
567
566
  toolbarText.visible = true;
568
567
  }
569
568
  body.visible = true;
@@ -590,7 +589,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
590
589
  landingBox.visible = true;
591
590
  body.visible = false;
592
591
  trendingTabBox.visible = false;
593
- gapBox.visible = false;
594
592
  searchBox.visible = false;
595
593
  toolbarText.visible = false;
596
594
  } else {
@@ -632,7 +630,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
632
630
  } else if (type === "update") {
633
631
  updateDim.visible = true;
634
632
  updateBox.visible = true;
635
- updateSelect.focus();
633
+ // Selection is driven by updateSelectedOption + the global key handler
634
+ // (see the "Update panel" block); there is no focusable Select here.
636
635
  }
637
636
  renderer.requestRender();
638
637
  }
@@ -643,8 +642,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
643
642
  visible: false,
644
643
  flexGrow: 1,
645
644
  backgroundColor: colors.bg,
646
- borderBottom: true,
647
- borderBottomColor: colors.border,
648
645
  });
649
646
  const helpScroll = new ScrollBoxRenderable(renderer, {
650
647
  flexGrow: 1,
@@ -655,15 +652,15 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
655
652
  viewportOptions: { backgroundColor: colors.bg },
656
653
  contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
657
654
  scrollbarOptions: {
658
- backgroundColor: colors.bg,
659
- foregroundColor: colors.muted,
660
655
  width: 1,
656
+ trackOptions: {
657
+ backgroundColor: colors.bg,
658
+ foregroundColor: colors.muted,
659
+ },
661
660
  },
662
661
  });
663
662
  for (const section of helpSections) {
664
663
  const sectionBox = new BoxRenderable(renderer, {
665
- borderBottom: true,
666
- borderBottomColor: colors.border,
667
664
  title: section.title,
668
665
  titleColor:
669
666
  (colors as Record<string, string>)[section.titleColor] ??
@@ -679,7 +676,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
679
676
  sectionBox.add(
680
677
  new TextRenderable(renderer, {
681
678
  content: ` ${keys} ${row.action}`,
682
- color: colors.text,
679
+ fg: colors.text,
683
680
  }),
684
681
  );
685
682
  }
@@ -687,7 +684,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
687
684
  sectionBox.add(
688
685
  new TextRenderable(renderer, {
689
686
  content: ` ${section.note}`,
690
- color: colors.muted,
687
+ fg: colors.muted,
691
688
  }),
692
689
  );
693
690
  }
@@ -701,8 +698,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
701
698
  visible: false,
702
699
  flexGrow: 1,
703
700
  backgroundColor: colors.bg,
704
- borderBottom: true,
705
- borderBottomColor: colors.border,
706
701
  title: " Search History ",
707
702
  titleColor: colors.blue,
708
703
  });
@@ -741,8 +736,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
741
736
  visible: false,
742
737
  flexGrow: 1,
743
738
  backgroundColor: colors.bg,
744
- borderBottom: true,
745
- borderBottomColor: colors.border,
746
739
  title: " Bookmarks ",
747
740
  titleColor: colors.green,
748
741
  });
@@ -781,8 +774,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
781
774
  visible: false,
782
775
  flexGrow: 1,
783
776
  backgroundColor: colors.bg,
784
- borderBottom: true,
785
- borderBottomColor: colors.border,
786
777
  title: " Saved Searches ",
787
778
  titleColor: colors.yellow,
788
779
  });
@@ -805,8 +796,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
805
796
  visible: false,
806
797
  flexGrow: 1,
807
798
  backgroundColor: colors.bg,
808
- borderBottom: true,
809
- borderBottomColor: colors.border,
810
799
  title: " Topics ",
811
800
  titleColor: colors.purple ?? colors.blue,
812
801
  });
@@ -845,8 +834,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
845
834
  visible: false,
846
835
  flexGrow: 1,
847
836
  backgroundColor: colors.bg,
848
- borderBottom: true,
849
- borderBottomColor: colors.border,
850
837
  title: " Export ",
851
838
  titleColor: colors.green,
852
839
  });
@@ -886,15 +873,13 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
886
873
  visible: false,
887
874
  flexGrow: 1,
888
875
  backgroundColor: colors.bg,
889
- borderBottom: true,
890
- borderBottomColor: colors.border,
891
876
  title: " Comparison ",
892
877
  titleColor: colors.yellow,
893
878
  });
894
879
  const compareText = new TextRenderable(renderer, {
895
880
  content: "",
896
- color: colors.text,
897
- backgroundColor: colors.bg,
881
+ fg: colors.text,
882
+ bg: colors.bg,
898
883
  });
899
884
  compareBox.add(compareText);
900
885
  root.add(compareBox);
@@ -904,15 +889,13 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
904
889
  visible: false,
905
890
  flexGrow: 1,
906
891
  backgroundColor: colors.bg,
907
- borderBottom: true,
908
- borderBottomColor: colors.border,
909
892
  title: " Notifications ",
910
893
  titleColor: colors.yellow,
911
894
  });
912
895
  const notifsText = new TextRenderable(renderer, {
913
896
  content: "(no notifications)",
914
- color: colors.text,
915
- backgroundColor: colors.bg,
897
+ fg: colors.text,
898
+ bg: colors.bg,
916
899
  });
917
900
  notifsBox.add(notifsText);
918
901
  root.add(notifsBox);
@@ -933,8 +916,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
933
916
  visible: false,
934
917
  flexGrow: 1,
935
918
  backgroundColor: colors.bg,
936
- borderBottom: true,
937
- borderBottomColor: colors.border,
938
919
  title: " Share Repo ",
939
920
  titleColor: colors.blue,
940
921
  });
@@ -1041,11 +1022,9 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1041
1022
  });
1042
1023
  const leaderFooter = new TextRenderable(renderer, {
1043
1024
  content: " ↑↓ select Enter run Esc/q close ",
1044
- color: colors.muted,
1045
- backgroundColor: colors.surfaceDim,
1025
+ fg: colors.muted,
1026
+ bg: colors.surfaceDim,
1046
1027
  height: 1,
1047
- borderTop: true,
1048
- borderTopColor: colors.border,
1049
1028
  paddingX: 2,
1050
1029
  });
1051
1030
  leaderBox.add(leaderSelect);
@@ -1087,8 +1066,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1087
1066
  });
1088
1067
  const updateText = new TextRenderable(renderer, {
1089
1068
  content: "",
1090
- color: colors.accent,
1091
- backgroundColor: colors.surfaceDim,
1069
+ fg: colors.accent,
1070
+ bg: colors.surfaceDim,
1092
1071
  });
1093
1072
 
1094
1073
  // Horizontal option boxes
@@ -1113,8 +1092,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1113
1092
  });
1114
1093
  const updateNowText = new TextRenderable(renderer, {
1115
1094
  content: " Update Now ",
1116
- color: updateSelectedOption === 0 ? colors.yellow : colors.text,
1117
- backgroundColor: colors.surfaceDim,
1095
+ fg: updateSelectedOption === 0 ? colors.yellow : colors.text,
1096
+ bg: colors.surfaceDim,
1118
1097
  });
1119
1098
  updateNowBox.add(updateNowText);
1120
1099
 
@@ -1129,8 +1108,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1129
1108
  });
1130
1109
  const laterText = new TextRenderable(renderer, {
1131
1110
  content: " Later",
1132
- color: updateSelectedOption === 1 ? colors.yellow : colors.text,
1133
- backgroundColor: colors.surfaceDim,
1111
+ fg: updateSelectedOption === 1 ? colors.yellow : colors.text,
1112
+ bg: colors.surfaceDim,
1134
1113
  });
1135
1114
  laterBox.add(laterText);
1136
1115
 
@@ -1145,8 +1124,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1145
1124
  });
1146
1125
  const neverText = new TextRenderable(renderer, {
1147
1126
  content: " Don't show again",
1148
- color: updateSelectedOption === 2 ? colors.yellow : colors.text,
1149
- backgroundColor: colors.surfaceDim,
1127
+ fg: updateSelectedOption === 2 ? colors.yellow : colors.text,
1128
+ bg: colors.surfaceDim,
1150
1129
  });
1151
1130
  neverBox.add(neverText);
1152
1131
 
@@ -1157,10 +1136,9 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1157
1136
  updateSelectedOption === 1 ? colors.yellow : colors.border;
1158
1137
  neverBox.borderColor =
1159
1138
  updateSelectedOption === 2 ? colors.yellow : colors.border;
1160
- updateNowText.color =
1161
- updateSelectedOption === 0 ? colors.yellow : colors.text;
1162
- laterText.color = updateSelectedOption === 1 ? colors.yellow : colors.text;
1163
- neverText.color = updateSelectedOption === 2 ? colors.yellow : colors.text;
1139
+ updateNowText.fg = updateSelectedOption === 0 ? colors.yellow : colors.text;
1140
+ laterText.fg = updateSelectedOption === 1 ? colors.yellow : colors.text;
1141
+ neverText.fg = updateSelectedOption === 2 ? colors.yellow : colors.text;
1164
1142
  renderer.requestRender();
1165
1143
  }
1166
1144
 
@@ -1169,8 +1147,8 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1169
1147
 
1170
1148
  const updateFooter = new TextRenderable(renderer, {
1171
1149
  content: " ←→ select Enter Esc/q close",
1172
- color: colors.muted,
1173
- backgroundColor: colors.surfaceDim,
1150
+ fg: colors.muted,
1151
+ bg: colors.surfaceDim,
1174
1152
  height: 1,
1175
1153
  paddingX: 1,
1176
1154
  });
@@ -1543,13 +1521,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1543
1521
  name: "Quit",
1544
1522
  description: "Exit ghfind",
1545
1523
  action: () => {
1546
- saveSession({
1547
- mode: currentMode,
1548
- query: currentQueryInput,
1549
- sort: currentSort,
1550
- limit: currentLimit,
1551
- trendingTab,
1552
- });
1524
+ saveSession(buildSessionState());
1553
1525
  cleanup();
1554
1526
  },
1555
1527
  },
@@ -1673,7 +1645,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1673
1645
  saveSearch(
1674
1646
  name,
1675
1647
  currentQueryInput,
1676
- currentMode,
1648
+ currentMode === "trending" ? "trending" : "search",
1677
1649
  currentSort,
1678
1650
  currentLimit,
1679
1651
  currentMode === "trending" ? trendingTab : undefined,
@@ -1712,8 +1684,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1712
1684
  visible: false,
1713
1685
  flexGrow: 1,
1714
1686
  backgroundColor: colors.surface,
1715
- borderBottom: true,
1716
- borderBottomColor: colors.border,
1717
1687
  title: " Org profile ",
1718
1688
  titleColor: colors.green,
1719
1689
  });
@@ -1726,22 +1696,24 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1726
1696
  viewportOptions: { backgroundColor: colors.bg },
1727
1697
  contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
1728
1698
  scrollbarOptions: {
1729
- backgroundColor: colors.bg,
1730
- foregroundColor: colors.muted,
1731
1699
  width: 1,
1700
+ trackOptions: {
1701
+ backgroundColor: colors.bg,
1702
+ foregroundColor: colors.muted,
1703
+ },
1732
1704
  },
1733
1705
  });
1734
1706
  const orgText = new TextRenderable(renderer, {
1735
1707
  content: "",
1736
- color: colors.text,
1737
- backgroundColor: colors.bg,
1708
+ fg: colors.text,
1709
+ bg: colors.bg,
1738
1710
  });
1739
1711
  orgScroll.add(orgText);
1740
1712
  orgBox.add(orgScroll);
1741
1713
  const orgFooter = new TextRenderable(renderer, {
1742
1714
  content: " ↑↓/jk scroll Esc/q close",
1743
- color: colors.muted,
1744
- backgroundColor: colors.bg,
1715
+ fg: colors.muted,
1716
+ bg: colors.bg,
1745
1717
  height: 1,
1746
1718
  paddingX: 1,
1747
1719
  });
@@ -1812,8 +1784,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1812
1784
  visible: false,
1813
1785
  flexGrow: 1,
1814
1786
  backgroundColor: colors.surface,
1815
- borderBottom: true,
1816
- borderBottomColor: colors.border,
1817
1787
  title: " README ",
1818
1788
  titleColor: colors.green,
1819
1789
  });
@@ -1826,22 +1796,24 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1826
1796
  viewportOptions: { backgroundColor: colors.bg },
1827
1797
  contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
1828
1798
  scrollbarOptions: {
1829
- backgroundColor: colors.bg,
1830
- foregroundColor: colors.muted,
1831
1799
  width: 1,
1800
+ trackOptions: {
1801
+ backgroundColor: colors.bg,
1802
+ foregroundColor: colors.muted,
1803
+ },
1832
1804
  },
1833
1805
  });
1834
1806
  const readmeText = new TextRenderable(renderer, {
1835
1807
  content: "",
1836
- color: colors.text,
1837
- backgroundColor: colors.bg,
1808
+ fg: colors.text,
1809
+ bg: colors.bg,
1838
1810
  });
1839
1811
  readmeScroll.add(readmeText);
1840
1812
  readmeBox.add(readmeScroll);
1841
1813
  const readmeFooter = new TextRenderable(renderer, {
1842
1814
  content: " ↑↓/jk scroll Esc/q close",
1843
- color: colors.muted,
1844
- backgroundColor: colors.bg,
1815
+ fg: colors.muted,
1816
+ bg: colors.bg,
1845
1817
  height: 1,
1846
1818
  paddingX: 1,
1847
1819
  });
@@ -1889,12 +1861,9 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1889
1861
  // ── Status bar ──────────────────────────────────────────────────────
1890
1862
  const statusBar = new TextRenderable(renderer, {
1891
1863
  content: " Ready. Press Enter to search.",
1892
- backgroundColor: colors.surface,
1893
- color: colors.muted,
1864
+ fg: colors.muted,
1894
1865
  height: 1,
1895
1866
  paddingX: 1,
1896
- borderTop: true,
1897
- borderTopColor: colors.border,
1898
1867
  });
1899
1868
  root.add(statusBar);
1900
1869
 
@@ -1919,6 +1888,23 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
1919
1888
  statusMgr.set("idle", msg);
1920
1889
  }
1921
1890
 
1891
+ /** Session snapshot; only restorable modes are persisted (see SessionState). */
1892
+ function buildSessionState(): SessionState {
1893
+ const mode: SessionState["mode"] =
1894
+ currentMode === "trending"
1895
+ ? "trending"
1896
+ : currentMode === "packages"
1897
+ ? "packages"
1898
+ : "search";
1899
+ return {
1900
+ mode,
1901
+ query: currentQueryInput,
1902
+ sort: currentSort,
1903
+ limit: currentLimit,
1904
+ trendingTab,
1905
+ };
1906
+ }
1907
+
1922
1908
  function setToolbar() {
1923
1909
  const sort = currentMode === "packages" ? currentPackageSort : currentSort;
1924
1910
  toolbarText.content = formatToolbar(sort, currentLimit, totalCount);
@@ -2081,11 +2067,29 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
2081
2067
  ),
2082
2068
  fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`, {
2083
2069
  headers,
2084
- }).then((r) => (r.ok ? r.clone().json() : null)),
2070
+ }).then(
2071
+ (
2072
+ r,
2073
+ ): Promise<{
2074
+ all?: number[];
2075
+ topics?: string[];
2076
+ message?: string;
2077
+ } | null> | null =>
2078
+ r.ok
2079
+ ? (r.clone().json() as Promise<{
2080
+ all?: number[];
2081
+ topics?: string[];
2082
+ message?: string;
2083
+ }>)
2084
+ : null,
2085
+ ),
2085
2086
  ]);
2086
2087
  let chartSection = "";
2087
2088
  if (chartRes.ok) {
2088
- const data = await chartRes.json();
2089
+ const data = (await chartRes.json()) as {
2090
+ all?: number[];
2091
+ message?: string;
2092
+ };
2089
2093
  if (data?.all && data.all.length >= 2) {
2090
2094
  chartCommitData = data.all;
2091
2095
  // Chart width: leave room for y-axis labels (6 chars) and padding.
@@ -2250,7 +2254,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
2250
2254
  function showSearchMode() {
2251
2255
  currentMode = "search";
2252
2256
  trendingTabBox.visible = false;
2253
- gapBox.visible = false;
2254
2257
  searchBox.visible = true;
2255
2258
  toolbarText.visible = true;
2256
2259
  body.visible = true;
@@ -2263,7 +2266,6 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
2263
2266
  function showPackagesMode() {
2264
2267
  currentMode = "packages";
2265
2268
  trendingTabBox.visible = false;
2266
- gapBox.visible = false;
2267
2269
  searchBox.visible = true;
2268
2270
  toolbarText.visible = true;
2269
2271
  body.visible = true;
@@ -2807,13 +2809,7 @@ ${pack.description ?? ""}`;
2807
2809
  toggleGraph();
2808
2810
  return;
2809
2811
  }
2810
- saveSession({
2811
- mode: currentMode,
2812
- query: currentQueryInput,
2813
- sort: currentSort,
2814
- limit: currentLimit,
2815
- trendingTab,
2816
- });
2812
+ saveSession(buildSessionState());
2817
2813
  cleanup();
2818
2814
  return;
2819
2815
  }
@@ -2826,12 +2822,9 @@ ${pack.description ?? ""}`;
2826
2822
 
2827
2823
  // Space toggles leader menu (open if closed, close if open)
2828
2824
  // Skip when the search input is focused so spaces can be typed in queries
2825
+ // (overlays were already handled above, so this is always "open" here)
2829
2826
  if (key.name === "space" && !searchInput.focused) {
2830
- if (currentOverlay === "leader") {
2831
- showOverlay("none");
2832
- } else {
2833
- showLeaderMenu();
2834
- }
2827
+ showLeaderMenu();
2835
2828
  return;
2836
2829
  }
2837
2830
 
@@ -2843,6 +2836,13 @@ ${pack.description ?? ""}`;
2843
2836
  const next = themes[(idx + 1) % themes.length];
2844
2837
  const nextTheme = loadTheme(next);
2845
2838
  Object.assign(colors, nextTheme);
2839
+ // Keep the terminal-matched backdrop and blended layers across theme
2840
+ // switches (fall back to the new theme's own layers when no detection).
2841
+ if (terminalBg) {
2842
+ colors.bg = terminalBg;
2843
+ Object.assign(colors, deriveSurfaceLayers(terminalBg));
2844
+ }
2845
+ renderer.setBackgroundColor(colors.bg);
2846
2846
  config.theme = next;
2847
2847
  saveConfig(config);
2848
2848
  renderer.requestRender();
@@ -2851,13 +2851,9 @@ ${pack.description ?? ""}`;
2851
2851
 
2852
2852
  // 'c' toggles compare on selected repo (when not typing in search)
2853
2853
  if (key.name === "c" && !searchInput.focused) {
2854
- if (currentOverlay === "compare") {
2855
- showOverlay("none");
2856
- } else {
2857
- toggleCompareOnSelected();
2858
- refreshCompare();
2859
- showOverlay("compare");
2860
- }
2854
+ toggleCompareOnSelected();
2855
+ refreshCompare();
2856
+ showOverlay("compare");
2861
2857
  return;
2862
2858
  }
2863
2859