@kud/gh-ink 0.53.0 → 0.54.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
@@ -66,19 +66,26 @@ type SidebarRow = {
66
66
  */
67
67
  url?: string;
68
68
  };
69
+ /**
70
+ * Plain data, deliberately: a host caches its last fetch to disk and paints it
71
+ * on the next launch, and this rides along in that file. A function on it would
72
+ * not survive the round trip, so the host's words for `live` travel as a prop
73
+ * on `SidePanel` instead — vocabulary is configuration, not data that goes
74
+ * stale.
75
+ */
69
76
  type Sidebar = {
70
77
  /** Heading for the rail. The host's vocabulary, never ours. */
71
78
  title: string;
72
79
  rows: SidebarRow[];
73
- /**
74
- * What to call `live` on screen, in the host's words — `4 on board` and `off
75
- * board` on a ticket cockpit, say. Takes the number rather than a word pair
76
- * because the two states need not share a sentence shape: "off board" has no
77
- * N in it. `live` itself stays a number, so the absent-draws-nothing contract
78
- * and the host's sort are untouched.
79
- */
80
- liveLabel?: (live: number) => string;
81
80
  };
81
+ /**
82
+ * What to call `live` on screen, in the host's words — `4 on board` and `off
83
+ * board` on a ticket cockpit, say. Takes the number rather than a word pair
84
+ * because the two states need not share a sentence shape: "off board" has no
85
+ * N in it. `live` itself stays a number, so the absent-draws-nothing contract
86
+ * and the host's sort are untouched.
87
+ */
88
+ type LiveLabel = (live: number) => string;
82
89
  /**
83
90
  * The rail's width, INCLUDING its rule and padding. Exported because the list
84
91
  * beside it has to shrink by exactly this much: every row in that list truncates
@@ -91,8 +98,18 @@ type Sidebar = {
91
98
  * word and left the rail listing keys with an ellipsis after them. The key line
92
99
  * was never the constraint — the label is what makes a key readable, and a rail
93
100
  * you cannot read is forty columns spent on nothing.
101
+ *
102
+ * Widened again once the label became the row's first line: at 45 usable
103
+ * columns a real epic title still lost its last two words. But a 200-column
104
+ * terminal has room to give where a 120-column one does not — at 120 a rail
105
+ * this wide leaves the list 52 columns, which elides a PR title past its own
106
+ * key. So this is the CEILING, and `railWidth` sizes the rail to the frame:
107
+ * a third of it, never below the 52 the grid was laid out for, never above.
94
108
  */
95
- declare const SIDEBAR_COLS = 52;
109
+ declare const SIDEBAR_COLS = 64;
110
+ declare const MIN_SIDEBAR_COLS = 52;
111
+ /** How wide the rail is in a frame of `cols` — pass the same number to `width`. */
112
+ declare const railWidth: (cols: number) => number;
96
113
  /**
97
114
  * The plain-text facts of a row, for a host or a test that wants them as one
98
115
  * string: how far through, then how much is moving. Either half may be missing
@@ -100,7 +117,7 @@ declare const SIDEBAR_COLS = 52;
100
117
  * counted, because "nothing is moving" is exactly the thing a roadmap is read
101
118
  * to notice.
102
119
  */
103
- declare const counts: (row: SidebarRow, liveLabel?: (live: number) => string) => string;
120
+ declare const counts: (row: SidebarRow, liveLabel?: LiveLabel) => string;
104
121
  /**
105
122
  * How many rows fit in `height` — one fewer than would physically fit whenever
106
123
  * that means anything is left over, to buy the line that says so.
@@ -126,8 +143,11 @@ declare const railCapacity: (height: number, rows: number) => number;
126
143
  * the arrows are pointed at this rail at all, are the host's state — which is
127
144
  * what lets a screen with two focus regions have exactly one of them lit.
128
145
  */
