@kud/gh-ink 0.60.0 → 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,6 +748,213 @@ 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":
@@ -848,51 +1055,6 @@ var StatusStripLine = ({
848
1055
  age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${age}` }) : null
849
1056
  ] });
850
1057
  };
851
- var healthDisplay = {
852
- none: { glyph: " ", color: colors.muted },
853
- draft: { glyph: "~", color: colors.muted },
854
- "ci-fail": { glyph: "\u2717", color: colors.error },
855
- conflict: { glyph: "!", color: colors.accent },
856
- "changes-req": { glyph: "\xB1", color: colors.warning },
857
- threads: { glyph: "\u25C6", color: colors.accent },
858
- pending: { glyph: "*", color: colors.warning },
859
- approved: { glyph: "\u2713", color: colors.success },
860
- waiting: { glyph: "\xB7", color: colors.muted },
861
- // The one glyph here that MEANS its state rather than merely differing from
862
- // the others. `✓` and `✗` you never have to think about; `»` you did — it was
863
- // chosen to suggest "moved through", which is a stretch nobody reads at a
864
- // glance. This is Nerd Font's git-merge icon, written as an escape because raw
865
- // PUA bytes get mangled by editors and diffs.
866
- //
867
- // The cost, stated rather than discovered: a terminal without a Nerd Font
868
- // draws a box here where it used to draw a chevron. That is a trade this
869
- // package has already made once — the inbox hardcodes the comment glyph at
870
- // \u{f086} — and one state degrading to tofu is cheaper than ten states none
871
- // of which say what they mean.
872
- merged: { glyph: "\uE727", color: colors.muted },
873
- // Nerd Font's closed-pull-request icon, for the same reason as `merged` above
874
- // and with the same caveat about a font that lacks it. `×` had a second problem
875
- // beyond being arbitrary: it is one stroke away from `✗` (ci failing) two rows
876
- // up in this very map, and the shape-distinctness this file turns on is a
877
- // silhouette test, not a codepoint one.
878
- closed: { glyph: "\uEBDA", color: colors.muted }
879
- };
880
- var healthGlyph = (h) => healthDisplay[h].glyph;
881
- var healthColor = (h) => healthDisplay[h].color;
882
- var healthLegend = [
883
- ["approved", "Approved \xB7 ready to merge"],
884
- ["pending", "Checks running"],
885
- ["ci-fail", "CI failing"],
886
- ["conflict", "Merge conflict"],
887
- ["changes-req", "Changes requested"],
888
- ["threads", "Open threads \xB7 your reply"],
889
- ["waiting", "Awaiting review"],
890
- ["draft", "Draft"],
891
- ["merged", "Merged"],
892
- ["closed", "Closed"]
893
- ];
894
- var UNREAD_DISPLAY = { glyph: "\u25CB", color: colors.muted };
895
- var displayFor = (h) => h ? healthDisplay[h] : UNREAD_DISPLAY;
896
1058
 
897
1059
  // src/inbox/cache.ts
898
1060
  var cacheTtlMs = () => inboxConfig().cacheTtlMs;
@@ -1385,11 +1547,6 @@ var withHeaders = (items, idx) => {
1385
1547
  start--;
1386
1548
  return start;
1387
1549
  };
1388
- var truncate = (str, max) => {
1389
- if (str.length <= max) return str;
1390
- const half = Math.floor((max - 1) / 2);
1391
- return `${str.slice(0, half)}\u2026${str.slice(-half)}`;
1392
- };
1393
1550
  var clipboard = (text) => {
1394
1551
  const p = spawn("pbcopy", [], { stdio: "pipe" });
1395
1552
  p.stdin.write(text);
@@ -1574,37 +1731,6 @@ var FRAME_CHROME_COLS = 2 + FRAME_PAD_X * 2;
1574
1731
  var COLS = (process.stdout.columns ?? 120) - FRAME_CHROME_COLS;
1575
1732
  var MODAL_CHROME_COLS = 2 + 1 * 2;
1576
1733
  var OVERLAY_BG = "#22222e";
1577
- var DimContext = createContext(false);
1578
- var BACKDROP_FG = "#5a5a68";
1579
- var BACKDROP_FG_RECESSED = "#3a3a46";
1580
- var backdropStyle = (props) => ({
1581
- color: props.dimColor ? BACKDROP_FG_RECESSED : BACKDROP_FG,
1582
- dimColor: false,
1583
- bold: false,
1584
- italic: false,
1585
- inverse: false,
1586
- backgroundColor: void 0
1587
- });
1588
- var Text4 = ({ children, ...props }) => {
1589
- const dimmed = useContext(DimContext);
1590
- return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, ...backdropStyle(props), children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
1591
- };
1592
- var useBackdropped = () => useContext(DimContext);
1593
- var Backdrop = ({
1594
- dimmed,
1595
- absolute,
1596
- height,
1597
- children
1598
- }) => /* @__PURE__ */ jsx(DimContext.Provider, { value: dimmed, children: absolute ? /* @__PURE__ */ jsx(
1599
- Box,
1600
- {
1601
- position: "absolute",
1602
- flexDirection: "column",
1603
- width: "100%",
1604
- height,
1605
- children
1606
- }
1607
- ) : children });
1608
1734
  var workCount = (s) => {
1609
1735
  let n = 0;
1610
1736
  for (let i = 0; i < s.items.length; i++) {
@@ -1981,13 +2107,13 @@ var InboxHeader = ({
1981
2107
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - budgetSeg.length - statusSeg.length
1982
2108
  );
1983
2109
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1984
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: brand }),
1985
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: countSeg }),
1986
- userSeg ? /* @__PURE__ */ jsx(Text4, { children: userSeg }) : null,
1987
- scopeLabel ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: workLabel }) : null,
1988
- 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,
1989
2115
  statusText ? /* @__PURE__ */ jsx(
1990
- Text4,
2116
+ Text3,
1991
2117
  {
1992
2118
  color: statusColor,
1993
2119
  dimColor: !statusColor,
@@ -1995,7 +2121,7 @@ var InboxHeader = ({
1995
2121
  children: statusSeg
1996
2122
  }
1997
2123
  ) : null,
1998
- /* @__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) })
1999
2125
  ] });
2000
2126
  };
2001
2127
  var stripSignature = (strip) => strip === null ? "\u2205" : strip.items.map((i) => `${i.key}=${i.state}${i.alarm ? "\u25B2" : ""}`).join(",");
@@ -2019,24 +2145,24 @@ var CiStatusLine = ({
2019
2145
  const name = job ?? "ci";
2020
2146
  if (state.kind === "loading")
2021
2147
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2022
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " \xB7 " }),
2023
- /* @__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` })
2024
2150
  ] });
