@kud/gh-ink 0.57.1 → 0.59.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +126 -16
  2. package/dist/index.js +460 -166
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React2, { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
2
  import { useWindowSize, useInput, Text, Box } from 'ink';
3
- import { colors, ScrollView, TextInput, useAppKeys, LoadingScreen, ProgressBar, StatusMessage, FooterHints, useListCursor, Tabs, pillWidth, Pill } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, useAppKeys, LoadingScreen, StatusMessage, FooterHints, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth, Pill } from '@kud/ink-ui';
4
4
  import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
  import { $ } from 'zx';
@@ -634,15 +634,28 @@ var Progress = ({ row }) => {
634
634
  /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: GUTTER + fraction.padStart(FRACTION_COLS) })
635
635
  ] });
636
636
  };
637
- var SidePanel = ({
637
+ var railsOf = (rails) => rails === void 0 ? [] : Array.isArray(rails) ? rails : [rails];
638
+ var railRowsOf = (rails) => railsOf(rails).flatMap((s) => s.rows);
639
+ var linesFor = (rows) => HEADING_LINES + Math.max(1, rows) * ROW_LINES;
640
+ var railHeights = (height, rowCounts) => {
641
+ const out = [];
642
+ let remaining = height;
643
+ rowCounts.forEach((rows, i) => {
644
+ const share = Math.floor(remaining / (rowCounts.length - i));
645
+ const h = Math.min(share, linesFor(rows));
646
+ out.push(h);
647
+ remaining -= h;
648
+ });
649
+ return out;
650
+ };
651
+ var RailSection = ({
638
652
  sidebar,
639
- liveLabel = defaultLiveLabel,
640
- width = SIDEBAR_COLS,
653
+ liveLabel,
654
+ content,
641
655
  height,
642
- focused = false,
643
- cursor = 0
656
+ focused,
657
+ cursor
644
658
  }) => {
645
- const content = contentOf(width);
646
659
  const labelCols = content - MARKS;
647
660
  const liveCols = content - FACTS_FIXED;
648
661
  const capacity = height === void 0 ? sidebar.rows.length : railCapacity(height, sidebar.rows.length);
@@ -653,6 +666,46 @@ var SidePanel = ({
653
666
  const rule = "\u254C".repeat(
654
667
  Math.max(0, content - [...sidebar.title].length - focus.length - 1)
655
668
  );
669
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height, children: [
670
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
671
+ /* @__PURE__ */ jsx(Text, { bold: true, children: sidebar.title }),
672
+ focused ? /* @__PURE__ */ jsx(Text, { color: colors.accent, children: focus }) : null,
673
+ /* @__PURE__ */ jsx(Text, { color: colors.info, dimColor: true, children: ` ${rule}` })
674
+ ] }),
675
+ sidebar.rows.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: "nothing open" }) }) : shown.map((row, i) => {
676
+ const active = focused && start + i === cursor;
677
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
678
+ /* @__PURE__ */ jsxs(Box, { children: [
679
+ /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? "\u276F " : " " }),
680
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
681
+ /* @__PURE__ */ jsx(Text, { bold: active, children: truncateWords(row.label, labelCols) })
682
+ ] }),
683
+ /* @__PURE__ */ jsxs(Box, { children: [
684
+ /* @__PURE__ */ jsx(Text, { color: row.markerColor, children: " ".repeat(FACTS_INDENT - 2) + (row.marker ?? " ") + " " }),
685
+ /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: row.key.padEnd(KEY_COLS) + GUTTER }),
686
+ /* @__PURE__ */ jsx(Progress, { row }),
687
+ /* @__PURE__ */ jsx(Text, { children: GUTTER + (row.live !== void 0 ? liveLabel(row.live).slice(0, liveCols) : "") })
688
+ ] })
689
+ ] }, row.key);
690
+ }),
691
+ hidden > 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: `+${hidden} more` }) }) : null
692
+ ] });
693
+ };
694
+ var SidePanel = ({
695
+ sidebar,
696
+ liveLabel = defaultLiveLabel,
697
+ width = SIDEBAR_COLS,
698
+ height,
699
+ focused = false,
700
+ cursor = 0
701
+ }) => {
702
+ const content = contentOf(width);
703
+ const sections = railsOf(sidebar);
704
+ const heights = height === void 0 ? sections.map(() => void 0) : railHeights(
705
+ height,
706
+ sections.map((s) => s.rows.length)
707
+ );
708
+ let offset = 0;
656
709
  return (
657
710
  // A rule down the left rather than a full box: the rail's other three edges
658
711
  // already have the frame's border a column or two away, and a second
@@ -660,7 +713,7 @@ var SidePanel = ({
660
713
  // focus and act on, which this cannot be. One line is the whole claim: what
661
714
  // is left of it is the list, what is right of it is not. In the frame's own
662
715
  // colour and not dimmed, so the two rules read as one hierarchy.
663
- /* @__PURE__ */ jsxs(
716
+ /* @__PURE__ */ jsx(
664
717
  Box,
665
718
  {
666
719
  flexDirection: "column",
@@ -673,34 +726,113 @@ var SidePanel = ({
673
726
  borderRight: false,
674
727
  borderBottom: false,
675
728
  paddingLeft: PAD,
676
- children: [
677
- /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
678
- /* @__PURE__ */ jsx(Text, { bold: true, children: sidebar.title }),
679
- focused ? /* @__PURE__ */ jsx(Text, { color: colors.accent, children: focus }) : null,
680
- /* @__PURE__ */ jsx(Text, { color: colors.info, dimColor: true, children: ` ${rule}` })
681
- ] }),
682
- sidebar.rows.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: "nothing open" }) }) : shown.map((row, i) => {
683
- const active = focused && start + i === cursor;
684
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
685
- /* @__PURE__ */ jsxs(Box, { children: [
686
- /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? "\u276F " : " " }),
687
- /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
688
- /* @__PURE__ */ jsx(Text, { bold: active, children: truncateWords(row.label, labelCols) })
689
- ] }),
690
- /* @__PURE__ */ jsxs(Box, { children: [
691
- /* @__PURE__ */ jsx(Text, { color: row.markerColor, children: " ".repeat(FACTS_INDENT - 2) + (row.marker ?? " ") + " " }),
692
- /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: row.key.padEnd(KEY_COLS) + GUTTER }),
693
- /* @__PURE__ */ jsx(Progress, { row }),
694
- /* @__PURE__ */ jsx(Text, { children: GUTTER + (row.live !== void 0 ? liveLabel(row.live).slice(0, liveCols) : "") })
695
- ] })
696
- ] }, row.key);
697
- }),
698
- hidden > 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: `+${hidden} more` }) }) : null
699
- ]
729
+ children: sections.map((section, i) => {
730
+ const first = offset;
731
+ offset += section.rows.length;
732
+ const inside = focused && cursor >= first && cursor < offset;
733
+ const owns = inside || focused && sections.length === 1;
734
+ return /* @__PURE__ */ jsx(
735
+ RailSection,
736
+ {
737
+ sidebar: section,
738
+ liveLabel,
739
+ content,
740
+ height: heights[i],
741
+ focused: owns,
742
+ cursor: cursor - first
743
+ },
744
+ section.title
745
+ );
746
+ })
700
747
  }