129
- declare const SidePanel: ({ sidebar, height, focused, cursor, }: {
146
+ declare const SidePanel: ({ sidebar, liveLabel, width, height, focused, cursor, }: {
130
147
  sidebar: Sidebar;
148
+ liveLabel?: LiveLabel;
149
+ /** From `railWidth`, so the list beside it can subtract the same number. */
150
+ width?: number;
131
151
  height?: number;
132
152
  /** The arrows are pointed here, so this rail draws the cursor. */
133
153
  focused?: boolean;
@@ -192,12 +212,20 @@ type CachedCockpit = {
192
212
  login: string;
193
213
  at: number;
194
214
  budget?: InboxBudget;
215
+ /**
216
+ * The rail, when the host supplied one. Cached beside the rows because it is
217
+ * painted beside them: a launch that trusts the cache never refetches, so a
218
+ * rail left out of the file is a rail that does not exist until the next
219
+ * refresh — `i` did nothing and the footer did not even offer it.
220
+ */
221
+ sidebar?: Sidebar;
195
222
  };
196
223
  declare const readCache: (key: string) => CachedCockpit | null;
197
224
  declare const writeCache: (key: string, data: {
198
225
  sections: Section[];
199
226
  login: string;
200
227
  budget?: InboxBudget;
228
+ sidebar?: Sidebar;
201
229
  }) => void;
202
230
 
203
231
  type DetailContext = {
@@ -386,7 +414,7 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
386
414
  cursor: number;
387
415
  }) => React.JSX.Element;
388
416
  declare const signatureOf: (sections: Section[]) => string;
389
- declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, }: {
417
+ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, }: {
390
418
  fetcher: () => Promise<{
391
419
  sections: Section[];
392
420
  login: string;
@@ -409,6 +437,13 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
409
437
  sidebar?: Sidebar;
410
438
  }>;
411
439
  cacheKey?: string;
440
+ /**
441
+ * What the rail calls a row's `live` count, in this host's words — `4 on
442
+ * board` / `off board` on a ticket cockpit. A prop rather than part of the
443
+ * fetch result because it is vocabulary, not data: it never goes stale, and
444
+ * a function would not survive the cache the sidebar is painted from.
445
+ */
446
+ liveLabel?: LiveLabel;
412
447
  title?: string;
413
448
  detailFor?: (ctx: DetailContext) => React.ReactNode;
414
449
  /** A two-way repo split, and what to call the side on screen. See OriginSplit. */
@@ -461,4 +496,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
461
496
  */
462
497
  declare const parsePatterns: (value: string) => string[];
463
498
 
464
- export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type RepoFilter, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
499
+ export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, type RepoFilter, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railWidth, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/dist/index.js CHANGED
@@ -585,18 +585,19 @@ var HealthPanel = ({
585
585
  ] }) }) : note ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.success, children: " " + note }) }) : null
586
586
  ] });
587
587
  };
588
- var SIDEBAR_COLS = 52;
588
+ var SIDEBAR_COLS = 64;
589
+ var MIN_SIDEBAR_COLS = 52;
590
+ var railWidth = (cols) => Math.max(MIN_SIDEBAR_COLS, Math.min(SIDEBAR_COLS, Math.round(cols / 3)));
589
591
  var RULE = 1;
590
592
  var PAD = 2;
591
- var CONTENT = SIDEBAR_COLS - RULE - PAD;
593
+ var contentOf = (width) => width - RULE - PAD;
592
594
  var MARKS = 4;
593
- var LABEL_COLS = CONTENT - MARKS;
594
595
  var FACTS_INDENT = 6;
595
596
  var KEY_COLS = 10;
596
597
  var BAR_COLS = 10;
597
598
  var FRACTION_COLS = 5;
598
599
  var GUTTER = " ";
599
- var LIVE_COLS = CONTENT - FACTS_INDENT - KEY_COLS - BAR_COLS - FRACTION_COLS - GUTTER.length * 3;
600
+ var FACTS_FIXED = FACTS_INDENT + KEY_COLS + BAR_COLS + FRACTION_COLS + GUTTER.length * 3;
600
601
  var defaultLiveLabel = (live) => live === 0 ? "nothing live" : `${live} live`;
601
602
  var fractionOf = (row) => row.done !== void 0 && row.total !== void 0 ? `${row.done}/${row.total}` : "";
602
603
  var counts = (row, liveLabel = defaultLiveLabel) => [fractionOf(row), row.live !== void 0 ? liveLabel(row.live) : ""].filter(Boolean).join(" \xB7 ");
@@ -634,18 +635,22 @@ var Progress = ({ row }) => {
634
635
  };
635
636
  var SidePanel = ({
636
637
  sidebar,
638
+ liveLabel = defaultLiveLabel,
639
+ width = SIDEBAR_COLS,
637
640
  height,
638
641
  focused = false,
639
642
  cursor = 0
640
643
  }) => {
641
- const liveLabel = sidebar.liveLabel ?? defaultLiveLabel;
644
+ const content = contentOf(width);
645
+ const labelCols = content - MARKS;
646
+ const liveCols = content - FACTS_FIXED;
642
647
  const capacity = height === void 0 ? sidebar.rows.length : railCapacity(height, sidebar.rows.length);
643
648
  const start = focused && cursor >= capacity ? Math.min(cursor - capacity + 1, sidebar.rows.length - capacity) : 0;
644
649
  const shown = sidebar.rows.slice(start, start + capacity);
645
650
  const hidden = sidebar.rows.length - shown.length;
646
651
  const focus = focused ? " \u25CF focus" : "";
647
652
  const rule = "\u254C".repeat(
648
- Math.max(0, CONTENT - [...sidebar.title].length - focus.length - 1)
653
+ Math.max(0, content - [...sidebar.title].length - focus.length - 1)
649
654
  );
650
655
  return (
651
656
  // A rule down the left rather than a full box: the rail's other three edges
@@ -658,7 +663,7 @@ var SidePanel = ({
658
663
  Box,
659
664
  {
660
665
  flexDirection: "column",
661
- width: SIDEBAR_COLS,
666
+ width,
662
667
  flexShrink: 0,
663
668
  height,
664
669
  borderStyle: "single",
@@ -679,12 +684,12 @@ var SidePanel = ({
679
684
  /* @__PURE__ */ jsxs(Box, { children: [
680
685
  /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? "\u276F " : " " }),
681
686
  /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
682
- /* @__PURE__ */ jsx(Text, { bold: active, children: truncateWords(row.label, LABEL_COLS) })
687
+ /* @__PURE__ */ jsx(Text, { bold: active, children: truncateWords(row.label, labelCols) })
683
688
  ] }),
684
689
  /* @__PURE__ */ jsxs(Box, { paddingLeft: FACTS_INDENT, children: [
685
690
  /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: row.key.padEnd(KEY_COLS) + GUTTER }),
686
691
  /* @__PURE__ */ jsx(Progress, { row }),
687
- /* @__PURE__ */ jsx(Text, { children: GUTTER + (row.live !== void 0 ? liveLabel(row.live).slice(0, LIVE_COLS) : "") })
692
+ /* @__PURE__ */ jsx(Text, { children: GUTTER + (row.live !== void 0 ? liveLabel(row.live).slice(0, liveCols) : "") })
688
693
  ] })
689
694
  ] }, row.key);
690
695
  }),
@@ -757,7 +762,8 @@ var readCache = (key) => {
757
762
  sections: raw.sections,
758
763
  login: raw.login ?? "",
759
764
  at: raw.at ?? 0,
760
- budget: raw.budget
765
+ budget: raw.budget,
766
+ ...Array.isArray(raw.sidebar?.rows) ? { sidebar: raw.sidebar } : {}
761
767
  };
762
768
  } catch {
763
769
  return null;
@@ -2401,7 +2407,8 @@ var BrowseScreen = ({
2401
2407
  onLeave,
2402
2408
  transients,
2403
2409
  onTabChange,
2404
- sidebar
2410
+ sidebar,
2411
+ liveLabel
2405
2412
  }) => {
2406
2413
  const { rows } = useWindowSize();
2407
2414
  const { exit } = useApp();
@@ -2525,7 +2532,8 @@ var BrowseScreen = ({
2525
2532
  const railRows = sidebar?.rows ?? [];
2526
2533
  const railActive = showRail && railFocus && railRows.length > 0;
2527
2534
  const railAt = Math.min(railCursor, Math.max(0, railRows.length - 1));
2528
- const listCols = showRail ? COLS - SIDEBAR_COLS : COLS;
2535
+ const railCols = railWidth(COLS);
2536
+ const listCols = showRail ? COLS - railCols : COLS;
2529
2537
  const [sparkFrame, setSparkFrame] = useState(0);
2530
2538
  const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || // Any MARKED TAB, not just rows on screen. The scope used to be this tab's
2531
2539
  // own items, which was right while the animation lived on the rows and is
@@ -3152,6 +3160,8 @@ var BrowseScreen = ({
3152
3160
  SidePanel,
3153
3161
  {
3154
3162
  sidebar,
3163
+ liveLabel,
3164
+ width: railCols,
3155
3165
  height: listHeight,
3156
3166
  focused: railActive,
3157
3167
  cursor: railAt
@@ -3214,7 +3224,8 @@ var App = ({
3214
3224
  watchJitterMs = 0,
3215
3225
  extensions,
3216
3226
  tabHelp,
3217
- emptyHint
3227
+ emptyHint,
3228
+ liveLabel
3218
3229
  }) => {
3219
3230
  const { rows } = useWindowSize();
3220
3231
  const [state, setState] = useState({ phase: "loading" });
@@ -3391,6 +3402,7 @@ var App = ({
3391
3402
  displayedKey.current = signatureOf(cached.sections);
3392
3403
  displayedSections.current = cached.sections;
3393
3404
  setFetchedAt(cached.at);
3405
+ if (cached.sidebar) setSidebar(cached.sidebar);
3394
3406
  setState({
3395
3407
  phase: "browse",
3396
3408
  sections: cached.sections,
@@ -3435,6 +3447,7 @@ var App = ({
3435
3447
  const shared = cacheKey ? readCache(cacheKey) : null;
3436
3448
  if (shared && shared.at > firedAt) {
3437
3449
  receive({ sections: shared.sections, login: shared.login });
3450
+ if (shared.sidebar) setSidebar(shared.sidebar);
3438
3451
  setFetchedAt(shared.at);
3439
3452
  return;
3440
3453
  }
@@ -3579,6 +3592,7 @@ var App = ({
3579
3592
  transients,
3580
3593
  onTabChange: setVisibleTab,
3581
3594
  sidebar,
3595
+ liveLabel,
3582
3596
  ciStatusState: hasCiStatus ? ciStatusState : void 0,
3583
3597
  ciJob,
3584
3598
  tabHelp,
@@ -3631,4 +3645,4 @@ var matchesFilter = (repo, filter) => {
3631
3645
  };
3632
3646
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3633
3647
 
3634
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
3648
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railWidth, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.53.0",
3
+ "version": "0.54.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",