2025
2151
  if (state.kind === "error")
2026
2152
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2027
- /* @__PURE__ */ jsx(Text4, { color: colors.error, bold: true, children: " \u2717 " }),
2028
- /* @__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` })
2029
2155
  ] });
2030
2156
  const { status } = state;
2031
2157
  const [, color] = jenkinsResultDisplay(status.result, status.building);
2032
2158
  const label = status.building ? "BUILDING" : status.result;
2033
2159
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2034
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " }),
2035
- /* @__PURE__ */ jsx(Text4, { color, bold: true, children: "\u25CF " }),
2036
- /* @__PURE__ */ jsx(Text4, { bold: true, children: status.job }),
2037
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: " " + label }),
2038
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: " #" + status.buildNumber }),
2039
- 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
2040
2166
  ] });
2041
2167
  };
2042
2168
  var FENCE_COLS = 46;
@@ -2049,10 +2175,10 @@ var RepoHeaderRow = ({
2049
2175
  const fill = Math.max(4, FENCE_COLS - label.length);
2050
2176
  const rule = "\u2500".repeat(fill);
2051
2177
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2052
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2053
- /* @__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 " }),
2054
2180
  /* @__PURE__ */ jsx(
2055
- Text4,
2181
+ Text3,
2056
2182
  {
2057
2183
  dimColor: false,
2058
2184
  color: active ? void 0 : colors.secondary,
@@ -2060,7 +2186,7 @@ var RepoHeaderRow = ({
2060
2186
  children: repo
2061
2187
  }
2062
2188
  ),
2063
- /* @__PURE__ */ jsx(Text4, { dimColor: !active, children: " " + rule })
2189
+ /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
2064
2190
  ] });
2065
2191
  };
2066
2192
  var MERGED_HOLD_MS = 3e3;
@@ -2125,48 +2251,48 @@ var ItemRow = ({
2125
2251
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
2126
2252
  if (item.kind === "subgroup-header")
2127
2253
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2128
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: " \xBB " }),
2129
- /* @__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 })
2130
2256
  ] });
2131
2257
  if (item.kind === "show-more")
2132
2258
  return /* @__PURE__ */ jsxs(Box, { children: [
2133
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2134
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2135
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: active ? "\u21B5 " : " " }),
2136
- /* @__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` })
2137
2263
  ] });
2138
2264
  if (item.kind === "show-less")
2139
2265
  return /* @__PURE__ */ jsxs(Box, { children: [
2140
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2141
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2142
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: active ? "\u21B5 " : " " }),
2143
- /* @__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" })
2144
2270
  ] });
2145
2271
  if (item.kind === "task") {
2146
2272
  const note = item.note ?? "";
2147
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";
2148
- const titleMax2 = Math.max(
2274
+ const titleMax = Math.max(
2149
2275
  1,
2150
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
2151
2277
  );
2152
2278
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
2153
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2154
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2155
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: parent ? "\u252C " : " " }),
2156
- /* @__PURE__ */ jsx(Text4, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
2157
- markerCols > 0 ? /* @__PURE__ */ jsx(Text4, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
2158
- /* @__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 + " " }),
2159
2285
  /* @__PURE__ */ jsx(
2160
- Text4,
2286
+ Text3,
2161
2287
  {
2162
2288
  bold: active || !!transient && isArrival(transient),
2163
2289
  dimColor: !!transient && isDeparture(transient),
2164
2290
  strikethrough: transient === "out",
2165
- children: truncate(item.summary, titleMax2)
2291
+ children: truncate(item.summary, titleMax)
2166
2292
  }
2167
2293
  ),
2168
2294
  item.pill && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2169
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2295
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
2170
2296
  /* @__PURE__ */ jsx(
2171
2297
  Pill,
2172
2298
  {
@@ -2177,127 +2303,39 @@ var ItemRow = ({
2177
2303
  }
2178
2304
  )
2179
2305
  ] }) : null,
2180
- note ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` ${note}` }) : null,
2306
+ note ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${note}` }) : null,
2181
2307
  farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2182
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2308
+ /* @__PURE__ */ jsx(Text3, { children: " " }),
2183
2309
  /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2184
2310
  ] }) : null
2185
2311
  ] });
2186
2312
  }
2187
- const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
2188
- 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;
2189
- const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
2190
- const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
2191
- const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
2192
- const numStr = `#${item.number}`.padEnd(7);
2193
- const showAuthor = !!item.author && item.author !== login;
2194
- const sizeLabel = sizeOf(item);
2195
- const sizeParts = sizePartsOf(item);
2196
- const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
2197
- const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
2198
- const ageLabel2 = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
2199
- const mergedLabel = merged ? "merged" : "";
2200
- const pillCaps = [mergedLabel, farewellLabel].filter(Boolean).length * 2;
2201
- const repoLabel = depthOf(item) > 0 ? item.repo : "";
2202
- const implied = [...impliedLabels(item.repo), ...uniformLabels];
2203
- const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
2204
- const givingUp = {
2205
- author: false,
2206
- size: false,
2207
- threads: false,
2208
- age: false,
2209
- repo: false,
2210
- labels: 0
2211
- };
2212
- const LABEL_CELL_MAX = 24;
2213
- const labelCell = () => {
2214
- const shown = labelNames.slice(
2215
- 0,
2216
- Math.max(0, labelNames.length - givingUp.labels)
2217
- );
2218
- if (shown.length === 0) return "";
2219
- const fitted = [];
2220
- for (const name of shown) {
2221
- const next = [...fitted, name].join(", ");
2222
- if (next.length <= LABEL_CELL_MAX) fitted.push(name);
2223
- }
2224
- if (fitted.length === 0) {
2225
- 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
2226
2337
  }
2227
- return `\uF02B ${fitted.join(", ")}`;
2228
- };
2229
- const widthOf2 = () => {
2230
- const suffix = [
2231
- givingUp.age ? "" : ageLabel2 || "",
2232
- // ASCII only, so no PUA double-width correction — see the label cell.
2233
- givingUp.size ? "" : sizeLabel || "",
2234
- givingUp.threads ? "" : unresolvedLabel,
2235
- showAuthor && !givingUp.author ? `by ${item.author}` : "",
2236
- mergedLabel
2237
- ].filter(Boolean).join(" ");
2238
- const cell = labelCell();
2239
- return 2 + prefix.length + 2 + 2 + 7 + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + suffix.length + pillCaps + 6;
2240
- };
2241
- const MIN_TITLE = 24;
2242
- for (const give of [
2243
- () => givingUp.author = true,
2244
- () => givingUp.labels = 1,
2245
- () => givingUp.size = true,
2246
- () => givingUp.threads = true,
2247
- () => givingUp.labels = 2,
2248
- () => givingUp.repo = true,
2249
- () => givingUp.age = true
2250
- ]) {
2251
- if (cols - widthOf2() >= MIN_TITLE) break;
2252
- give();
2253
- }
2254
- const labelLabel = labelCell();
2255
- const titleMax = Math.max(1, cols - widthOf2());
2256
- return /* @__PURE__ */ jsxs(Box, { children: [
2257
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2258
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: prefix }),
2259
- /* @__PURE__ */ jsx(Text4, { color, bold: true, children: icon + " " }),
2260
- /* @__PURE__ */ jsx(Text4, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
2261
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: numStr }),
2262
- /* @__PURE__ */ jsx(
2263
- Text4,
2264
- {
2265
- bold: active || !!transient && isArrival(transient),
2266
- dimColor: !!transient && isDeparture(transient),
2267
- strikethrough: transient === "out",
2268
- children: truncate(item.title, titleMax) + " "
2269
- }
2270
- ),
2271
- labelLabel ? /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: labelLabel + " " }) : null,
2272
- repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: repoLabel }) : null,
2273
- sizeParts && !givingUp.size ? /* @__PURE__ */ jsxs(Text4, { children: [
2274
- " ",
2275
- /* @__PURE__ */ jsx(Text4, { color: colors.success, children: sizeParts.added }),
2276
- " ",
2277
- /* @__PURE__ */ jsx(Text4, { color: colors.error, children: sizeParts.removed })
2278
- ] }) : null,
2279
- unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
2280
- Text4,
2281
- {
2282
- bold: !spokeLast,
2283
- color: spokeLast ? colors.secondary : colors.accent,
2284
- children: " " + unresolvedLabel
2285
- }
2286
- ) : null,
2287
- showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text4, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2288
- ageLabel2 && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
2289
- /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: " " + agePair.activity }),
2290
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` (${agePair.lifetime})` })
2291
- ] }) : /* @__PURE__ */ jsx(Text4, { color: colors.secondary, children: " " + ageLabel2 }) : null,
2292
- mergedLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2293
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2294
- /* @__PURE__ */ jsx(Pill, { color: MERGED_COLOUR, children: mergedLabel })
2295
- ] }) : null,
2296
- farewellLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2297
- /* @__PURE__ */ jsx(Text4, { children: " " }),
2298
- /* @__PURE__ */ jsx(Pill, { color: TRANSIT_COLOUR.out, children: farewellLabel })
2299
- ] }) : null
2300
- ] });
2338
+ );
2301
2339
  };
2302
2340
  var useActionMenu = () => {
2303
2341
  const [actions, setActions] = useState(null);
@@ -2342,15 +2380,15 @@ var ActionMenu = ({
2342
2380
  paddingX: 1,
2343
2381
  marginTop: 1,
2344
2382
  children: [
2345
- /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: title }),
2346
- /* @__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) }),
2347
2385
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
2348
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: i === cursor ? "\u276F " : " " }),
2349
- /* @__PURE__ */ jsx(Text4, { bold: i === cursor, children: a.label }),
2350
- /* @__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 })
2351
2389
  ] }, a.label)),
2352
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(32) }),
2353
- /* @__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" })
2354
2392
  ]
2355
2393
  }
2356
2394
  );
@@ -2398,11 +2436,11 @@ var LegendGroup = ({
2398
2436
  width,
2399
2437
  marginRight
2400
2438
  }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, marginRight, children: [
2401
- 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,
2402
2440
  lines.map(
2403
- (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: [
2404
- /* @__PURE__ */ jsx(Text4, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
2405
- /* @__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 })
2406
2444
  ] }, index)
2407
2445
  )
2408
2446
  ] });
@@ -2523,8 +2561,8 @@ var HelpModal = ({
2523
2561
  width: contentWidth + MODAL_CHROME_COLS,
2524
2562
  children: [
2525
2563
  /* @__PURE__ */ jsxs(Box, { children: [
2526
- /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: "Legend" }),
2527
- 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
2528
2566
  ] }),
2529
2567
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
2530
2568
  LegendGroup,
@@ -2536,8 +2574,8 @@ var HelpModal = ({
2536
2574
  },
2537
2575
  group[0]?.title ?? index
2538
2576
  )) }),
2539
- tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
2540
- /* @__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" })
2541
2579
  ]
2542
2580
  }
2543
2581
  );
@@ -2552,13 +2590,13 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2552
2590
  paddingX: 1,
2553
2591
  width: Math.min(COLS, 78),
2554
2592
  children: [
2555
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2556
- /* @__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 }),
2557
2595
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2558
- /* @__PURE__ */ jsx(Text4, { bold: true, dimColor: true, children: section.heading }),
2559
- 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))
2560
2598
  ] }, section.heading)),
2561
- /* @__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" }) })
2562
2600
  ]
2563
2601
  }
2564
2602
  );
@@ -2584,21 +2622,21 @@ var RepoPicker = ({
2584
2622
  paddingX: 1,
2585
2623
  minWidth: 42,
2586
2624
  children: [
2587
- /* @__PURE__ */ jsx(Text4, { color: colors.info, bold: true, children: `Filter by repo (${selected.size} on)` }),
2588
- /* @__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) }),
2589
2627
  visible.map((repo, i) => {
2590
2628
  const idx = start + i;
2591
2629
  const on = selected.has(repo);
2592
2630
  const active = idx === cursor;
2593
2631
  return /* @__PURE__ */ jsxs(Box, { children: [
2594
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: active ? "\u276F " : " " }),
2595
- /* @__PURE__ */ jsx(Text4, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2596
- /* @__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 })
2597
2635
  ] }, repo);
2598
2636
  }),
2599
- repos.length > budget ? /* @__PURE__ */ jsx(Text4, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
2600
- /* @__PURE__ */ jsx(Text4, { dimColor: true, children: "\u2500".repeat(36) }),
2601
- /* @__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" })
2602
2640
  ]
2603
2641
  }
2604
2642
  );
@@ -3302,11 +3340,11 @@ var BrowseScreen = ({
3302
3340
  const query = palette.trim();
3303
3341
  const ticketKey = jiraKeyRe ? query.toUpperCase().match(jiraKeyRe)?.[0] : void 0;
3304
3342
  const commands = [];
3305
- const rich = (key, where) => /* @__PURE__ */ jsxs(Text4, { children: [
3343
+ const rich = (key, where) => /* @__PURE__ */ jsxs(Text3, { children: [
3306
3344
  "Open ",
3307
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: key }),
3345
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: key }),
3308
3346
  " ",
3309
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: where })
3347
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: where })
3310
3348
  ] });
3311
3349
  const labels = /* @__PURE__ */ new Map();
3312
3350
  if (ticketKey && jiraBase) {
@@ -3438,8 +3476,8 @@ var BrowseScreen = ({
3438
3476
  }
3439
3477
  ) : null,
3440
3478
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3441
- /* @__PURE__ */ jsx(Text4, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
3442
- /* @__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" })
3443
3481
  ] }) : null,
3444
3482
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
3445
3483
  Tabs,
@@ -3475,14 +3513,14 @@ var BrowseScreen = ({
3475
3513
  }
3476
3514
  ) }),
3477
3515
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3478
- /* @__PURE__ */ jsx(Text4, { color: colors.info, children: " / " }),
3479
- /* @__PURE__ */ jsx(Text4, { children: search }),
3480
- searchInput ? /* @__PURE__ */ jsx(Text4, { color: colors.info, children: "\u258F" }) : null,
3481
- /* @__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"}` })
3482
3520
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3483
- /* @__PURE__ */ jsx(Text4, { color: colors.accent, children: " \u25C9 " }),
3484
- /* @__PURE__ */ jsx(Text4, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
3485
- /* @__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" })
3486
3524
  ] }) : null,
3487
3525
  /* @__PURE__ */ jsxs(Box, { children: [
3488
3526
  /* @__PURE__ */ jsxs(
@@ -3520,7 +3558,7 @@ var BrowseScreen = ({
3520
3558
  },
3521
3559
  `${viewStart + i}:${rowKey(item)}`
3522
3560
  )) }),
3523
- hasMore && /* @__PURE__ */ jsxs(Text4, { dimColor: true, children: [
3561
+ hasMore && /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
3524
3562
  " ",
3525
3563
  "\u2193 ",
3526
3564
  section.items.length - viewStart - visibleCount,
@@ -3554,7 +3592,7 @@ var BrowseScreen = ({
3554
3592
  }
3555
3593
  ) : null
3556
3594
  ] }),
3557
- /* @__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 }) })
3558
3596
  ]
3559
3597
  }
3560
3598
  );
@@ -3569,7 +3607,7 @@ var NoRowsScreen = ({
3569
3607
  });
3570
3608
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3571
3609
  /* @__PURE__ */ jsx(StatusMessage, { variant: reason === "empty" ? "info" : "error", children: reason === "empty" ? "Nothing open." : `Fetch failed \u2014 ${detail ?? "no reason reported"}` }),
3572
- 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,
3573
3611
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
3574
3612
  FooterHints,
3575
3613
  {
@@ -3737,10 +3775,13 @@ var App = ({
3737
3775
  const running = [...heldSince].filter(([tab]) => held.has(tab));
3738
3776
  if (running.length === 0) return;
3739
3777
  const remaining = (at) => at + TRANSIT_HOLD_MS - Date.now();
3740
- const timer = setTimeout(() => {
3741
- for (const [tab, at] of running)
3742
- if (remaining(at) <= 0) settleTab(tab, transients);
3743
- }, 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
+ );
3744
3785
  return () => clearTimeout(timer);
3745
3786
  }, [transients, heldSince, state.phase]);
3746
3787
  const pendingSummary = useMemo(
@@ -4099,4 +4140,4 @@ var matchesFilter = (repo, filter) => {
4099
4140
  };
4100
4141
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
4101
4142
 
4102
- 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.60.0",
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",