701
748
  )
702
749
  );
703
750
  };
751
+ var stripGlyph = (state) => {
752
+ switch (state) {
753
+ case "ok":
754
+ return ["\u2713", colors.success];
755
+ case "warn":
756
+ return ["!", colors.warning];
757
+ case "fail":
758
+ return ["\u2717", colors.error];
759
+ case "unknown":
760
+ return ["\u25CB", colors.muted];
761
+ }
762
+ };
763
+ var ALARM = "\u25B2";
764
+ var SEVERITY = {
765
+ fail: 0,
766
+ warn: 1,
767
+ unknown: 2,
768
+ ok: 3
769
+ };
770
+ var ageLabel = (at, now = Date.now()) => {
771
+ if (at === void 0) return "";
772
+ const s = Math.max(0, Math.round((now - at) / 1e3));
773
+ if (s < 60) return `${s}s`;
774
+ const m = Math.round(s / 60);
775
+ if (m < 60) return `${m}m`;
776
+ return `${Math.round(m / 60)}h`;
777
+ };
778
+ var itemText = (item) => `${stripGlyph(item.state)[0]} ${item.key}${item.alarm ? ALARM : ""}`;
779
+ var stripLayout = (strip, width, now = Date.now()) => {
780
+ const age = ageLabel(strip.at, now);
781
+ const fixed = 2 + strip.title.length + 2 + (age ? age.length + 3 : 0);
782
+ const full = strip.items.map(itemText).join(" ");
783
+ if (fixed + full.length <= width)
784
+ return { parts: strip.items, summary: full, age };
785
+ const sorted = [...strip.items].sort(
786
+ (a, b) => SEVERITY[a.state] - SEVERITY[b.state]
787
+ );
788
+ const bad = sorted.filter((i) => i.state !== "ok" || i.alarm);
789
+ const ok = strip.items.length - bad.length;
790
+ const tally = (n, s) => n > 0 ? `${n} ${stripGlyph(s)[0]}` : "";
791
+ let named = [];
792
+ let text = "";
793
+ for (let n = bad.length; n >= 0; n--) {
794
+ named = bad.slice(0, n);
795
+ const rest = bad.slice(n);
796
+ const counts2 = ["fail", "warn", "unknown"].map((s) => tally(rest.filter((i) => i.state === s).length, s)).filter(Boolean);
797
+ text = [tally(ok, "ok"), ...counts2, ...named.map(itemText)].filter(Boolean).join(" \xB7 ");
798
+ if (fixed + text.length <= width) break;
799
+ }
800
+ return { parts: null, summary: text, age };
801
+ };
802
+ var StatusStripLine = ({
803
+ strip,
804
+ label = "status",
805
+ width,
806
+ now
807
+ }) => {
808
+ if (strip === void 0)
809
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
810
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
811
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} loading\u2026` })
812
+ ] });
813
+ if (strip === null)
814
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
815
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u25CB " }),
816
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} no signal / not configured` })
817
+ ] });
818
+ const { parts, summary, age } = stripLayout(strip, width, now);
819
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
820
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${strip.title} ` }),
821
+ parts ? parts.map((item, i) => {
822
+ const [glyph, color] = stripGlyph(item.state);
823
+ return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
824
+ i > 0 ? /* @__PURE__ */ jsx(Text, { children: " " }) : null,
825
+ /* @__PURE__ */ jsx(Text, { color, bold: true, children: glyph }),
826
+ /* @__PURE__ */ jsxs(Text, { children: [
827
+ " ",
828
+ item.key
829
+ ] }),
830
+ item.alarm ? /* @__PURE__ */ jsx(Text, { color: colors.error, bold: true, children: ALARM }) : null
831
+ ] }, item.key);
832
+ }) : /* @__PURE__ */ jsx(Text, { children: summary }),
833
+ age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${age}` }) : null
834
+ ] });
835
+ };
704
836
  var healthDisplay = {
705
837
  none: { glyph: " ", color: colors.muted },
706
838
  draft: { glyph: "~", color: colors.muted },
@@ -749,7 +881,7 @@ var displayFor = (h) => h ? healthDisplay[h] : UNREAD_DISPLAY;
749
881
 
750
882
  // src/inbox/cache.ts
751
883
  var cacheTtlMs = () => inboxConfig().cacheTtlMs;
752
- var CACHE_VERSION = 4;
884
+ var CACHE_VERSION = 5;
753
885
  var cacheDir = () => join(
754
886
  process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
755
887
  inboxConfig().cacheNamespace
@@ -765,7 +897,7 @@ var readCache = (key) => {
765
897
  login: raw.login ?? "",
766
898
  at: raw.at ?? 0,
767
899
  budget: raw.budget,
768
- ...Array.isArray(raw.sidebar?.rows) ? { sidebar: raw.sidebar } : {}
900
+ ...railsOf(raw.sidebar).every((s) => Array.isArray(s?.rows)) && raw.sidebar !== void 0 ? { sidebar: raw.sidebar } : {}
769
901
  };
770
902
  } catch {
771
903
  return null;
@@ -1059,9 +1191,7 @@ var turnSentences = (item, login) => {
1059
1191
  return [`${item.lastActor} spoke last (\u2190), ${when}. Your reply is owed.`];
1060
1192
  const them = item.author && item.author !== login ? item.author : null;
1061
1193
  if (!them)
1062
- return [
1063
- `You spoke last, ${when}. It is waiting on a reviewer, not on you.`
1064
- ];
1194
+ return [`You spoke last, ${when}. It is waiting on a reviewer, not on you.`];
1065
1195
  const lines = [`You spoke last, ${when}. The ball is with ${them}.`];
1066
1196
  if (d?.lastCommitAt && d.lastEventAt && d.lastCommitAt < d.lastEventAt)
1067
1197
  lines.push(
@@ -1440,7 +1570,7 @@ var backdropStyle = (props) => ({
1440
1570
  inverse: false,
1441
1571
  backgroundColor: void 0
1442
1572
  });
1443
- var Text3 = ({ children, ...props }) => {
1573
+ var Text4 = ({ children, ...props }) => {
1444
1574
  const dimmed = useContext(DimContext);
1445
1575
  return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, ...backdropStyle(props), children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
1446
1576
  };
@@ -1521,6 +1651,11 @@ var unsubscribeFrom = async (item) => {
1521
1651
  if (!id) throw new Error(`no node id for ${item.repo}#${item.number}`);
1522
1652
  await quietly`gh api graphql -f query=${UNSUBSCRIBE_MUTATION} -f id=${id}`;
1523
1653
  };
1654
+ var openInJira = (jiraBase, key, showFlash) => {
1655
+ quietly`open ${jiraBase}/${key}`.catch(() => {
1656
+ });
1657
+ showFlash(`\u2197 Opened ${key} in Jira`);
1658
+ };
1524
1659
  var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions, onRefresh, onRemove, onOpenView, ext) => {
1525
1660
  if (item.kind === "repo-header" || item.kind === "subgroup-header" || item.kind === "show-more" || item.kind === "show-less")
1526
1661
  return [];
@@ -1728,11 +1863,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1728
1863
  actions.push({
1729
1864
  label: `Open ${jiraKey} in Jira`,
1730
1865
  hint: "t",
1731
- run: () => {
1732
- quietly`open ${jiraBase}/${jiraKey}`.catch(() => {
1733
- });
1734
- showFlash(`\u2197 Opened ${jiraKey} in Jira`);
1735
- }
1866
+ run: () => openInJira(jiraBase, jiraKey, showFlash)
1736
1867
  });
1737
1868
  }
1738
1869
  }
@@ -1835,13 +1966,13 @@ var InboxHeader = ({
1835
1966
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - budgetSeg.length - statusSeg.length
1836
1967
  );
1837
1968
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1838
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: brand }),
1839
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: countSeg }),
1840
- userSeg ? /* @__PURE__ */ jsx(Text3, { children: userSeg }) : null,
1841
- scopeLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: workLabel }) : null,
1842
- budgetLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: budgetCritical ? "#FF5F5F" : colors.accent, children: budgetSeg }) : null,
1969
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: brand }),
1970
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: countSeg }),
1971
+ userSeg ? /* @__PURE__ */ jsx(Text4, { children: userSeg }) : null,
1972
+ scopeLabel ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: workLabel }) : null,
1973
+ budgetLabel ? /* @__PURE__ */ jsx(Text4, { bold: true, color: budgetCritical ? "#FF5F5F" : colors.accent, children: budgetSeg }) : null,
1843
1974
  statusText ? /* @__PURE__ */ jsx(
1844
- Text3,
1975
+ Text4,
1845
1976
  {
1846
1977
  color: statusColor,
1847
1978
  dimColor: !statusColor,
@@ -1849,9 +1980,10 @@ var InboxHeader = ({
1849
1980
  children: statusSeg
1850
1981
  }
1851
1982
  ) : null,
1852
- /* @__PURE__ */ jsx(Text3, { color: colors.info, dimColor: true, children: "\u254C".repeat(fill) })
1983
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, dimColor: true, children: "\u254C".repeat(fill) })
1853
1984
  ] });
