@kud/gh-ink 0.59.1 → 0.61.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,8 +1,8 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { PrComments, PrHealthData, PrCheck, Health } from '@kud/gh';
3
- import { StyledLine } from '@kud/ink-ui';
4
- import { AnyItem, Section, CiStatus, GHItem, OriginSplit } from '@kud/gh-workflow';
3
+ import { GHItem, AnyItem, Section, CiStatus, OriginSplit } from '@kud/gh-workflow';
5
4
  export { AnyItem, Band, CiStatus, GHDetail, GHItem, InboxConfig, Move, OriginSplit, PIN_MARK, RepoHeader, RepoProfile, Section, ShowLess, ShowMore, Standing, SubgroupHeader, TaskRow, bandOf, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
5
+ import { StyledLine } from '@kud/ink-ui';
6
6
 
7
7
  type CommentsPanelProps = {
8
8
  repo: string;
@@ -179,6 +179,82 @@ declare const SidePanel: ({ sidebar, liveLabel, width, height, focused, cursor,
179
179
  cursor?: number;
180
180
  }) => React.JSX.Element;
181
181
 
182
+ /**
183
+ * How this row is moving through the list right now.
184
+ *
185
+ * The row itself never works this out: whoever owns the list owns the
186
+ * choreography — what counts as an arrival, how long a departure is held, which
187
+ * frame of a ramp is on screen — and hands the row the conclusion. So this
188
+ * enumerates what the TITLE has to say, not what happened upstream to cause it.
189
+ *
190
+ * `withdrawn` is `departing` plus the strike: a row struck through is one that
191
+ * has been taken off the list rather than moved along it, and the two read
192
+ * differently on purpose.
193
+ */
194
+ type RowMotion = "arriving" | "departing" | "withdrawn";
195
+ /**
196
+ * A solid pill at the end of the row, saying what just happened to it.
197
+ *
198
+ * Solid because it is an EVENT — `@kud/ink-ui`'s pill law in one line: the
199
+ * column says where it sits, soft says what it is, solid says something
200
+ * happened. The word and the colour are the caller's vocabulary; what the row
201
+ * owns is that the pill is charged against the width budget whether or not it
202
+ * is drawn.
203
+ */
204
+ type RowAnnouncement = {
205
+ label: string;
206
+ color: string;
207
+ };
208
+ type PrRowProps = {
209
+ item: GHItem;
210
+ active: boolean;
211
+ login?: string;
212
+ /**
213
+ * Columns available to this row, which is NOT always the frame width: a rail
214
+ * beside the list takes its share, and a budget that does not know the rail is
215
+ * there overflows by exactly the rail.
216
+ */
217
+ cols: number;
218
+ /** Overrides the health glyph — the inbox passes its transit/merge frame here. */
219
+ icon?: {
220
+ glyph: string;
221
+ color: string;
222
+ };
223
+ /**
224
+ * This row's tree glyphs, already assembled — see `treePrefix`. Empty for a
225
+ * top-level row.
226
+ *
227
+ * A row cannot work this out about itself: it is a fact about the rows BELOW
228
+ * it (is a sibling still to come?) and about its ANCESTORS (does a stem still
229
+ * need to run past this level?), so the list supplies it.
230
+ */
231
+ prefix?: string;
232
+ /**
233
+ * Labels carried by EVERY label-bearing row in this section, measured once by
234
+ * the list. Suppressed here for the same reason `impliedLabels` suppresses a
235
+ * repo's conventional ones — a label on every row classifies nothing — but on
236
+ * the section axis, which catches uniformity that comes from the query rather
237
+ * than from a repo convention. Defaulted so a host that does not measure it
238
+ * keeps exactly the behaviour it had.
239
+ */
240
+ uniformLabels?: readonly string[];
241
+ /** How the title reads while the row is coming or going. Still when absent. */
242
+ motion?: RowMotion;
243
+ /** In order, left to right. Charged against the width budget either way. */
244
+ announcements?: readonly RowAnnouncement[];
245
+ };
246
+ /**
247
+ * One pull request or issue, as a single line of a list.
248
+ *
249
+ * The shared renderer: the inbox draws its rows with it, and so does anything
250
+ * else that has a `GHItem` and a width. Everything it knows is in its props —
251
+ * it holds no timers, reads no clock, and has no opinion about whether the list
252
+ * is refreshing. That is deliberate and it is the seam: the choreography of
253
+ * arrival, merge and departure belongs to whoever owns the list, and reaches
254
+ * the row as `icon`, `motion` and `announcements` after the decisions are made.
255
+ */
256
+ declare const PrRow: ({ item, active, login, cols, icon: iconOverride, prefix, uniformLabels, motion, announcements, }: PrRowProps) => React.JSX.Element;
257
+
182
258
  /**
183
259
  * One state per thing, from a closed set the strip knows how to draw. The host
184
260
  * decides what "warn" means — an error-rate edge, a stale feed, a flaky build —
@@ -252,6 +328,15 @@ declare const healthGlyph: (h: Health) => string;
252
328
  declare const healthColor: (h: Health) => string;
253
329
  declare const healthLegend: [Health, string][];
254
330
 
331
+ /**
332
+ * Shorten a string to `max` columns, eliding the MIDDLE rather than the tail.
333
+ *
334
+ * Shared rather than local because both row renderers measure against the same
335
+ * budget arithmetic, and a second copy is a second place for the ellipsis rule
336
+ * to drift.
337
+ */
338
+ declare const truncate: (str: string, max: number) => string;
339
+
255
340
  type ExtensionTarget = {
256
341
  item?: AnyItem;
257
342
  ciJob?: string;
@@ -421,7 +506,6 @@ declare const fitCount: (items: AnyItem[], start: number, budget: number) => num
421
506
  declare const windowCount: (items: AnyItem[], start: number, budget: number) => number;
422
507
  declare const maxViewStart: (items: AnyItem[], budget: number) => number;
423
508
  declare const withHeaders: (items: AnyItem[], idx: number) => number;
424
- declare const truncate: (str: string, max: number) => string;
425
509
  declare const clipboard: (text: string) => void;
426
510
  declare const buildCheckoutCmd: (repoFull: string, branch: string, login: string) => Promise<string>;
427
511
  declare const resolveRepoPath: (repoFull: string) => Promise<string | null>;
@@ -629,4 +713,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
629
713
  */
630
714
  declare const parsePatterns: (value: string) => string[];
631
715
 
632
- export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, type Rails, type RepoFilter, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, 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 };
716
+ export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, 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 };
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
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, StatusMessage, FooterHints, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth, Pill } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, Pill, useAppKeys, LoadingScreen, StatusMessage, FooterHints, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } 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
+ import { PIN_MARK, sizeOf, sizePartsOf, depthOf, impliedLabels, labelPriority, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin } from '@kud/gh-workflow';
7
+ export { PIN_MARK, bandOf, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
6
8
  import { $ } from 'zx';
7
9
  import { spawn } from 'child_process';
8
10
  import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, watch, rmSync } from 'fs';
9
11
  import { join, dirname, basename } from 'path';
10
12
  import { homedir } from 'os';
11
- import { PIN_MARK, depthOf, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, sizeOf, sizePartsOf, impliedLabels, labelPriority } from '@kud/gh-workflow';
12
- export { PIN_MARK, bandOf, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
13
13
 
14
14
  // src/components/comments-panel.tsx
15
15
  var ENTITIES = {
@@ -748,19 +748,226 @@ var SidePanel = ({
748
748
  )
749
749
  );
750
750
  };
751
+ var healthDisplay = {
752
+ none: { glyph: " ", color: colors.muted },
753
+ draft: { glyph: "~", color: colors.muted },
754
+ "ci-fail": { glyph: "\u2717", color: colors.error },
755
+ conflict: { glyph: "!", color: colors.accent },
756
+ "changes-req": { glyph: "\xB1", color: colors.warning },
757
+ threads: { glyph: "\u25C6", color: colors.accent },
758
+ pending: { glyph: "*", color: colors.warning },
759
+ approved: { glyph: "\u2713", color: colors.success },
760
+ waiting: { glyph: "\xB7", color: colors.muted },
761
+ // The one glyph here that MEANS its state rather than merely differing from
762
+ // the others. `✓` and `✗` you never have to think about; `»` you did — it was
763
+ // chosen to suggest "moved through", which is a stretch nobody reads at a
764
+ // glance. This is Nerd Font's git-merge icon, written as an escape because raw
765
+ // PUA bytes get mangled by editors and diffs.
766
+ //
767
+ // The cost, stated rather than discovered: a terminal without a Nerd Font
768
+ // draws a box here where it used to draw a chevron. That is a trade this
769
+ // package has already made once — the inbox hardcodes the comment glyph at
770
+ // \u{f086} — and one state degrading to tofu is cheaper than ten states none
771
+ // of which say what they mean.
772
+ merged: { glyph: "\uE727", color: colors.muted },
773
+ // Nerd Font's closed-pull-request icon, for the same reason as `merged` above
774
+ // and with the same caveat about a font that lacks it. `×` had a second problem
775
+ // beyond being arbitrary: it is one stroke away from `✗` (ci failing) two rows
776
+ // up in this very map, and the shape-distinctness this file turns on is a
777
+ // silhouette test, not a codepoint one.
778
+ closed: { glyph: "\uEBDA", color: colors.muted }
779
+ };
780
+ var healthGlyph = (h) => healthDisplay[h].glyph;
781
+ var healthColor = (h) => healthDisplay[h].color;
782
+ var healthLegend = [
783
+ ["approved", "Approved \xB7 ready to merge"],
784
+ ["pending", "Checks running"],
785
+ ["ci-fail", "CI failing"],
786
+ ["conflict", "Merge conflict"],
787
+ ["changes-req", "Changes requested"],
788
+ ["threads", "Open threads \xB7 your reply"],
789
+ ["waiting", "Awaiting review"],
790
+ ["draft", "Draft"],
791
+ ["merged", "Merged"],
792
+ ["closed", "Closed"]
793
+ ];
794
+ var UNREAD_DISPLAY = { glyph: "\u25CB", color: colors.muted };
795
+ var displayFor = (h) => h ? healthDisplay[h] : UNREAD_DISPLAY;
796
+
797
+ // src/lib/truncate.ts
798
+ var truncate = (str, max) => {
799
+ if (str.length <= max) return str;
800
+ const half = Math.floor((max - 1) / 2);
801
+ return `${str.slice(0, half)}\u2026${str.slice(-half)}`;
802
+ };
803
+ var DimContext = createContext(false);
804
+ var BACKDROP_FG = "#5a5a68";
805
+ var BACKDROP_FG_RECESSED = "#3a3a46";
806
+ var backdropStyle = (props) => ({
807
+ color: props.dimColor ? BACKDROP_FG_RECESSED : BACKDROP_FG,
808
+ dimColor: false,
809
+ bold: false,
810
+ italic: false,
811
+ inverse: false,
812
+ backgroundColor: void 0
813
+ });
814
+ var Text3 = ({ children, ...props }) => {
815
+ const dimmed = useContext(DimContext);
816
+ return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, ...backdropStyle(props), children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
817
+ };
818
+ var useBackdropped = () => useContext(DimContext);
819
+ var Backdrop = ({
820
+ dimmed,
821
+ absolute,
822
+ height,
823
+ children
824
+ }) => /* @__PURE__ */ jsx(DimContext.Provider, { value: dimmed, children: absolute ? /* @__PURE__ */ jsx(
825
+ Box,
826
+ {
827
+ position: "absolute",
828
+ flexDirection: "column",
829
+ width: "100%",
830
+ height,
831
+ children
832
+ }
833
+ ) : children });
834
+ var PrRow = ({
835
+ item,
836
+ active,
837
+ login,
838
+ cols,
839
+ icon: iconOverride,
840
+ prefix = "",
841
+ uniformLabels = [],
842
+ motion,
843
+ announcements = []
844
+ }) => {
845
+ const backdropped = useBackdropped();
846
+ const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
847
+ const { glyph: icon, color } = iconOverride ?? {
848
+ glyph: healthIcon,
849
+ color: healthColor2
850
+ };
851
+ const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
852
+ const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
853
+ const numStr = `#${item.number}`.padEnd(7);
854
+ const showAuthor = !!item.author && item.author !== login;
855
+ const sizeLabel = sizeOf(item);
856
+ const sizeParts = sizePartsOf(item);
857
+ const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
858
+ const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
859
+ const ageLabel2 = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
860
+ const pillCaps = announcements.length * 2;
861
+ const repoLabel = depthOf(item) > 0 ? item.repo : "";
862
+ const implied = [...impliedLabels(item.repo), ...uniformLabels];
863
+ const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
864
+ const givingUp = {
865
+ author: false,
866
+ size: false,
867
+ threads: false,
868
+ age: false,
869
+ repo: false,
870
+ labels: 0
871
+ };
872
+ const LABEL_CELL_MAX = 24;
873
+ const labelCell = () => {
874
+ const shown = labelNames.slice(
875
+ 0,
876
+ Math.max(0, labelNames.length - givingUp.labels)
877
+ );
878
+ if (shown.length === 0) return "";
879
+ const fitted = [];
880
+ for (const name of shown) {
881
+ const next = [...fitted, name].join(", ");
882
+ if (next.length <= LABEL_CELL_MAX) fitted.push(name);
883
+ }
884
+ if (fitted.length === 0) {
885
+ return `\uF02B ${truncate(shown[0], LABEL_CELL_MAX)}`;
886
+ }
887
+ return `\uF02B ${fitted.join(", ")}`;
888
+ };
889
+ const widthOf2 = () => {
890
+ const suffix = [
891
+ givingUp.age ? "" : ageLabel2 || "",
892
+ // ASCII only, so no PUA double-width correction — see the label cell.
893
+ givingUp.size ? "" : sizeLabel || "",
894
+ givingUp.threads ? "" : unresolvedLabel,
895
+ showAuthor && !givingUp.author ? `by ${item.author}` : "",
896
+ ...announcements.map((a) => a.label)
897
+ ].filter(Boolean).join(" ");
898
+ const cell = labelCell();
899
+ return 2 + prefix.length + 2 + 2 + 7 + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + suffix.length + pillCaps + 6;
900
+ };
901
+ const MIN_TITLE = 24;
902
+ for (const give of [
903
+ () => givingUp.author = true,
904
+ () => givingUp.labels = 1,
905
+ () => givingUp.size = true,
906
+ () => givingUp.threads = true,
907
+ () => givingUp.labels = 2,
908
+ () => givingUp.repo = true,
909
+ () => givingUp.age = true
910
+ ]) {
911
+ if (cols - widthOf2() >= MIN_TITLE) break;
912
+ give();
913
+ }
914
+ const labelLabel = labelCell();
915
+ const titleMax = Math.max(1, cols - widthOf2());
916
+ return /* @__PURE__ */ jsxs(Box, { children: [
917
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
918
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
919
+ /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
920
+ /* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
921
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: numStr }),
922
+ /* @__PURE__ */ jsx(
923
+ Text3,
924
+ {
925
+ bold: active || motion === "arriving",
926
+ dimColor: motion === "departing" || motion === "withdrawn",
927
+ strikethrough: motion === "withdrawn",
928
+ children: truncate(item.title, titleMax) + " "
929
+ }
930
+ ),
931
+ labelLabel ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: labelLabel + " " }) : null,
932
+ repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: repoLabel }) : null,
933
+ sizeParts && !givingUp.size ? /* @__PURE__ */ jsxs(Text3, { children: [
934
+ " ",
935
+ /* @__PURE__ */ jsx(Text3, { color: colors.success, children: sizeParts.added }),
936
+ " ",
937
+ /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
938
+ ] }) : null,
939
+ unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
940
+ Text3,
941
+ {
942
+ bold: !spokeLast,
943
+ color: spokeLast ? colors.secondary : colors.accent,
944
+ children: " " + unresolvedLabel
945
+ }
946
+ ) : null,
947
+ showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
948
+ ageLabel2 && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
949
+ /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + agePair.activity }),
950
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` (${agePair.lifetime})` })
951
+ ] }) : /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + ageLabel2 }) : null,
952
+ backdropped ? null : announcements.map((a) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
953
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
954
+ /* @__PURE__ */ jsx(Pill, { color: a.color, children: a.label })
955
+ ] }, a.label))
956
+ ] });
957
+ };
751
958
  var stripGlyph = (state) => {
752
959
  switch (state) {
753
960
  case "ok":
754
- return ["\u2713", colors.success];
961
+ return ["\uF00C", colors.success];
755
962
  case "warn":
756
- return ["!", colors.warning];
963
+ return ["\uF071", colors.warning];
757
964
  case "fail":
758
- return ["\u2717", colors.error];
965
+ return ["\uF00D", colors.error];
759
966
  case "unknown":
760
- return ["\u25CB", colors.muted];
967
+ return ["?", colors.muted];
761
968
  }
762
969
  };
763
- var ALARM = "\u25B2";
970
+ var ALARM = "\uF06D";
764
971
  var SEVERITY = {
765
972
  fail: 0,
766
973
  warn: 1,
@@ -775,7 +982,7 @@ var ageLabel = (at, now = Date.now()) => {
775
982
  if (m < 60) return `${m}m`;
776
983
  return `${Math.round(m / 60)}h`;
777
984
  };
778
- var itemText = (item) => `${stripGlyph(item.state)[0]} ${item.key}${item.alarm ? ALARM : ""}`;
985
+ var itemText = (item) => `${stripGlyph(item.state)[0]}${item.alarm ? ` ${ALARM}` : ""} ${item.key}`;
779
986
  var stripLayout = (strip, width, now = Date.now()) => {
780
987
  const age = ageLabel(strip.at, now);
781
988
  const fixed = 2 + strip.title.length + 2 + (age ? age.length + 3 : 0);
@@ -799,6 +1006,12 @@ var stripLayout = (strip, width, now = Date.now()) => {
799
1006
  }
800
1007
  return { parts: null, summary: text, age };
801
1008
  };
1009
+ var nameStyle = (item) => {
1010
+ if (item.state === "fail" || item.alarm)
1011
+ return { color: colors.error, bold: true };
1012
+ if (item.state === "unknown") return { dimColor: true };
1013
+ return {};
1014
+ };
802
1015
  var StatusStripLine = ({
803
1016
  strip,
804
1017
  label = "status",
@@ -812,7 +1025,7 @@ var StatusStripLine = ({
812
1025
  ] });
813
1026
  if (strip === null)
814
1027
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
815
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u25CB " }),
1028
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${stripGlyph("unknown")[0]} ` }),
816
1029
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} no signal / not configured` })
817
1030
  ] });
818
1031
  const { parts, summary, age } = stripLayout(strip, width, now);
@@ -820,64 +1033,28 @@ var StatusStripLine = ({
820
1033
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${strip.title} ` }),
821
1034
  parts ? parts.map((item, i) => {
822
1035
  const [glyph, color] = stripGlyph(item.state);
1036
+ const asking = item.state === "fail" || item.state === "warn";
823
1037
  return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
824
1038
  i > 0 ? /* @__PURE__ */ jsx(Text, { children: " " }) : null,
825
- /* @__PURE__ */ jsx(Text, { color, bold: true, children: glyph }),
826
- /* @__PURE__ */ jsxs(Text, { children: [
1039
+ /* @__PURE__ */ jsx(
1040
+ Text,
1041
+ {
1042
+ color: item.state === "unknown" ? void 0 : color,
1043
+ dimColor: item.state === "unknown",
1044
+ bold: asking,
1045
+ children: glyph
1046
+ }
1047
+ ),
1048
+ item.alarm ? /* @__PURE__ */ jsx(Text, { color: colors.error, bold: true, children: ` ${ALARM}` }) : null,
1049
+ /* @__PURE__ */ jsxs(Text, { ...nameStyle(item), children: [
827
1050
  " ",
828
1051
  item.key
829
- ] }),
830
- item.alarm ? /* @__PURE__ */ jsx(Text, { color: colors.error, bold: true, children: ALARM }) : null
1052
+ ] })
831
1053
  ] }, item.key);
832
1054
  }) : /* @__PURE__ */ jsx(Text, { children: summary }),
833
1055
  age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${age}` }) : null
834
1056
  ] });
835
1057
  };
836
- var healthDisplay = {
837
- none: { glyph: " ", color: colors.muted },
838
- draft: { glyph: "~", color: colors.muted },
839
- "ci-fail": { glyph: "\u2717", color: colors.error },
840
- conflict: { glyph: "!", color: colors.accent },
841
- "changes-req": { glyph: "\xB1", color: colors.warning },
842
- threads: { glyph: "\u25C6", color: colors.accent },
843
- pending: { glyph: "*", color: colors.warning },
844
- approved: { glyph: "\u2713", color: colors.success },
845
- waiting: { glyph: "\xB7", color: colors.muted },
846
- // The one glyph here that MEANS its state rather than merely differing from
847
- // the others. `✓` and `✗` you never have to think about; `»` you did — it was
848
- // chosen to suggest "moved through", which is a stretch nobody reads at a
849
- // glance. This is Nerd Font's git-merge icon, written as an escape because raw
850
- // PUA bytes get mangled by editors and diffs.
851
- //
852
- // The cost, stated rather than discovered: a terminal without a Nerd Font
853
- // draws a box here where it used to draw a chevron. That is a trade this
854
- // package has already made once — the inbox hardcodes the comment glyph at
855
- // \u{f086} — and one state degrading to tofu is cheaper than ten states none
856
- // of which say what they mean.
857
- merged: { glyph: "\uE727", color: colors.muted },
858
- // Nerd Font's closed-pull-request icon, for the same reason as `merged` above
859
- // and with the same caveat about a font that lacks it. `×` had a second problem
860
- // beyond being arbitrary: it is one stroke away from `✗` (ci failing) two rows
861
- // up in this very map, and the shape-distinctness this file turns on is a
862
- // silhouette test, not a codepoint one.
863
- closed: { glyph: "\uEBDA", color: colors.muted }
864
- };
865
- var healthGlyph = (h) => healthDisplay[h].glyph;
866
- var healthColor = (h) => healthDisplay[h].color;
867
- var healthLegend = [
868
- ["approved", "Approved \xB7 ready to merge"],
869
- ["pending", "Checks running"],
870
- ["ci-fail", "CI failing"],
871
- ["conflict", "Merge conflict"],
872
- ["changes-req", "Changes requested"],
873
- ["threads", "Open threads \xB7 your reply"],
874
- ["waiting", "Awaiting review"],
875
- ["draft", "Draft"],
876
- ["merged", "Merged"],
877
- ["closed", "Closed"]
878
- ];
879
- var UNREAD_DISPLAY = { glyph: "\u25CB", color: colors.muted };
880
- var displayFor = (h) => h ? healthDisplay[h] : UNREAD_DISPLAY;
881
1058
 
882
1059
  // src/inbox/cache.ts
883
1060
  var cacheTtlMs = () => inboxConfig().cacheTtlMs;
@@ -1370,11 +1547,6 @@ var withHeaders = (items, idx) => {
1370
1547
  start--;
1371
1548
  return start;
1372
1549
  };
1373
- var truncate = (str, max) => {
1374
- if (str.length <= max) return str;
1375
- const half = Math.floor((max - 1) / 2);
1376
- return `${str.slice(0, half)}\u2026${str.slice(-half)}`;
1377
- };
1378
1550
  var clipboard = (text) => {
1379
1551
  const p = spawn("pbcopy", [], { stdio: "pipe" });
1380
1552
  p.stdin.write(text);
@@ -1559,37 +1731,6 @@ var FRAME_CHROME_COLS = 2 + FRAME_PAD_X * 2;
1559
1731
  var COLS = (process.stdout.columns ?? 120) - FRAME_CHROME_COLS;
1560
1732
  var MODAL_CHROME_COLS = 2 + 1 * 2;
1561
1733
  var OVERLAY_BG = "#22222e";
1562
- var DimContext = createContext(false);
1563
- var BACKDROP_FG = "#5a5a68";
1564
- var BACKDROP_FG_RECESSED = "#3a3a46";
1565
- var backdropStyle = (props) => ({
1566
- color: props.dimColor ? BACKDROP_FG_RECESSED : BACKDROP_FG,
1567
- dimColor: false,
1568
- bold: false,
1569
- italic: false,
1570
- inverse: false,
1571
- backgroundColor: void 0
1572
- });
1573
- var Text4 = ({ children, ...props }) => {
1574
- const dimmed = useContext(DimContext);
1575
- return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, ...backdropStyle(props), children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
1576
- };
1577
- var useBackdropped = () => useContext(DimContext);
1578
- var Backdrop = ({
1579
- dimmed,
1580
- absolute,
1581
- height,
1582
- children
1583
- }) => /* @__PURE__ */ jsx(DimContext.Provider, { value: dimmed, children: absolute ? /* @__PURE__ */ jsx(
1584
- Box,
1585
- {
1586
- position: "absolute",
1587
- flexDirection: "column",
1588
- width: "100%",
1589
- height,
1590
- children
1591
- }
1592
- ) : children });
1593
1734
  var workCount = (s) => {
1594
1735
  let n = 0;
1595
1736
  for (let i = 0; i < s.items.length; i++) {
@@ -1966,13 +2107,13 @@ var InboxHeader = ({
1966
2107
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - budgetSeg.length - statusSeg.length
1967
2108
  );
1968
2109
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
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,
2110
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: brand }),
2111
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: countSeg }),
2112
+ userSeg ? /* @__PURE__ */ jsx(Text3, { children: userSeg }) : null,
2113
+ scopeLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: workLabel }) : null,
2114
+ budgetLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: budgetCritical ? "#FF5F5F" : colors.accent, children: budgetSeg }) : null,
1974
2115
  statusText ? /* @__PURE__ */ jsx(
1975
- Text4,
2116
+ Text3,
1976
2117
  {
1977
2118
  color: statusColor,
1978
2119
  dimColor: !statusColor,
@@ -1980,7 +2121,7 @@ var InboxHeader = ({
1980
2121
  children: statusSeg
1981
2122
  }
1982
2123
  ) : null,
1983
- /* @__PURE__ */ jsx(Text4, { color: colors.info, dimColor: true, children: "\u254C".repeat(fill) })
2124
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, dimColor: true, children: "\u254C".repeat(fill) })
1984
2125
  ] });
1985
2126
  };
1986
2127
  var stripSignature = (strip) => strip === null ? "\u2205" : strip.items.map((i) => `${i.key}=${i.state}${i.alarm ? "\u25B2" : ""}`).join(",");
@@ -2004,24 +2145,24 @@ var CiStatusLine = ({
2004
2145
  const name = job ?? "ci";
2005
2146
  if (state.kind === "loading")
2006
2147
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2007
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " \xB7 " }),
2008
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: `${name} loading\u2026` })
2148
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " \xB7 " }),
2149
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} loading\u2026` })
2009
2150
  ] });
2010
2151
  if (state.kind === "error")
2011
2152
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2012
- /* @__PURE__ */ jsx(Text4, { color: colors.error, bold: true, children: " \u2717 " }),
2013
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: `${name} no build / not configured` })
2153
+ /* @__PURE__ */ jsx(Text3, { color: colors.error, bold: true, children: " \u2717 " }),
2154
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} no build / not configured` })
2014
2155
  ] });
2015
2156
  const { status } = state;
2016
2157
  const [, color] = jenkinsResultDisplay(status.result, status.building);
2017
2158
  const label = status.building ? "BUILDING" : status.result;
2018
2159
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
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
2160
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " }),
2161
+ /* @__PURE__ */ jsx(Text3, { color, bold: true, children: "\u25CF " }),
2162
+ /* @__PURE__ */ jsx(Text3, { bold: true, children: status.job }),
2163
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + label }),
2164
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " #" + status.buildNumber }),
2165
+ status.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + status.age }) : null
2025
2166
  ] });
2026
2167
  };
2027
2168
  var FENCE_COLS = 46;
@@ -2034,10 +2175,10 @@ var RepoHeaderRow = ({
2034
2175
  const fill = Math.max(4, FENCE_COLS - label.length);
2035
2176
  const rule = "\u2500".repeat(fill);
2036
2177
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2037
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2038
- /* @__PURE__ */ jsx(Text4, { dimColor: !active, children: "\u2500\u2500 " }),
2178
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2179
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: "\u2500\u2500 " }),
2039
2180
  /* @__PURE__ */ jsx(
2040
- Text4,
2181
+ Text3,
2041
2182
  {
2042
2183
  dimColor: false,
2043
2184
  color: active ? void 0 : colors.secondary,
@@ -2045,7 +2186,7 @@ var RepoHeaderRow = ({
2045
2186
  children: repo
2046
2187
  }
2047
2188
  ),
2048
- /* @__PURE__ */ jsx(Text4, { dimColor: !active, children: " " + rule })
2189
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
2049
2190
  ] });
2050
2191
  };
2051
2192
  var MERGED_HOLD_MS = 3e3;
@@ -2110,48 +2251,48 @@ var ItemRow = ({
2110
2251
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
2111
2252
  if (item.kind === "subgroup-header")
2112
2253
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2113
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: " \xBB " }),
2114
- /* @__PURE__ */ jsx(Text4, { bold: true, children: item.label })
2254
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: " \xBB " }),
2255
+ /* @__PURE__ */ jsx(Text3, { bold: true, children: item.label })
2115
2256
  ] });
2116
2257
  if (item.kind === "show-more")
2117
2258
  return /* @__PURE__ */ jsxs(Box, { children: [
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` })
2259
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2260
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2261
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
2262
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `+${item.hidden.length} more` })
2122
2263
  ] });
2123
2264
  if (item.kind === "show-less")
2124
2265
  return /* @__PURE__ */ jsxs(Box, { children: [
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" })
2266
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2267
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2268
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
2269
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "show less" })
2129
2270
  ] });
2130
2271
  if (item.kind === "task") {
2131
2272
  const note = item.note ?? "";
2132
2273
  const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
2133
- const titleMax2 = Math.max(
2274
+ const titleMax = Math.max(
2134
2275
  1,
2135
2276
  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
2136
2277
  );
2137
2278
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
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 + " " }),
2279
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2280
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2281
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
2282
+ /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
2283
+ markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
2284
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: active, children: item.key + " " }),
2144
2285
  /* @__PURE__ */ jsx(
2145
- Text4,
2286
+ Text3,
2146
2287
  {
2147
2288
  bold: active || !!transient && isArrival(transient),
2148
2289
  dimColor: !!transient && isDeparture(transient),
2149
2290
  strikethrough: transient === "out",
2150
- children: truncate(item.summary, titleMax2)
2291
+ children: truncate(item.summary, titleMax)
2151
2292
  }
2152
2293
  ),
2153
2294
  item.pill && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2154
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2295
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
2155
2296
  /* @__PURE__ */ jsx(
2156
2297
  Pill,
2157
2298
  {
@@ -2162,127 +2303,39 @@ var ItemRow = ({
2162
2303
  }
2163
2304
  )
2164
2305
  ] }) : null,
2165
- note ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` ${note}` }) : null,
2306
+ note ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${note}` }) : null,
2166
2307
  farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2167
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2308
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
2168
2309
  /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2169
2310
  ] }) : null
2170
2311
  ] });
2171
2312
  }
2172
- const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
2173
- const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient && isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : transient && isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : healthIcon;
2174
- const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
2175
- const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
2176
- const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
2177
- const numStr = `#${item.number}`.padEnd(7);
2178
- const showAuthor = !!item.author && item.author !== login;
2179
- const sizeLabel = sizeOf(item);
2180
- const sizeParts = sizePartsOf(item);
2181
- const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
2182
- const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
2183
- const ageLabel2 = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
2184
- const mergedLabel = merged ? "merged" : "";
2185
- const pillCaps = [mergedLabel, farewellLabel].filter(Boolean).length * 2;
2186
- const repoLabel = depthOf(item) > 0 ? item.repo : "";
2187
- const implied = [...impliedLabels(item.repo), ...uniformLabels];
2188
- const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
2189
- const givingUp = {
2190
- author: false,
2191
- size: false,
2192
- threads: false,
2193
- age: false,
2194
- repo: false,
2195
- labels: 0
2196
- };
2197
- const LABEL_CELL_MAX = 24;
2198
- const labelCell = () => {
2199
- const shown = labelNames.slice(
2200
- 0,
2201
- Math.max(0, labelNames.length - givingUp.labels)
2202
- );
2203
- if (shown.length === 0) return "";
2204
- const fitted = [];
2205
- for (const name of shown) {
2206
- const next = [...fitted, name].join(", ");
2207
- if (next.length <= LABEL_CELL_MAX) fitted.push(name);
2208
- }
2209
- if (fitted.length === 0) {
2210
- return `\uF02B ${truncate(shown[0], LABEL_CELL_MAX)}`;
2313
+ const icon = merged ? {
2314
+ glyph: MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length],
2315
+ color: MERGED_COLOUR
2316
+ } : transient ? {
2317
+ glyph: isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : displayFor(item.health).glyph,
2318
+ color: TRANSIT_COLOUR[transient]
2319
+ } : void 0;
2320
+ const motion = transient === "out" ? "withdrawn" : transient && isDeparture(transient) ? "departing" : transient && isArrival(transient) ? "arriving" : void 0;
2321
+ const announcements = [
2322
+ ...merged ? [{ label: "merged", color: MERGED_COLOUR }] : [],
2323
+ ...farewellLabel ? [{ label: farewellLabel, color: TRANSIT_COLOUR.out }] : []
2324
+ ];
2325
+ return /* @__PURE__ */ jsx(
2326
+ PrRow,
2327
+ {
2328
+ item,
2329
+ active,
2330
+ login,
2331
+ cols,
2332
+ prefix,
2333
+ uniformLabels,
2334
+ icon,
2335
+ motion,
2336
+ announcements
2211
2337
  }
2212
- return `\uF02B ${fitted.join(", ")}`;
2213
- };
2214
- const widthOf2 = () => {
2215
- const suffix = [
2216
- givingUp.age ? "" : ageLabel2 || "",
2217
- // ASCII only, so no PUA double-width correction — see the label cell.
2218
- givingUp.size ? "" : sizeLabel || "",
2219
- givingUp.threads ? "" : unresolvedLabel,
2220
- showAuthor && !givingUp.author ? `by ${item.author}` : "",
2221
- mergedLabel
2222
- ].filter(Boolean).join(" ");
2223
- const cell = labelCell();
2224
- return 2 + prefix.length + 2 + 2 + 7 + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + suffix.length + pillCaps + 6;
2225
- };
2226
- const MIN_TITLE = 24;
2227
- for (const give of [
2228
- () => givingUp.author = true,
2229
- () => givingUp.labels = 1,
2230
- () => givingUp.size = true,
2231
- () => givingUp.threads = true,
2232
- () => givingUp.labels = 2,
2233
- () => givingUp.repo = true,
2234
- () => givingUp.age = true
2235
- ]) {
2236
- if (cols - widthOf2() >= MIN_TITLE) break;
2237
- give();
2238
- }
2239
- const labelLabel = labelCell();
2240
- const titleMax = Math.max(1, cols - widthOf2());
2241
- return /* @__PURE__ */ jsxs(Box, { children: [
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 }),
2247
- /* @__PURE__ */ jsx(
2248
- Text4,
2249
- {
2250
- bold: active || !!transient && isArrival(transient),
2251
- dimColor: !!transient && isDeparture(transient),
2252
- strikethrough: transient === "out",
2253
- children: truncate(item.title, titleMax) + " "
2254
- }
2255
- ),
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: [
2259
- " ",
2260
- /* @__PURE__ */ jsx(Text4, { color: colors.success, children: sizeParts.added }),
2261
- " ",
2262
- /* @__PURE__ */ jsx(Text4, { color: colors.error, children: sizeParts.removed })
2263
- ] }) : null,
2264
- unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
2265
- Text4,
2266
- {
2267
- bold: !spokeLast,
2268
- color: spokeLast ? colors.secondary : colors.accent,
2269
- children: " " + unresolvedLabel
2270
- }
2271
- ) : 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,
2277
- mergedLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2278
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2279
- /* @__PURE__ */ jsx(Pill, { color: MERGED_COLOUR, children: mergedLabel })
2280
- ] }) : null,
2281
- farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2282
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2283
- /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2284
- ] }) : null
2285
- ] });
2338
+ );
2286
2339
  };
2287
2340
  var useActionMenu = () => {
2288
2341
  const [actions, setActions] = useState(null);
@@ -2327,15 +2380,15 @@ var ActionMenu = ({
2327
2380
  paddingX: 1,
2328
2381
  marginTop: 1,
2329
2382
  children: [
2330
- /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: title }),
2331
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(32) }),
2383
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: title }),
2384
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2332
2385
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
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 })
2386
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: i === cursor ? "\u276F " : " " }),
2387
+ /* @__PURE__ */ jsx(Text3, { bold: i === cursor, children: a.label }),
2388
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + a.hint })
2336
2389
  ] }, a.label)),
2337
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(32) }),
2338
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2390
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2391
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
2339
2392
  ]
2340
2393
  }
2341
2394
  );
@@ -2383,11 +2436,11 @@ var LegendGroup = ({
2383
2436
  width,
2384
2437
  marginRight
2385
2438
  }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, marginRight, children: [
2386
- pinned !== void 0 ? /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2439
+ pinned !== void 0 ? /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
2387
2440
  lines.map(
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 })
2441
+ (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: [
2442
+ /* @__PURE__ */ jsx(Text3, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2443
+ /* @__PURE__ */ jsx(Text3, { wrap: "truncate-end", children: line.row.label })
2391
2444
  ] }, index)
2392
2445
  )
2393
2446
  ] });
@@ -2508,8 +2561,8 @@ var HelpModal = ({
2508
2561
  width: contentWidth + MODAL_CHROME_COLS,
2509
2562
  children: [
2510
2563
  /* @__PURE__ */ jsxs(Box, { children: [
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
2564
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: "Legend" }),
2565
+ scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2513
2566
  ] }),
2514
2567
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
2515
2568
  LegendGroup,
@@ -2521,8 +2574,8 @@ var HelpModal = ({
2521
2574
  },
2522
2575
  group[0]?.title ?? index
2523
2576
  )) }),
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" })
2577
+ tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2578
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
2526
2579
  ]
2527
2580
  }
2528
2581
  );
@@ -2537,13 +2590,13 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2537
2590
  paddingX: 1,
2538
2591
  width: Math.min(COLS, 78),
2539
2592
  children: [
2540
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2541
- /* @__PURE__ */ jsx(Text4, { children: item.title }),
2593
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2594
+ /* @__PURE__ */ jsx(Text3, { children: item.title }),
2542
2595
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2543
- /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: section.heading }),
2544
- section.lines.map((line, i) => /* @__PURE__ */ jsx(Text4, { children: " " + line }, i))
2596
+ /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: section.heading }),
2597
+ section.lines.map((line, i) => /* @__PURE__ */ jsx(Text3, { children: " " + line }, i))
2545
2598
  ] }, section.heading)),
2546
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "esc \xB7 e close" }) })
2599
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "esc \xB7 e close" }) })
2547
2600
  ]
2548
2601
  }
2549
2602
  );
@@ -2569,21 +2622,21 @@ var RepoPicker = ({
2569
2622
  paddingX: 1,
2570
2623
  minWidth: 42,
2571
2624
  children: [
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) }),
2625
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: `Filter by repo (${selected.size} on)` }),
2626
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2574
2627
  visible.map((repo, i) => {
2575
2628
  const idx = start + i;
2576
2629
  const on = selected.has(repo);
2577
2630
  const active = idx === cursor;
2578
2631
  return /* @__PURE__ */ jsxs(Box, { children: [
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 })
2632
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2633
+ /* @__PURE__ */ jsx(Text3, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2634
+ /* @__PURE__ */ jsx(Text3, { bold: active, children: repo })
2582
2635
  ] }, repo);
2583
2636
  }),
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" })
2637
+ repos.length > budget ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2638
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2639
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
2587
2640
  ]
2588
2641
  }
2589
2642
  );
@@ -3287,11 +3340,11 @@ var BrowseScreen = ({
3287
3340
  const query = palette.trim();
3288
3341
  const ticketKey = jiraKeyRe ? query.toUpperCase().match(jiraKeyRe)?.[0] : void 0;
3289
3342
  const commands = [];
3290
- const rich = (key, where) => /* @__PURE__ */ jsxs(Text4, { children: [
3343
+ const rich = (key, where) => /* @__PURE__ */ jsxs(Text3, { children: [
3291
3344
  "Open ",
3292
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: key }),
3345
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: key }),
3293
3346
  " ",
3294
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: where })
3347
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: where })
3295
3348
  ] });
3296
3349
  const labels = /* @__PURE__ */ new Map();
3297
3350
  if (ticketKey && jiraBase) {
@@ -3423,8 +3476,8 @@ var BrowseScreen = ({
3423
3476
  }
3424
3477
  ) : null,
3425
3478
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
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" })
3479
+ /* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
3480
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
3428
3481
  ] }) : null,
3429
3482
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
3430
3483
  Tabs,
@@ -3460,14 +3513,14 @@ var BrowseScreen = ({
3460
3513
  }
3461
3514
  ) }),
3462
3515
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
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"}` })
3516
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: " / " }),
3517
+ /* @__PURE__ */ jsx(Text3, { children: search }),
3518
+ searchInput ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
3519
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3467
3520
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
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" })
3521
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
3522
+ /* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
3523
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " f edit \xB7 esc clear" })
3471
3524
  ] }) : null,
3472
3525
  /* @__PURE__ */ jsxs(Box, { children: [
3473
3526
  /* @__PURE__ */ jsxs(
@@ -3505,7 +3558,7 @@ var BrowseScreen = ({
3505
3558
  },
3506
3559
  `${viewStart + i}:${rowKey(item)}`
3507
3560
  )) }),
3508
- hasMore && /* @__PURE__ */ jsxs(Text4, { dimColor: true, children: [
3561
+ hasMore && /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
3509
3562
  " ",
3510
3563
  "\u2193 ",
3511
3564
  section.items.length - viewStart - visibleCount,
@@ -3539,7 +3592,7 @@ var BrowseScreen = ({
3539
3592
  }
3540
3593
  ) : null
3541
3594
  ] }),
3542
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text4, { color: colors.success, children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3595
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3543
3596
  ]
3544
3597
  }
3545
3598
  );
@@ -3554,7 +3607,7 @@ var NoRowsScreen = ({
3554
3607
  });
3555
3608
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3556
3609
  /* @__PURE__ */ jsx(StatusMessage, { variant: reason === "empty" ? "info" : "error", children: reason === "empty" ? "Nothing open." : `Fetch failed \u2014 ${detail ?? "no reason reported"}` }),
3557
- reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text4, { dimColor: true, children: detail }) }) : null,
3610
+ reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text3, { dimColor: true, children: detail }) }) : null,
3558
3611
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
3559
3612
  FooterHints,
3560
3613
  {
@@ -3722,10 +3775,13 @@ var App = ({
3722
3775
  const running = [...heldSince].filter(([tab]) => held.has(tab));
3723
3776
  if (running.length === 0) return;
3724
3777
  const remaining = (at) => at + TRANSIT_HOLD_MS - Date.now();
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)))));
3778
+ const timer = setTimeout(
3779
+ () => {
3780
+ for (const [tab, at] of running)
3781
+ if (remaining(at) <= 0) settleTab(tab, transients);
3782
+ },
3783
+ Math.max(0, Math.min(...running.map(([, at]) => remaining(at))))
3784
+ );
3729
3785
  return () => clearTimeout(timer);
3730
3786
  }, [transients, heldSince, state.phase]);
3731
3787
  const pendingSummary = useMemo(
@@ -4084,4 +4140,4 @@ var matchesFilter = (repo, filter) => {
4084
4140
  };
4085
4141
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
4086
4142
 
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 };
4143
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PrRow, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.59.1",
3
+ "version": "0.61.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",