@kud/gh-ink 0.36.0 → 0.38.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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { PrComments, PrHealthData, PrCheck, Health } from '@kud/gh';
3
- import { StyledLine } from '@kud/ink-ui';
3
+ import { StyledLine, PillVariant } from '@kud/ink-ui';
4
4
 
5
5
  type CommentsPanelProps = {
6
6
  repo: string;
@@ -24,6 +24,58 @@ type HealthPanelProps = {
24
24
  };
25
25
  declare const HealthPanel: ({ repo, number, data, error, reload, onOpenCheck, onMerged, }: HealthPanelProps) => React.JSX.Element;
26
26
 
27
+ /**
28
+ * One initiative in the rail — a container of work rather than a unit of it.
29
+ *
30
+ * Deliberately not a `TaskRow`. The list answers "what is in front of me today"
31
+ * and prices every row by stage; this answers "what am I building, over months",
32
+ * where a stage means nothing because an initiative's stage is only ever an
33
+ * aggregate of its children's. Sharing a shape would have meant carrying a
34
+ * `status` neither side agrees on.
35
+ */
36
+ type SidebarRow = {
37
+ /** Stable identity, and the left column: a ticket key, an id, a slug. */
38
+ key: string;
39
+ /** What it is, in words. Never omitted — a key alone cannot be read. */
40
+ label: string;
41
+ /**
42
+ * How much is moving under it right now. Absent draws nothing rather than a
43
+ * zero: "no live work" and "we did not count" are different claims, and a
44
+ * hard 0 asserts the first from evidence for neither.
45
+ */
46
+ live?: number;
47
+ /** Something under it is waiting on you. */
48
+ wantsYou?: boolean;
49
+ };
50
+ type Sidebar = {
51
+ /** Heading for the rail. The host's vocabulary, never ours. */
52
+ title: string;
53
+ rows: SidebarRow[];
54
+ };
55
+ /**
56
+ * The rail's width, INCLUDING its one-column gutter. Exported because the list
57
+ * beside it has to shrink by exactly this much: every row in that list truncates
58
+ * against a budget, and a budget that does not know the rail is there overflows
59
+ * by the rail's whole width — which in a frame sized to fill the terminal
60
+ * scrolls the panel rather than clipping a row.
61
+ */
62
+ declare const SIDEBAR_COLS = 30;
63
+ /**
64
+ * A right-hand rail of initiatives, standing beside the list.
65
+ *
66
+ * Two lines per row rather than one, because both halves are load-bearing and
67
+ * neither survives the other being cut: a key with no words cannot be read at a
68
+ * glance, and words with no key cannot be opened. Thirty columns is not enough
69
+ * for both on one line, so they stack.
70
+ *
71
+ * Presentational, like every other row renderer here — it takes no keyboard and
72
+ * holds no cursor. Whether the rail is shown at all is the host's state.
73
+ */
74
+ declare const SidePanel: ({ sidebar, height, }: {
75
+ sidebar: Sidebar;
76
+ height?: number;
77
+ }) => React.JSX.Element;
78
+
27
79
  declare const renderMarkdown: (raw: string, width: number, fileLink?: (path: string, line?: number) => string) => StyledLine[];
28
80
 
29
81
  declare const healthDisplay: Record<Health, {
@@ -204,6 +256,20 @@ type TaskRow = {
204
256
  * separately instead of being smuggled past the truncation maths.
205
257
  */
206
258
  note?: string;
259
+ /**
260
+ * A category this row BELONGS to — `epic`, `blocked`, `spike` — drawn as a
261
+ * filled pill after the summary.
262
+ *
263
+ * Deliberately not a second spelling of `note`, which the epic marker used to
264
+ * borrow. The two want opposite weights: a pill says the word itself is the
265
+ * information, while `note` is for a reference the reader follows — a parent
266
+ * ticket key, a source. Filling a breadcrumb gives it a weight it has not
267
+ * earned, and a row can legitimately carry both (a story under someone else's
268
+ * epic shows its parent's key AND, one day, a status of its own).
269
+ */
270
+ pill?: string;
271
+ /** Which fill the pill takes. See `@kud/ink-ui`'s `PillVariant`. */
272
+ pillVariant?: PillVariant;
207
273
  /**
208
274
  * The Jira issue key behind this row, when one exists. Its PRESENCE is what
209
275
  * turns on the ticket affordances — ↵ opens a menu led by `jira issue view`,
@@ -479,6 +545,15 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
479
545
  * can decline to spend the last of it on a refresh nobody requested.
480
546
  */
481
547
  budget?: InboxBudget;
548
+ /**
549
+ * A rail of containers to stand beside the list — initiatives, epics,
550
+ * whatever this host groups its work under. Omit it and no rail exists: no
551
+ * key, no hint, no columns spent.
552
+ *
553
+ * Part of the fetch result rather than a static prop because it is data, and
554
+ * data that goes stale the same way the rows do.
555
+ */
556
+ sidebar?: Sidebar;
482
557
  }>;
483
558
  cacheKey?: string;
484
559
  title?: string;
@@ -601,4 +676,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
601
676
  */
602
677
  declare const parsePatterns: (value: string) => string[];
603
678
 
604
- export { type Action, ActionMenu, type AnyItem, App, COLS, type CiStatus, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, type GHDetail, type GHItem, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxConfig, type InboxExtension, type JiraTransition, type OriginSplit, PIN_MARK, type RepoFilter, type RepoHeader, type RepoProfile, type Section, type ShowLess, type ShowMore, type Standing, type SubgroupHeader, type TaskRow, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
679
+ export { type Action, ActionMenu, type AnyItem, App, COLS, type CiStatus, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, type GHDetail, type GHItem, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxConfig, type InboxExtension, type JiraTransition, type OriginSplit, PIN_MARK, type RepoFilter, type RepoHeader, type RepoProfile, SIDEBAR_COLS, type Section, type ShowLess, type ShowMore, SidePanel, type Sidebar, type SidebarRow, type Standing, type SubgroupHeader, type TaskRow, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
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, LoadingScreen, StatusMessage, FooterHints, useListCursor, Tabs } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, LoadingScreen, StatusMessage, FooterHints, useListCursor, Tabs, 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';
@@ -583,6 +583,37 @@ var HealthPanel = ({
583
583
  ] }) }) : note ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.success, children: " " + note }) }) : null
584
584
  ] });
585
585
  };
586
+ var ACCENT = "#FF8700";
587
+ var SIDEBAR_COLS = 30;
588
+ var GUTTER = 1;
589
+ var CONTENT = SIDEBAR_COLS - GUTTER;
590
+ var truncate = (text, max) => [...text].length <= max ? text : [...text].slice(0, Math.max(0, max - 1)).join("") + "\u2026";
591
+ var SidePanel = ({
592
+ sidebar,
593
+ height
594
+ }) => /* @__PURE__ */ jsxs(
595
+ Box,
596
+ {
597
+ flexDirection: "column",
598
+ width: SIDEBAR_COLS,
599
+ flexShrink: 0,
600
+ height,
601
+ children: [
602
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, paddingLeft: GUTTER, children: [
603
+ /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: "\xBB " }),
604
+ /* @__PURE__ */ jsx(Text, { bold: true, children: sidebar.title })
605
+ ] }),
606
+ sidebar.rows.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: GUTTER + 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "nothing open" }) }) : sidebar.rows.map((row) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
607
+ /* @__PURE__ */ jsxs(Box, { paddingLeft: GUTTER, children: [
608
+ /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
609
+ /* @__PURE__ */ jsx(Text, { color: ACCENT, children: row.key }),
610
+ row.live !== void 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${row.live} live` }) : null
611
+ ] }),
612
+ /* @__PURE__ */ jsx(Box, { paddingLeft: GUTTER + 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncate(row.label, CONTENT - 2) }) })
613
+ ] }, row.key))
614
+ ]
615
+ }
616
+ );
586
617
  var healthDisplay = {
587
618
  none: { glyph: " ", color: colors.muted },
588
619
  draft: { glyph: "~", color: colors.muted },
@@ -1215,7 +1246,7 @@ var withHeaders = (items, idx) => {
1215
1246
  start--;
1216
1247
  return start;
1217
1248
  };
1218
- var truncate = (str, max) => {
1249
+ var truncate2 = (str, max) => {
1219
1250
  if (str.length <= max) return str;
1220
1251
  const half = Math.floor((max - 1) / 2);
1221
1252
  return `${str.slice(0, half)}\u2026${str.slice(-half)}`;
@@ -1405,7 +1436,7 @@ var COLS = (process.stdout.columns ?? 120) - FRAME_CHROME_COLS;
1405
1436
  var MODAL_CHROME_COLS = 2 + 1 * 2;
1406
1437
  var OVERLAY_BG = "#22222e";
1407
1438
  var DimContext = createContext(false);
1408
- var Text2 = ({ children, ...props }) => {
1439
+ var Text3 = ({ children, ...props }) => {
1409
1440
  const dimmed = useContext(DimContext);
1410
1441
  return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, bold: false, dimColor: true, children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
1411
1442
  };
@@ -1784,13 +1815,13 @@ var InboxHeader = ({
1784
1815
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - budgetSeg.length - statusSeg.length
1785
1816
  );
1786
1817
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1787
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: brand }),
1788
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: countSeg }),
1789
- userSeg ? /* @__PURE__ */ jsx(Text2, { children: userSeg }) : null,
1790
- scopeLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: workLabel }) : null,
1791
- budgetLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: budgetCritical ? "#FF5F5F" : "#FF8700", children: budgetSeg }) : null,
1818
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: brand }),
1819
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: countSeg }),
1820
+ userSeg ? /* @__PURE__ */ jsx(Text3, { children: userSeg }) : null,
1821
+ scopeLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: workLabel }) : null,
1822
+ budgetLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: budgetCritical ? "#FF5F5F" : "#FF8700", children: budgetSeg }) : null,
1792
1823
  statusText ? /* @__PURE__ */ jsx(
1793
- Text2,
1824
+ Text3,
1794
1825
  {
1795
1826
  color: statusColor,
1796
1827
  dimColor: !statusColor,
@@ -1798,7 +1829,7 @@ var InboxHeader = ({
1798
1829
  children: statusSeg
1799
1830
  }
1800
1831
  ) : null,
1801
- /* @__PURE__ */ jsx(Text2, { color: "cyan", dimColor: true, children: "\u254C".repeat(fill) })
1832
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", dimColor: true, children: "\u254C".repeat(fill) })
1802
1833
  ] });
1803
1834
  };
1804
1835
  var toCiStatusState = (status) => status ? { kind: "ready", status } : { kind: "error" };
@@ -1821,24 +1852,24 @@ var CiStatusLine = ({
1821
1852
  const name = job ?? "ci";
1822
1853
  if (state.kind === "loading")
1823
1854
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1824
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " \xB7 " }),
1825
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `${name} loading\u2026` })
1855
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " \xB7 " }),
1856
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} loading\u2026` })
1826
1857
  ] });
1827
1858
  if (state.kind === "error")
1828
1859
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1829
- /* @__PURE__ */ jsx(Text2, { color: "red", bold: true, children: " \u2717 " }),
1830
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `${name} no build / not configured` })
1860
+ /* @__PURE__ */ jsx(Text3, { color: "red", bold: true, children: " \u2717 " }),
1861
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} no build / not configured` })
1831
1862
  ] });
1832
1863
  const { status } = state;
1833
1864
  const [, color] = jenkinsResultDisplay(status.result, status.building);
1834
1865
  const label = status.building ? "BUILDING" : status.result;
1835
1866
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1836
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " }),
1837
- /* @__PURE__ */ jsx(Text2, { color, bold: true, children: "\u25CF " }),
1838
- /* @__PURE__ */ jsx(Text2, { bold: true, children: status.job }),
1839
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label }),
1840
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " #" + status.buildNumber }),
1841
- status.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + status.age }) : null
1867
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " }),
1868
+ /* @__PURE__ */ jsx(Text3, { color, bold: true, children: "\u25CF " }),
1869
+ /* @__PURE__ */ jsx(Text3, { bold: true, children: status.job }),
1870
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + label }),
1871
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: " #" + status.buildNumber }),
1872
+ status.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + status.age }) : null
1842
1873
  ] });
1843
1874
  };
1844
1875
  var FENCE_COLS = 46;
@@ -1851,10 +1882,10 @@ var RepoHeaderRow = ({
1851
1882
  const fill = Math.max(4, FENCE_COLS - label.length);
1852
1883
  const rule = "\u2500".repeat(fill);
1853
1884
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1854
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1855
- /* @__PURE__ */ jsx(Text2, { dimColor: !active, children: "\u2500\u2500 " }),
1856
- /* @__PURE__ */ jsx(Text2, { dimColor: !active, bold: active, children: repo }),
1857
- /* @__PURE__ */ jsx(Text2, { dimColor: !active, children: " " + rule })
1885
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1886
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: "\u2500\u2500 " }),
1887
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, bold: active, children: repo }),
1888
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
1858
1889
  ] });
1859
1890
  };
1860
1891
  var MERGED_HOLD_MS = 5e3;
@@ -1902,29 +1933,30 @@ var ItemRow = ({
1902
1933
  leaving,
1903
1934
  prefix = "",
1904
1935
  parent,
1905
- sparkFrame = 0
1936
+ sparkFrame = 0,
1937
+ cols = COLS
1906
1938
  }) => {
1907
1939
  const transient = leaving ? "out" : refreshMark;
1908
1940
  if (item.kind === "repo-header")
1909
1941
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
1910
1942
  if (item.kind === "subgroup-header")
1911
1943
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1912
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: " \xBB " }),
1913
- /* @__PURE__ */ jsx(Text2, { bold: true, children: item.label })
1944
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: " \xBB " }),
1945
+ /* @__PURE__ */ jsx(Text3, { bold: true, children: item.label })
1914
1946
  ] });
1915
1947
  if (item.kind === "show-more")
1916
1948
  return /* @__PURE__ */ jsxs(Box, { children: [
1917
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1918
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1919
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1920
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `+${item.hidden.length} more` })
1949
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1950
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1951
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1952
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `+${item.hidden.length} more` })
1921
1953
  ] });
1922
1954
  if (item.kind === "show-less")
1923
1955
  return /* @__PURE__ */ jsxs(Box, { children: [
1924
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1925
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1926
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1927
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "show less" })
1956
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1957
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1958
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1959
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "show less" })
1928
1960
  ] });
1929
1961
  if (item.kind === "task") {
1930
1962
  const note = item.note ?? "";
@@ -1932,25 +1964,29 @@ var ItemRow = ({
1932
1964
  const transitLabel2 = transient ? TRANSIT_LABEL[transient] : "";
1933
1965
  const titleMax2 = Math.max(
1934
1966
  20,
1935
- COLS - item.key.length - note.length - transitLabel2.length - prefix.length - 12
1967
+ cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - transitLabel2.length - prefix.length - 12
1936
1968
  );
1937
1969
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1938
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1939
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1940
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
1941
- /* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1942
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
1970
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1971
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1972
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
1973
+ /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1974
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: active, children: item.key + " " }),
1943
1975
  /* @__PURE__ */ jsx(
1944
- Text2,
1976
+ Text3,
1945
1977
  {
1946
1978
  bold: active || !!transient && isArrival(transient),
1947
1979
  dimColor: !!transient && isDeparture(transient),
1948
1980
  strikethrough: transient === "out",
1949
- children: truncate(item.summary, titleMax2)
1981
+ children: truncate2(item.summary, titleMax2)
1950
1982
  }
1951
1983
  ),
1952
- note ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
1953
- transitLabel2 ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel2 }) : null
1984
+ item.pill ? /* @__PURE__ */ jsxs(Fragment, { children: [
1985
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
1986
+ /* @__PURE__ */ jsx(Pill, { variant: item.pillVariant ?? "muted", children: item.pill })
1987
+ ] }) : null,
1988
+ note ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${note}` }) : null,
1989
+ transitLabel2 ? /* @__PURE__ */ jsx(Text3, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel2 }) : null
1954
1990
  ] });
1955
1991
  }
1956
1992
  const { glyph: healthIcon, color: healthColor2 } = healthDisplay[item.health];
@@ -1973,28 +2009,28 @@ var ItemRow = ({
1973
2009
  ].filter(Boolean).join(" ");
1974
2010
  const repoLabel = depthOf(item) > 0 ? item.repo : "";
1975
2011
  const fixedWidth = 2 + prefix.length + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
1976
- const titleMax = Math.max(20, COLS - fixedWidth);
2012
+ const titleMax = Math.max(20, cols - fixedWidth);
1977
2013
  return /* @__PURE__ */ jsxs(Box, { children: [
1978
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1979
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1980
- /* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
1981
- /* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1982
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
2014
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
2015
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2016
+ /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
2017
+ /* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
2018
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: numStr }),
1983
2019
  /* @__PURE__ */ jsx(
1984
- Text2,
2020
+ Text3,
1985
2021
  {
1986
2022
  bold: active || !!transient && isArrival(transient),
1987
2023
  dimColor: !!transient && isDeparture(transient),
1988
2024
  strikethrough: transient === "out",
1989
- children: truncate(item.title, titleMax) + " "
2025
+ children: truncate2(item.title, titleMax) + " "
1990
2026
  }
1991
2027
  ),
1992
- repoLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: repoLabel }) : null,
1993
- unresolvedLabel ? /* @__PURE__ */ jsx(Text2, { bold: !spokeLast, color: spokeLast ? "#888888" : "#FF8700", children: " " + unresolvedLabel }) : null,
1994
- showAuthor ? /* @__PURE__ */ jsx(Text2, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1995
- ageLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + ageLabel }) : null,
1996
- mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null,
1997
- transitLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel }) : null
2028
+ repoLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: repoLabel }) : null,
2029
+ unresolvedLabel ? /* @__PURE__ */ jsx(Text3, { bold: !spokeLast, color: spokeLast ? "#888888" : "#FF8700", children: " " + unresolvedLabel }) : null,
2030
+ showAuthor ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2031
+ ageLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + ageLabel }) : null,
2032
+ mergedLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null,
2033
+ transitLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel }) : null
1998
2034
  ] });
1999
2035
  };
2000
2036
  var useActionMenu = () => {
@@ -2040,15 +2076,15 @@ var ActionMenu = ({
2040
2076
  paddingX: 1,
2041
2077
  marginTop: 1,
2042
2078
  children: [
2043
- /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: title }),
2044
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(32) }),
2079
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: title }),
2080
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2045
2081
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
2046
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: i === cursor ? "\u276F " : " " }),
2047
- /* @__PURE__ */ jsx(Text2, { bold: i === cursor, children: a.label }),
2048
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + a.hint })
2082
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: i === cursor ? "\u276F " : " " }),
2083
+ /* @__PURE__ */ jsx(Text3, { bold: i === cursor, children: a.label }),
2084
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + a.hint })
2049
2085
  ] }, a.label)),
2050
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(32) }),
2051
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2086
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2087
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2052
2088
  ]
2053
2089
  }
2054
2090
  );
@@ -2096,11 +2132,11 @@ var LegendGroup = ({
2096
2132
  width,
2097
2133
  marginRight
2098
2134
  }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, marginRight, children: [
2099
- pinned !== void 0 ? /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2135
+ pinned !== void 0 ? /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2100
2136
  lines.map(
2101
- (line, index) => line.kind === "gap" ? /* @__PURE__ */ jsx(Text2, { children: " " }, index) : line.kind === "title" ? /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: line.text }, index) : /* @__PURE__ */ jsxs(Box, { children: [
2102
- /* @__PURE__ */ jsx(Text2, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2103
- /* @__PURE__ */ jsx(Text2, { wrap: "truncate-end", children: line.row.label })
2137
+ (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: [
2138
+ /* @__PURE__ */ jsx(Text3, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2139
+ /* @__PURE__ */ jsx(Text3, { wrap: "truncate-end", children: line.row.label })
2104
2140
  ] }, index)
2105
2141
  )
2106
2142
  ] });
@@ -2218,8 +2254,8 @@ var HelpModal = ({
2218
2254
  width: contentWidth + MODAL_CHROME_COLS,
2219
2255
  children: [
2220
2256
  /* @__PURE__ */ jsxs(Box, { children: [
2221
- /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: "Legend" }),
2222
- scrollable ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2257
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: "Legend" }),
2258
+ scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2223
2259
  ] }),
2224
2260
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
2225
2261
  LegendGroup,
@@ -2231,8 +2267,8 @@ var HelpModal = ({
2231
2267
  },
2232
2268
  group[0]?.title ?? index
2233
2269
  )) }),
2234
- tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2235
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
2270
+ tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2271
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
2236
2272
  ]
2237
2273
  }
2238
2274
  );
@@ -2247,13 +2283,13 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2247
2283
  paddingX: 1,
2248
2284
  width: Math.min(COLS, 78),
2249
2285
  children: [
2250
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2251
- /* @__PURE__ */ jsx(Text2, { children: item.title }),
2286
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2287
+ /* @__PURE__ */ jsx(Text3, { children: item.title }),
2252
2288
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2253
- /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: section.heading }),
2254
- section.lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { children: " " + line }, i))
2289
+ /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: section.heading }),
2290
+ section.lines.map((line, i) => /* @__PURE__ */ jsx(Text3, { children: " " + line }, i))
2255
2291
  ] }, section.heading)),
2256
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "esc \xB7 e close" }) })
2292
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "esc \xB7 e close" }) })
2257
2293
  ]
2258
2294
  }
2259
2295
  );
@@ -2279,21 +2315,21 @@ var RepoPicker = ({
2279
2315
  paddingX: 1,
2280
2316
  minWidth: 42,
2281
2317
  children: [
2282
- /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: `Filter by repo (${selected.size} on)` }),
2283
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(36) }),
2318
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: `Filter by repo (${selected.size} on)` }),
2319
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2284
2320
  visible.map((repo, i) => {
2285
2321
  const idx = start + i;
2286
2322
  const on = selected.has(repo);
2287
2323
  const active = idx === cursor;
2288
2324
  return /* @__PURE__ */ jsxs(Box, { children: [
2289
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
2290
- /* @__PURE__ */ jsx(Text2, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2291
- /* @__PURE__ */ jsx(Text2, { bold: active, children: repo })
2325
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
2326
+ /* @__PURE__ */ jsx(Text3, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2327
+ /* @__PURE__ */ jsx(Text3, { bold: active, children: repo })
2292
2328
  ] }, repo);
2293
2329
  }),
2294
- repos.length > budget ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2295
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(36) }),
2296
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
2330
+ repos.length > budget ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2331
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2332
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
2297
2333
  ]
2298
2334
  }
2299
2335
  );
@@ -2330,7 +2366,8 @@ var BrowseScreen = ({
2330
2366
  leavingUrls,
2331
2367
  onLeave,
2332
2368
  transients,
2333
- onTabChange
2369
+ onTabChange,
2370
+ sidebar
2334
2371
  }) => {
2335
2372
  const { rows } = useWindowSize();
2336
2373
  const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match) : secs;
@@ -2430,6 +2467,9 @@ var BrowseScreen = ({
2430
2467
  out.add(s.id);
2431
2468
  return out;
2432
2469
  }, [localSections, transients]);
2470
+ const [railOpen, setRailOpen] = useState(false);
2471
+ const showRail = railOpen && !!sidebar;
2472
+ const listCols = showRail ? COLS - SIDEBAR_COLS : COLS;
2433
2473
  const [sparkFrame, setSparkFrame] = useState(0);
2434
2474
  const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || !!transients?.size && section.items.some((item) => transientOf(transients, item, section.id));
2435
2475
  useEffect(() => {
@@ -2530,6 +2570,10 @@ var BrowseScreen = ({
2530
2570
  setRepoPicker(true);
2531
2571
  return;
2532
2572
  }
2573
+ if (input === "i" && sidebar) {
2574
+ setRailOpen((open) => !open);
2575
+ return;
2576
+ }
2533
2577
  if (key.escape && search != null) {
2534
2578
  setSearch(null);
2535
2579
  return;
@@ -2815,6 +2859,9 @@ var BrowseScreen = ({
2815
2859
  ["\u2190\u2192", "tab"],
2816
2860
  ["\u21B5/d", "open"],
2817
2861
  ["m", "actions"],
2862
+ // Advertised only where it does something, and named for what it
2863
+ // shows rather than for the furniture: nobody wants "a sidebar".
2864
+ ...sidebar ? [["i", railOpen ? "hide" : sidebar.title.toLowerCase()]] : [],
2818
2865
  ["?", "help"],
2819
2866
  ["q", "quit"]
2820
2867
  ];
@@ -2867,8 +2914,8 @@ var BrowseScreen = ({
2867
2914
  ),
2868
2915
  ciStatusState ? /* @__PURE__ */ jsx(CiStatusLine, { state: ciStatusState, job: ciJob }) : null,
2869
2916
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2870
- /* @__PURE__ */ jsx(Text2, { bold: true, color: "#FF8700", children: " \u26A1 auto-refresh paused to save API budget" }),
2871
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " r refreshes anyway" })
2917
+ /* @__PURE__ */ jsx(Text3, { bold: true, color: "#FF8700", children: " \u26A1 auto-refresh paused to save API budget" }),
2918
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
2872
2919
  ] }) : null,
2873
2920
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
2874
2921
  Tabs,
@@ -2882,52 +2929,65 @@ var BrowseScreen = ({
2882
2929
  }
2883
2930
  ) }),
2884
2931
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2885
- /* @__PURE__ */ jsx(Text2, { color: "cyan", children: " / " }),
2886
- /* @__PURE__ */ jsx(Text2, { children: search }),
2887
- searchInput ? /* @__PURE__ */ jsx(Text2, { color: "cyan", children: "\u258F" }) : null,
2888
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
2932
+ /* @__PURE__ */ jsx(Text3, { color: "cyan", children: " / " }),
2933
+ /* @__PURE__ */ jsx(Text3, { children: search }),
2934
+ searchInput ? /* @__PURE__ */ jsx(Text3, { color: "cyan", children: "\u258F" }) : null,
2935
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
2889
2936
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2890
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " \u25C9 " }),
2891
- /* @__PURE__ */ jsx(Text2, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
2892
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " f edit \xB7 esc clear" })
2937
+ /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: " \u25C9 " }),
2938
+ /* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
2939
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " f edit \xB7 esc clear" })
2893
2940
  ] }) : null,
2894
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", minHeight: listHeight, children: [
2895
- /* @__PURE__ */ jsxs(Backdrop, { dimmed: !!overlay, absolute: !!overlay, height: listHeight, children: [
2896
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleItems.map((item, i) => /* @__PURE__ */ jsx(
2897
- ItemRow,
2898
- {
2899
- item,
2900
- active: viewStart + i === cursor,
2901
- login,
2902
- merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2903
- leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
2904
- transient: transientOf(transients, item, section.id),
2905
- prefix: treePrefix(section.items, viewStart + i),
2906
- parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
2907
- sparkFrame,
2908
- gap: i > 0 && gapsAbove(section.items, viewStart + i)
2909
- },
2910
- `${viewStart + i}:${item.kind === "task" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
2911
- )) }),
2912
- hasMore && /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
2913
- " ",
2914
- "\u2193 ",
2915
- section.items.length - viewStart - visibleCount,
2916
- " more"
2917
- ] })
2918
- ] }),
2919
- overlay ? /* @__PURE__ */ jsx(
2941
+ /* @__PURE__ */ jsxs(Box, { children: [
2942
+ /* @__PURE__ */ jsxs(
2920
2943
  Box,
2921
2944
  {
2922
- flexGrow: 1,
2923
2945
  flexDirection: "column",
2924
- justifyContent: "center",
2925
- alignItems: "center",
2926
- children: overlay
2946
+ minHeight: listHeight,
2947
+ width: showRail ? listCols : void 0,
2948
+ flexShrink: 0,
2949
+ children: [
2950
+ /* @__PURE__ */ jsxs(Backdrop, { dimmed: !!overlay, absolute: !!overlay, height: listHeight, children: [
2951
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleItems.map((item, i) => /* @__PURE__ */ jsx(
2952
+ ItemRow,
2953
+ {
2954
+ item,
2955
+ active: viewStart + i === cursor,
2956
+ login,
2957
+ merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2958
+ leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
2959
+ transient: transientOf(transients, item, section.id),
2960
+ prefix: treePrefix(section.items, viewStart + i),
2961
+ parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
2962
+ sparkFrame,
2963
+ cols: listCols,
2964
+ gap: i > 0 && gapsAbove(section.items, viewStart + i)
2965
+ },
2966
+ `${viewStart + i}:${item.kind === "task" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
2967
+ )) }),
2968
+ hasMore && /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
2969
+ " ",
2970
+ "\u2193 ",
2971
+ section.items.length - viewStart - visibleCount,
2972
+ " more"
2973
+ ] })
2974
+ ] }),
2975
+ overlay ? /* @__PURE__ */ jsx(
2976
+ Box,
2977
+ {
2978
+ flexGrow: 1,
2979
+ flexDirection: "column",
2980
+ justifyContent: "center",
2981
+ alignItems: "center",
2982
+ children: overlay
2983
+ }
2984
+ ) : null
2985
+ ]
2927
2986
  }
2928
- ) : null
2987
+ ),
2988
+ showRail && sidebar ? /* @__PURE__ */ jsx(SidePanel, { sidebar, height: listHeight }) : null
2929
2989
  ] }),
2930
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text2, { color: "green", children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
2990
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: "green", children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
2931
2991
  ]
2932
2992
  }
2933
2993
  );
@@ -2943,7 +3003,7 @@ var NoRowsScreen = ({
2943
3003
  });
2944
3004
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2945
3005
  /* @__PURE__ */ jsx(StatusMessage, { variant: reason === "empty" ? "info" : "error", children: reason === "empty" ? "Nothing open." : `Fetch failed \u2014 ${detail ?? "no reason reported"}` }),
2946
- reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: detail }) }) : null,
3006
+ reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: detail }) }) : null,
2947
3007
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
2948
3008
  FooterHints,
2949
3009
  {
@@ -3108,6 +3168,7 @@ var App = ({
3108
3168
  if (manual) setRefreshing(true);
3109
3169
  fetcher().then((fresh) => {
3110
3170
  if (fresh.budget) setBudget(fresh.budget);
3171
+ if (fresh.sidebar !== void 0) setSidebar(fresh.sidebar);
3111
3172
  if (cacheKey) writeCache(cacheKey, fresh);
3112
3173
  setRefreshing(false);
3113
3174
  setFetchedAt(Date.now());
@@ -3218,6 +3279,7 @@ var App = ({
3218
3279
  const [budget, setBudget] = useState(
3219
3280
  () => cacheKey ? readCache(cacheKey)?.budget ?? null : null
3220
3281
  );
3282
+ const [sidebar, setSidebar] = useState(void 0);
3221
3283
  const [skippedForBudget, setSkippedForBudget] = useState(false);
3222
3284
  const [mergedUrls, setMergedUrls] = useState([]);
3223
3285
  const [leavingUrls, setLeavingUrls] = useState([]);
@@ -3324,6 +3386,7 @@ var App = ({
3324
3386
  onLeave: markLeaving,
3325
3387
  transients,
3326
3388
  onTabChange: setVisibleTab,
3389
+ sidebar,
3327
3390
  ciStatusState: hasCiStatus ? ciStatusState : void 0,
3328
3391
  ciJob,
3329
3392
  tabHelp,
@@ -3376,4 +3439,4 @@ var matchesFilter = (repo, filter) => {
3376
3439
  };
3377
3440
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3378
3441
 
3379
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, PIN_MARK, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
3442
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, PIN_MARK, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate2 as truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@kud/gh": "0.9.0",
52
- "@kud/ink-ui": "0.14.0",
52
+ "@kud/ink-ui": "0.17.0",
53
53
  "zx": "8.8.5"
54
54
  },
55
55
  "devDependencies": {