1854
1985
  };
1986
+ var stripSignature = (strip) => strip === null ? "\u2205" : strip.items.map((i) => `${i.key}=${i.state}${i.alarm ? "\u25B2" : ""}`).join(",");
1855
1987
  var toCiStatusState = (status) => status ? { kind: "ready", status } : { kind: "error" };
1856
1988
  var sameCiStatusState = (a, b) => {
1857
1989
  if (a.kind !== b.kind) return false;
@@ -1872,24 +2004,24 @@ var CiStatusLine = ({
1872
2004
  const name = job ?? "ci";
1873
2005
  if (state.kind === "loading")
1874
2006
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1875
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " \xB7 " }),
1876
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} loading\u2026` })
2007
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " \xB7 " }),
2008
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: `${name} loading\u2026` })
1877
2009
  ] });
1878
2010
  if (state.kind === "error")
1879
2011
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1880
- /* @__PURE__ */ jsx(Text3, { color: colors.error, bold: true, children: " \u2717 " }),
1881
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} no build / not configured` })
2012
+ /* @__PURE__ */ jsx(Text4, { color: colors.error, bold: true, children: " \u2717 " }),
2013
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: `${name} no build / not configured` })
1882
2014
  ] });
1883
2015
  const { status } = state;
1884
2016
  const [, color] = jenkinsResultDisplay(status.result, status.building);
1885
2017
  const label = status.building ? "BUILDING" : status.result;
1886
2018
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1887
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " }),
1888
- /* @__PURE__ */ jsx(Text3, { color, bold: true, children: "\u25CF " }),
1889
- /* @__PURE__ */ jsx(Text3, { bold: true, children: status.job }),
1890
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + label }),
1891
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " #" + status.buildNumber }),
1892
- status.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + status.age }) : null
2019
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " }),
2020
+ /* @__PURE__ */ jsx(Text4, { color, bold: true, children: "\u25CF " }),
2021
+ /* @__PURE__ */ jsx(Text4, { bold: true, children: status.job }),
2022
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " + label }),
2023
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: " #" + status.buildNumber }),
2024
+ status.age ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " + status.age }) : null
1893
2025
  ] });
1894
2026
  };
1895
2027
  var FENCE_COLS = 46;
@@ -1902,10 +2034,10 @@ var RepoHeaderRow = ({
1902
2034
  const fill = Math.max(4, FENCE_COLS - label.length);
1903
2035
  const rule = "\u2500".repeat(fill);
1904
2036
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1905
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1906
- /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: "\u2500\u2500 " }),
2037
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2038
+ /* @__PURE__ */ jsx(Text4, { dimColor: !active, children: "\u2500\u2500 " }),
1907
2039
  /* @__PURE__ */ jsx(
1908
- Text3,
2040
+ Text4,
1909
2041
  {
1910
2042
  dimColor: false,
1911
2043
  color: active ? void 0 : colors.secondary,
@@ -1913,7 +2045,7 @@ var RepoHeaderRow = ({
1913
2045
  children: repo
1914
2046
  }
1915
2047
  ),
1916
- /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
2048
+ /* @__PURE__ */ jsx(Text4, { dimColor: !active, children: " " + rule })
1917
2049
  ] });
1918
2050
  };
1919
2051
  var MERGED_HOLD_MS = 3e3;
@@ -1978,22 +2110,22 @@ var ItemRow = ({
1978
2110
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
1979
2111
  if (item.kind === "subgroup-header")
1980
2112
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1981
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: " \xBB " }),
1982
- /* @__PURE__ */ jsx(Text3, { bold: true, children: item.label })
2113
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: " \xBB " }),
2114
+ /* @__PURE__ */ jsx(Text4, { bold: true, children: item.label })
1983
2115
  ] });
1984
2116
  if (item.kind === "show-more")
1985
2117
  return /* @__PURE__ */ jsxs(Box, { children: [
1986
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1987
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1988
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1989
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `+${item.hidden.length} more` })
2118
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2119
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2120
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: active ? "\u21B5 " : " " }),
2121
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: `+${item.hidden.length} more` })
1990
2122
  ] });
1991
2123
  if (item.kind === "show-less")
1992
2124
  return /* @__PURE__ */ jsxs(Box, { children: [
1993
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1994
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1995
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1996
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "show less" })
2125
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2126
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2127
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: active ? "\u21B5 " : " " }),
2128
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "show less" })
1997
2129
  ] });
1998
2130
  if (item.kind === "task") {
1999
2131
  const note = item.note ?? "";
@@ -2003,14 +2135,14 @@ var ItemRow = ({
2003
2135
  cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - (markerCols > 0 ? markerCols + 1 : 0) - 12
2004
2136
  );
2005
2137
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2006
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2007
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2008
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
2009
- /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
2010
- markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
2011
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: active, children: item.key + " " }),
2138
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2139
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2140
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: parent ? "\u252C " : " " }),
2141
+ /* @__PURE__ */ jsx(Text4, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
2142
+ markerCols > 0 ? /* @__PURE__ */ jsx(Text4, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
2143
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: active, children: item.key + " " }),
2012
2144
  /* @__PURE__ */ jsx(
2013
- Text3,
2145
+ Text4,
2014
2146
  {
2015
2147
  bold: active || !!transient && isArrival(transient),
2016
2148
  dimColor: !!transient && isDeparture(transient),
@@ -2019,7 +2151,7 @@ var ItemRow = ({
2019
2151
  }
2020
2152
  ),
2021
2153
  item.pill && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2022
- /* @__PURE__ */ jsx(Text3, { children: " " }),
2154
+ /* @__PURE__ */ jsx(Text4, { children: " " }),
2023
2155
  /* @__PURE__ */ jsx(
2024
2156
  Pill,
2025
2157
  {
@@ -2030,9 +2162,9 @@ var ItemRow = ({
2030
2162
  }
2031
2163
  )
2032
2164
  ] }) : null,
2033
- note ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${note}` }) : null,
2165
+ note ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` ${note}` }) : null,
2034
2166
  farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2035
- /* @__PURE__ */ jsx(Text3, { children: " " }),
2167
+ /* @__PURE__ */ jsx(Text4, { children: " " }),
2036
2168
  /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2037
2169
  ] }) : null
2038
2170
  ] });
@@ -2048,7 +2180,7 @@ var ItemRow = ({
2048
2180
  const sizeParts = sizePartsOf(item);
2049
2181
  const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
2050
2182
  const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
2051
- const ageLabel = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
2183
+ const ageLabel2 = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
2052
2184
  const mergedLabel = merged ? "merged" : "";
2053
2185
  const pillCaps = [mergedLabel, farewellLabel].filter(Boolean).length * 2;
2054
2186
  const repoLabel = depthOf(item) > 0 ? item.repo : "";
@@ -2081,7 +2213,7 @@ var ItemRow = ({
2081
2213
  };
2082
2214
  const widthOf2 = () => {
2083
2215
  const suffix = [
2084
- givingUp.age ? "" : ageLabel || "",
2216
+ givingUp.age ? "" : ageLabel2 || "",
2085
2217
  // ASCII only, so no PUA double-width correction — see the label cell.
2086
2218
  givingUp.size ? "" : sizeLabel || "",
2087
2219
  givingUp.threads ? "" : unresolvedLabel,
@@ -2107,13 +2239,13 @@ var ItemRow = ({
2107
2239
  const labelLabel = labelCell();
2108
2240
  const titleMax = Math.max(1, cols - widthOf2());
2109
2241
  return /* @__PURE__ */ jsxs(Box, { children: [
2110
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2111
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2112
- /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
2113
- /* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
2114
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: numStr }),
2242
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2243
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2244
+ /* @__PURE__ */ jsx(Text4, { color, bold: true, children: icon + " " }),
2245
+ /* @__PURE__ */ jsx(Text4, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
2246
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: numStr }),
2115
2247
  /* @__PURE__ */ jsx(
2116
- Text3,
2248
+ Text4,
2117
2249
  {
2118
2250
  bold: active || !!transient && isArrival(transient),
2119
2251
  dimColor: !!transient && isDeparture(transient),
@@ -2121,33 +2253,33 @@ var ItemRow = ({
2121
2253
  children: truncate(item.title, titleMax) + " "
2122
2254
  }
2123
2255
  ),
2124
- labelLabel ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: labelLabel + " " }) : null,
2125
- repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: repoLabel }) : null,
2126
- sizeParts && !givingUp.size ? /* @__PURE__ */ jsxs(Text3, { children: [
2256
+ labelLabel ? /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: labelLabel + " " }) : null,
2257
+ repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: repoLabel }) : null,
2258
+ sizeParts && !givingUp.size ? /* @__PURE__ */ jsxs(Text4, { children: [
2127
2259
  " ",
2128
- /* @__PURE__ */ jsx(Text3, { color: colors.success, children: sizeParts.added }),
2260
+ /* @__PURE__ */ jsx(Text4, { color: colors.success, children: sizeParts.added }),
2129
2261
  " ",
2130
- /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
2262
+ /* @__PURE__ */ jsx(Text4, { color: colors.error, children: sizeParts.removed })
2131
2263
  ] }) : null,
2132
2264
  unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
2133
- Text3,
2265
+ Text4,
2134
2266
  {
2135
2267
  bold: !spokeLast,
2136
2268
  color: spokeLast ? colors.secondary : colors.accent,
2137
2269
  children: " " + unresolvedLabel
2138
2270
  }
2139
2271
  ) : null,
2140
- showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2141
- ageLabel && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
2142
- /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + agePair.activity }),
2143
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` (${agePair.lifetime})` })
2144
- ] }) : /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + ageLabel }) : null,
2272
+ showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text4, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2273
+ ageLabel2 && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
2274
+ /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: " " + agePair.activity }),
2275
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` (${agePair.lifetime})` })
2276
+ ] }) : /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: " " + ageLabel2 }) : null,
2145
2277
  mergedLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2146
- /* @__PURE__ */ jsx(Text3, { children: " " }),
2278
+ /* @__PURE__ */ jsx(Text4, { children: " " }),
2147
2279
  /* @__PURE__ */ jsx(Pill, { color: MERGED_COLOUR, children: mergedLabel })
2148
2280
  ] }) : null,
2149
2281
  farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2150
- /* @__PURE__ */ jsx(Text3, { children: " " }),
2282
+ /* @__PURE__ */ jsx(Text4, { children: " " }),
2151
2283
  /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2152
2284
  ] }) : null
2153
2285
  ] });
@@ -2195,15 +2327,15 @@ var ActionMenu = ({
2195
2327
  paddingX: 1,
2196
2328
  marginTop: 1,
2197
2329
  children: [
2198
- /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: title }),
2199
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2330
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: title }),
2331
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(32) }),
2200
2332
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
2201
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: i === cursor ? "\u276F " : " " }),
2202
- /* @__PURE__ */ jsx(Text3, { bold: i === cursor, children: a.label }),
2203
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + a.hint })
2333
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: i === cursor ? "\u276F " : " " }),
2334
+ /* @__PURE__ */ jsx(Text4, { bold: i === cursor, children: a.label }),
2335
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " + a.hint })
2204
2336
  ] }, a.label)),
2205
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2206
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2337
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(32) }),
2338
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2207
2339
  ]
2208
2340
  }
2209
2341
  );
@@ -2251,11 +2383,11 @@ var LegendGroup = ({
2251
2383
  width,
2252
2384
  marginRight
2253
2385
  }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, marginRight, children: [
2254
- pinned !== void 0 ? /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2386
+ pinned !== void 0 ? /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2255
2387
  lines.map(
2256
- (line, index) => line.kind === "gap" ? /* @__PURE__ */ jsx(Text3, { children: " " }, index) : line.kind === "title" ? /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: line.text }, index) : /* @__PURE__ */ jsxs(Box, { children: [
2257
- /* @__PURE__ */ jsx(Text3, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2258
- /* @__PURE__ */ jsx(Text3, { wrap: "truncate-end", children: line.row.label })
2388
+ (line, index) => line.kind === "gap" ? /* @__PURE__ */ jsx(Text4, { children: " " }, index) : line.kind === "title" ? /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: line.text }, index) : /* @__PURE__ */ jsxs(Box, { children: [
2389
+ /* @__PURE__ */ jsx(Text4, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2390
+ /* @__PURE__ */ jsx(Text4, { wrap: "truncate-end", children: line.row.label })
2259
2391
  ] }, index)
2260
2392
  )
2261
2393
  ] });
@@ -2289,7 +2421,10 @@ var HelpModal = ({
2289
2421
  ["p", "open repo in new pane"],
2290
2422
  ["u", "unsubscribe from this item"],
2291
2423
  ["x", "remove yourself as reviewer"],
2292
- ...hasJira ? [["t", "Jira: move / open ticket"]] : [],
2424
+ ...hasJira ? [
2425
+ ["t", "Jira: move / open ticket"],
2426
+ ["\u2303K", "launch \xB7 jump to a ticket by key"]
2427
+ ] : [],
2293
2428
  ["/", "search"],
2294
2429
  ["f", "filter by repo"],
2295
2430
  ["r", "refresh"],
@@ -2373,8 +2508,8 @@ var HelpModal = ({
2373
2508
  width: contentWidth + MODAL_CHROME_COLS,
2374
2509
  children: [
2375
2510
  /* @__PURE__ */ jsxs(Box, { children: [
2376
- /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: "Legend" }),
2377
- scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2511
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: "Legend" }),
2512
+ scrollable ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2378
2513
  ] }),
2379
2514
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
2380
2515
  LegendGroup,
@@ -2386,8 +2521,8 @@ var HelpModal = ({
2386
2521
  },
2387
2522
  group[0]?.title ?? index
2388
2523
  )) }),
2389
- tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2390
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
2524
+ tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2525
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
2391
2526
  ]
2392
2527
  }
2393
2528
  );
@@ -2402,13 +2537,13 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2402
2537
  paddingX: 1,
2403
2538
  width: Math.min(COLS, 78),
2404
2539
  children: [
2405
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2406
- /* @__PURE__ */ jsx(Text3, { children: item.title }),
2540
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2541
+ /* @__PURE__ */ jsx(Text4, { children: item.title }),
2407
2542
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2408
- /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: section.heading }),
2409
- section.lines.map((line, i) => /* @__PURE__ */ jsx(Text3, { children: " " + line }, i))
2543
+ /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: section.heading }),
2544
+ section.lines.map((line, i) => /* @__PURE__ */ jsx(Text4, { children: " " + line }, i))
2410
2545
  ] }, section.heading)),
2411
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "esc \xB7 e close" }) })
2546
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "esc \xB7 e close" }) })
2412
2547
  ]
2413
2548
  }
2414
2549
  );
@@ -2434,21 +2569,21 @@ var RepoPicker = ({
2434
2569
  paddingX: 1,
2435
2570
  minWidth: 42,
2436
2571
  children: [
2437
- /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: `Filter by repo (${selected.size} on)` }),
2438
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2572
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: `Filter by repo (${selected.size} on)` }),
2573
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(36) }),
2439
2574
  visible.map((repo, i) => {
2440
2575
  const idx = start + i;
2441
2576
  const on = selected.has(repo);
2442
2577
  const active = idx === cursor;
2443
2578
  return /* @__PURE__ */ jsxs(Box, { children: [
2444
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2445
- /* @__PURE__ */ jsx(Text3, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2446
- /* @__PURE__ */ jsx(Text3, { bold: active, children: repo })
2579
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2580
+ /* @__PURE__ */ jsx(Text4, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2581
+ /* @__PURE__ */ jsx(Text4, { bold: active, children: repo })
2447
2582
  ] }, repo);
2448
2583
  }),
2449
- repos.length > budget ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2450
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2451
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
2584
+ repos.length > budget ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2585
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(36) }),
2586
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
2452
2587
  ]
2453
2588
  }
2454
2589
  );
@@ -2464,6 +2599,7 @@ var BrowseScreen = ({
2464
2599
  login,
2465
2600
  jiraBase,
2466
2601
  jiraKeyRe,
2602
+ jiraSetupHint,
2467
2603
  jiraTransitions,
2468
2604
  onRefresh,
2469
2605
  onActed,
@@ -2482,6 +2618,8 @@ var BrowseScreen = ({
2482
2618
  extensions,
2483
2619
  ciStatusState,
2484
2620
  ciJob,
2621
+ stripState,
2622
+ stripLabel,
2485
2623
  tabHelp,
2486
2624
  origin,
2487
2625
  budget,
@@ -2520,14 +2658,16 @@ var BrowseScreen = ({
2520
2658
  const [helpScroll, setHelpScroll] = useState(0);
2521
2659
  const [helpRange, setHelpRange] = useState({ max: 0, page: 1 });
2522
2660
  const [explain, setExplain] = useState(false);
2661
+ const [palette, setPalette] = useState(null);
2523
2662
  const filterActive = search != null || repoFilter.size > 0;
2524
2663
  const reserveCiRow = ciStatusState != null;
2664
+ const reserveStripRow = stripState != null;
2525
2665
  const ciStatus = ciStatusState?.kind === "ready" ? ciStatusState.status : null;
2526
2666
  const listHeight = Math.max(
2527
2667
  5,
2528
2668
  // -10 rather than -8: the extra 2 are the frame's top and bottom border
2529
2669
  // rows, so the tree never grows taller than the terminal inside the frame.
2530
- rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0)
2670
+ rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0) - (reserveStripRow ? 2 : 0)
2531
2671
  );
2532
2672
  useEffect(() => {
2533
2673
  setCursors((p) => ({ ...p, [activeId]: 0 }));
@@ -2621,13 +2761,17 @@ var BrowseScreen = ({
2621
2761
  return out;
2622
2762
  }, [localSections, transients]);
2623
2763
  const [railOpen, setRailOpen] = useState(false);
2624
- const showRail = railOpen && !!sidebar;
2764
+ const rails = railsOf(sidebar);
2765
+ const hasRail = rails.length > 0;
2766
+ const railTitle = rails.map((s) => s.title.toLowerCase()).join(" \xB7 ");
2767
+ const showRail = railOpen && hasRail;
2625
2768
  const [railFocus, setRailFocus] = useState(false);
2626
2769
  const [railCursor, setRailCursor] = useState(0);
2627
- const railRows = sidebar?.rows ?? [];
2770
+ const railRows = railRowsOf(sidebar);
2628
2771
  const railActive = showRail && railFocus && railRows.length > 0;
2629
2772
  const railAt = Math.min(railCursor, Math.max(0, railRows.length - 1));
2630
2773
  const peel = () => {
2774
+ if (palette !== null) return setPalette(null), true;
2631
2775
  if (help) return setHelp(false), true;
2632
2776
  if (explain) return setExplain(false), true;
2633
2777
  if (repoPicker) return setRepoPicker(false), true;
@@ -2645,8 +2789,8 @@ var BrowseScreen = ({
2645
2789
  };
2646
2790
  });
2647
2791
  useEffect(() => {
2648
- onTyping?.(searchInput);
2649
- }, [searchInput, onTyping]);
2792
+ onTyping?.(searchInput || palette !== null);
2793
+ }, [searchInput, palette, onTyping]);
2650
2794
  const railCols = railWidth(COLS);
2651
2795
  const listCols = showRail ? COLS - railCols : COLS;
2652
2796
  const [sparkFrame, setSparkFrame] = useState(0);
@@ -2734,7 +2878,12 @@ var BrowseScreen = ({
2734
2878
  lastKeyAt.current = Date.now();
2735
2879
  if (idleFor >= PULSE_IDLE_MS && sparkKey) setPulseArm((n) => n + 1);
2736
2880
  if (hidden) return;
2881
+ if (key.ctrl && input === "k") {
2882
+ setPalette((open) => open === null ? "" : null);
2883
+ return;
2884
+ }
2737
2885
  if (key.ctrl || key.meta) return;
2886
+ if (palette !== null) return;
2738
2887
  if (help) {
2739
2888
  if (helpRange.max > 0) {
2740
2889
  const step2 = (n) => setHelpScroll((s) => Math.min(helpRange.max, Math.max(0, s + n)));
@@ -2792,7 +2941,7 @@ var BrowseScreen = ({
2792
2941
  setRepoPicker(true);
2793
2942
  return;
2794
2943
  }
2795
- if (input === "i" && sidebar) {
2944
+ if (input === "i" && hasRail) {
2796
2945
  setRailOpen((open) => {
2797
2946
  if (open) setRailFocus(false);
2798
2947
  return !open;
@@ -2833,6 +2982,13 @@ var BrowseScreen = ({
2833
2982
  }
2834
2983
  return;
2835
2984
  }
2985
+ if (input === "c") {
2986
+ const row = railRows[railAt];
2987
+ if (!row?.url) return;
2988
+ clipboard(row.url);
2989
+ showFlash(`\u2713 Copied URL for ${row.key}`);
2990
+ return;
2991
+ }
2836
2992
  if (input !== "r" && input !== "q" && !key.leftArrow && !key.rightArrow)
2837
2993
  return;
2838
2994
  }
@@ -3091,9 +3247,10 @@ var BrowseScreen = ({
3091
3247
  // reach a row is worse than a short footer: it names a key that does
3092
3248
  // nothing where you are standing.
3093
3249
  [
3094
- ["\u2191\u2193", "initiative"],
3250
+ ["\u2191\u2193", "row"],
3095
3251
  ["\u21B5", "open"],
3096
3252
  ["o", "browser"],
3253
+ ["c", "copy url"],
3097
3254
  ["\u21E5/esc", "back to list"],
3098
3255
  ["?", "help"],
3099
3256
  ["q", "quit"]
@@ -3111,17 +3268,101 @@ var BrowseScreen = ({
3111
3268
  ["\u2190\u2192", "tab"],
3112
3269
  ["\u21B5/d", "open"],
3113
3270
  ["m", "actions"],
3271
+ // Only where Jira is configured: without it the launcher can only say
3272
+ // so, and a footer key that opens an apology is a broken feature.
3273
+ ...jiraBase ? [["\u2303K", "launch"]] : [],
3114
3274
  // Advertised only where it does something, and named for what it
3115
3275
  // shows rather than for the furniture: nobody wants "a sidebar".
3116
- ...showRail ? [["\u21E5", sidebar.title.toLowerCase()]] : sidebar ? [["i", sidebar.title.toLowerCase()]] : [],
3276
+ ...showRail ? [["\u21E5", railTitle]] : hasRail ? [["i", railTitle]] : [],
3117
3277
  ["?", "help"],
3118
3278
  ["q", "quit"]
3119
3279
  ];
3120
3280
  const matchCount = section.items.filter(
3121
3281
  (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
3122
3282
  ).length;
3283
+ const launcher = useMemo(() => {
3284
+ const run = /* @__PURE__ */ new Map();
3285
+ if (palette === null)
3286
+ return { items: [], run, message: void 0 };
3287
+ const query = palette.trim();
3288
+ const ticketKey = jiraKeyRe ? query.toUpperCase().match(jiraKeyRe)?.[0] : void 0;
3289
+ const commands = [];
3290
+ const rich = (key, where) => /* @__PURE__ */ jsxs(Text4, { children: [
3291
+ "Open ",
3292
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: key }),
3293
+ " ",
3294
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: where })
3295
+ ] });
3296
+ const labels = /* @__PURE__ */ new Map();
3297
+ if (ticketKey && jiraBase) {
3298
+ const row = {
3299
+ kind: "task",
3300
+ key: ticketKey,
3301
+ ticket: ticketKey,
3302
+ summary: "",
3303
+ url: `${jiraBase}/${ticketKey}`,
3304
+ status: "",
3305
+ age: ""
3306
+ };
3307
+ if (drillExtensionFor(row, extensions)) {
3308
+ commands.push({
3309
+ id: "launcher:here",
3310
+ title: `Open ${ticketKey} here`,
3311
+ run: () => void openDrillView(row)
3312
+ });
3313
+ labels.set("launcher:here", rich(ticketKey, "here"));
3314
+ }
3315
+ commands.push({
3316
+ id: "launcher:jira",
3317
+ title: `Open ${ticketKey} in Jira`,
3318
+ run: () => openInJira(jiraBase, ticketKey, showFlash)
3319
+ });
3320
+ labels.set("launcher:jira", rich(ticketKey, "in Jira"));
3321
+ }
3322
+ const target = {
3323
+ ...extensionTargetFor(activeItem),
3324
+ query: palette,
3325
+ ticketKey
3326
+ };
3327
+ for (const ext of extensions ?? []) {
3328
+ for (const command of ext.commands?.(target) ?? []) {
3329
+ commands.push({ ...command, id: `${ext.id}:${command.id}` });
3330
+ }
3331
+ }
3332
+ for (const command of commands) run.set(command.id, command.run);
3333
+ const message = !jiraBase ? `Jira not configured${jiraSetupHint ? ` \xB7 ${jiraSetupHint}` : ""}` : query && !ticketKey ? `no ticket matches "${query}"` : void 0;
3334
+ const items = commands.map((command) => ({
3335
+ id: command.id,
3336
+ title: command.title,
3337
+ group: command.group,
3338
+ label: labels.get(command.id)
3339
+ }));
3340
+ return { items, run, message };
3341
+ }, [palette, activeItem, extensions, jiraBase, jiraKeyRe, jiraSetupHint]);
3123
3342
  if (hidden) return null;
3124
- const overlay = help ? /* @__PURE__ */ jsx(
3343
+ const overlay = palette !== null ? /* @__PURE__ */ jsx(
3344
+ CommandPalette,
3345
+ {
3346
+ items: launcher.items,
3347
+ query: palette,
3348
+ onQueryChange: setPalette,
3349
+ onSelect: (id) => {
3350
+ const run = launcher.run.get(id);
3351
+ setPalette(null);
3352
+ void run?.();
3353
+ },
3354
+ onClose: () => setPalette(null),
3355
+ message: launcher.message,
3356
+ placeholder: "ticket key\u2026",
3357
+ width: Math.min(60, Math.max(30, listCols - 2)),
3358
+ maxRows: Math.max(3, listHeight - 6),
3359
+ hints: [
3360
+ ["\u2191\u2193", "move"],
3361
+ ["\u23CE", "open"],
3362
+ ["esc", "close"]
3363
+ ]
3364
+ }
3365
+ ) : help ? /* @__PURE__ */ jsx(
3125
3366
  HelpModal,
3126
3367
  {
3127
3368
  extensions,
@@ -3138,7 +3379,14 @@ var BrowseScreen = ({
3138
3379
  selected: repoFilter,
3139
3380
  cursor: Math.min(repoCursor, Math.max(0, allRepos.length - 1))
3140
3381
  }
3141
- ) : menu.actions && activeItem && activeItem.kind !== "repo-header" ? /* @__PURE__ */ jsx(ActionMenu, { item: activeItem, actions: menu.actions, cursor: menu.cursor }) : null;
3382
+ ) : menu.actions && activeItem && activeItem.kind !== "repo-header" ? /* @__PURE__ */ jsx(
3383
+ ActionMenu,
3384
+ {
3385
+ item: activeItem,
3386
+ actions: menu.actions,
3387
+ cursor: menu.cursor
3388
+ }
3389
+ ) : null;
3142
3390
  return /* @__PURE__ */ jsxs(
3143
3391
  Box,
3144
3392
  {
@@ -3166,9 +3414,17 @@ var BrowseScreen = ({
3166
3414
  }
3167
3415
  ),
3168
3416
  ciStatusState ? /* @__PURE__ */ jsx(CiStatusLine, { state: ciStatusState, job: ciJob }) : null,
3417
+ stripState ? /* @__PURE__ */ jsx(
3418
+ StatusStripLine,
3419
+ {
3420
+ strip: stripState.kind === "ready" ? stripState.strip : void 0,
3421
+ label: stripLabel,
3422
+ width: COLS
3423
+ }
3424
+ ) : null,
3169
3425
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3170
- /* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
3171
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
3426
+ /* @__PURE__ */ jsx(Text4, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
3427
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " r refreshes anyway" })
3172
3428
  ] }) : null,
3173
3429
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
3174
3430
  Tabs,
@@ -3204,14 +3460,14 @@ var BrowseScreen = ({
3204
3460
  }
3205
3461
  ) }),
3206
3462
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3207
- /* @__PURE__ */ jsx(Text3, { color: colors.info, children: " / " }),
3208
- /* @__PURE__ */ jsx(Text3, { children: search }),
3209
- searchInput ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
3210
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3463
+ /* @__PURE__ */ jsx(Text4, { color: colors.info, children: " / " }),
3464
+ /* @__PURE__ */ jsx(Text4, { children: search }),
3465
+ searchInput ? /* @__PURE__ */ jsx(Text4, { color: colors.info, children: "\u258F" }) : null,
3466
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3211
3467
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3212
- /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
3213
- /* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
3214
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " f edit \xB7 esc clear" })
3468
+ /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: " \u25C9 " }),
3469
+ /* @__PURE__ */ jsx(Text4, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
3470
+ /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " f edit \xB7 esc clear" })
3215
3471
  ] }) : null,
3216
3472
  /* @__PURE__ */ jsxs(Box, { children: [
3217
3473
  /* @__PURE__ */ jsxs(
@@ -3249,7 +3505,7 @@ var BrowseScreen = ({
3249
3505
  },
3250
3506
  `${viewStart + i}:${rowKey(item)}`
3251
3507
  )) }),
3252
- hasMore && /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
3508
+ hasMore && /* @__PURE__ */ jsxs(Text4, { dimColor: true, children: [
3253
3509
  " ",
3254
3510
  "\u2193 ",
3255
3511
  section.items.length - viewStart - visibleCount,
@@ -3271,10 +3527,10 @@ var BrowseScreen = ({
3271
3527
  ]
3272
3528
  }
3273
3529
  ),
3274
- showRail && sidebar ? /* @__PURE__ */ jsx(
3530
+ showRail ? /* @__PURE__ */ jsx(
3275
3531
  SidePanel,
3276
3532
  {
3277
- sidebar,
3533
+ sidebar: rails,
3278
3534
  liveLabel,
3279
3535
  width: railCols,
3280
3536
  height: listHeight,
@@ -3283,7 +3539,7 @@ var BrowseScreen = ({
3283
3539
  }
3284
3540
  ) : null
3285
3541
  ] }),
3286
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3542
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text4, { color: colors.success, children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3287
3543
  ]
3288
3544
  }
3289
3545
  );
@@ -3298,7 +3554,7 @@ var NoRowsScreen = ({
3298
3554
  });
3299
3555
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3300
3556
  /* @__PURE__ */ jsx(StatusMessage, { variant: reason === "empty" ? "info" : "error", children: reason === "empty" ? "Nothing open." : `Fetch failed \u2014 ${detail ?? "no reason reported"}` }),
3301
- reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: detail }) }) : null,
3557
+ reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text4, { dimColor: true, children: detail }) }) : null,
3302
3558
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
3303
3559
  FooterHints,
3304
3560
  {
@@ -3323,11 +3579,15 @@ var App = ({
3323
3579
  origin,
3324
3580
  jiraBase,
3325
3581
  jiraKeyRe,
3582
+ jiraSetupHint,
3326
3583
  jiraTransitions,
3327
3584
  hasCiStatus,
3328
3585
  ciJob,
3329
3586
  ciFetcher,
3330
3587
  ciPollMs = 6e4,
3588
+ stripFetcher,
3589
+ stripLabel,
3590
+ stripPollMs = 3e5,
3331
3591
  watchPath,
3332
3592
  watchDebounceMs = 400,
3333
3593
  // Zero by default, deliberately. A package that adds randomness to its own
@@ -3371,6 +3631,15 @@ var App = ({
3371
3631
  const next = toCiStatusState(status);
3372
3632
  return sameCiStatusState(prev, next) ? prev : next;
3373
3633
  });
3634
+ const hasStrip = !!stripFetcher;
3635
+ const [stripState, setStripState] = useState({
3636
+ kind: "loading"
3637
+ });
3638
+ const applyStrip = (strip) => setStripState((prev) => {
3639
+ const next = { kind: "ready", strip };
3640
+ if (prev.kind !== "ready") return next;
3641
+ return stripSignature(prev.strip) === stripSignature(strip) ? prev : next;
3642
+ });
3374
3643
  const displayedKey = useRef("");
3375
3644
  const displayedSections = useRef([]);
3376
3645
  const [transients, setTransients] = useState(NO_TRANSIENTS);
@@ -3453,13 +3722,10 @@ var App = ({
3453
3722
  const running = [...heldSince].filter(([tab]) => held.has(tab));
3454
3723
  if (running.length === 0) return;
3455
3724
  const remaining = (at) => at + TRANSIT_HOLD_MS - Date.now();
3456
- const timer = setTimeout(
3457
- () => {
3458
- for (const [tab, at] of running)
3459
- if (remaining(at) <= 0) settleTab(tab, transients);
3460
- },
3461
- Math.max(0, Math.min(...running.map(([, at]) => remaining(at))))
3462
- );
3725
+ const timer = setTimeout(() => {
3726
+ for (const [tab, at] of running)
3727
+ if (remaining(at) <= 0) settleTab(tab, transients);
3728
+ }, Math.max(0, Math.min(...running.map(([, at]) => remaining(at)))));
3463
3729
  return () => clearTimeout(timer);
3464
3730
  }, [transients, heldSince, state.phase]);
3465
3731
  const pendingSummary = useMemo(
@@ -3567,6 +3833,23 @@ var App = ({
3567
3833
  clearInterval(id);
3568
3834
  };
3569
3835
  }, [hasCiStatus, ciFetcher, ciPollMs]);
3836
+ useEffect(() => {
3837
+ if (!stripFetcher) return;
3838
+ let live = true;
3839
+ const poll = () => {
3840
+ stripFetcher().then((strip) => {
3841
+ if (!live) return;
3842
+ applyStrip(strip);
3843
+ }).catch(() => {
3844
+ });
3845
+ };
3846
+ poll();
3847
+ const id = setInterval(poll, stripPollMs);
3848
+ return () => {
3849
+ live = false;
3850
+ clearInterval(id);
3851
+ };
3852
+ }, [stripFetcher, stripPollMs]);
3570
3853
  useEffect(() => {
3571
3854
  if (!watchPath) return;
3572
3855
  const dir = dirname(watchPath);
@@ -3651,6 +3934,14 @@ var App = ({
3651
3934
  }
3652
3935
  ),
3653
3936
  hasCiStatus ? /* @__PURE__ */ jsx(CiStatusLine, { state: ciStatusState, job: ciJob }) : null,
3937
+ hasStrip ? /* @__PURE__ */ jsx(
3938
+ StatusStripLine,
3939
+ {
3940
+ strip: stripState.kind === "ready" ? stripState.strip : void 0,
3941
+ label: stripLabel,
3942
+ width: COLS
3943
+ }
3944
+ ) : null,
3654
3945
  /* @__PURE__ */ jsx(
3655
3946
  Box,
3656
3947
  {
@@ -3711,6 +4002,7 @@ var App = ({
3711
4002
  login: state.login,
3712
4003
  jiraBase,
3713
4004
  jiraKeyRe,
4005
+ jiraSetupHint,
3714
4006
  jiraTransitions,
3715
4007
  onRefresh: applyOrRefresh,
3716
4008
  onActed,
@@ -3736,6 +4028,8 @@ var App = ({
3736
4028
  liveLabel,
3737
4029
  ciStatusState: hasCiStatus ? ciStatusState : void 0,
3738
4030
  ciJob,
4031
+ stripState: hasStrip ? stripState : void 0,
4032
+ stripLabel,
3739
4033
  tabHelp,
3740
4034
  onOpenPr: (item) => setState({
3741
4035
  phase: "pr",
@@ -3790,4 +4084,4 @@ var matchesFilter = (repo, filter) => {
3790
4084
  };
3791
4085
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3792
4086
 
3793
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railWidth, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
4087
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };