@kud/gh-ink 0.53.0 → 0.55.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
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
2
2
  import { PrComments, PrHealthData, PrCheck, Health } from '@kud/gh';
3
3
  import { StyledLine } from '@kud/ink-ui';
4
4
  import { AnyItem, Section, CiStatus, GHItem, OriginSplit } from '@kud/gh-workflow';
5
- export { AnyItem, CiStatus, GHDetail, GHItem, InboxConfig, Move, OriginSplit, PIN_MARK, RepoHeader, RepoProfile, Section, ShowLess, ShowMore, Standing, SubgroupHeader, TaskRow, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
5
+ 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';
6
6
 
7
7
  type CommentsPanelProps = {
8
8
  repo: string;
@@ -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
@@ -9,7 +9,7 @@ import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSy
9
9
  import { join, dirname, basename } from 'path';
10
10
  import { homedir } from 'os';
11
11
  import { depthOf, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, PIN_MARK, sizeOf, sizePartsOf, impliedLabels, labelPriority } from '@kud/gh-workflow';
12
- export { PIN_MARK, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } 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 = {
@@ -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;
@@ -928,6 +934,66 @@ var tabsOfMarks = (sections, marks) => {
928
934
  }
929
935
  return out;
930
936
  };
937
+ var HOLD_MS = 5 * 60 * 1e3;
938
+ var anchorIndex = (prevItems, prevIndex, freshItems) => {
939
+ const freshAt = /* @__PURE__ */ new Map();
940
+ freshItems.forEach((item, i) => {
941
+ const key = keyOf(item);
942
+ if (key) freshAt.set(key, i);
943
+ });
944
+ for (let i = prevIndex - 1; i >= 0; i--) {
945
+ const key = keyOf(prevItems[i]);
946
+ const at = key === null ? void 0 : freshAt.get(key);
947
+ if (at !== void 0) return at + 1;
948
+ }
949
+ return Math.min(prevIndex, freshItems.length);
950
+ };
951
+ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
952
+ if (prev.length === 0) return fresh;
953
+ const present = /* @__PURE__ */ new Set();
954
+ for (const section of fresh)
955
+ for (const item of section.items) {
956
+ const key = keyOf(item);
957
+ if (key) present.add(key);
958
+ }
959
+ const bySection = /* @__PURE__ */ new Map();
960
+ const labels = /* @__PURE__ */ new Map();
961
+ prev.forEach((section, at) => {
962
+ if (section.sampled) return;
963
+ section.items.forEach((item, index) => {
964
+ if (item.kind !== "pr" && item.kind !== "issue") return;
965
+ const row = item;
966
+ if (present.has(row.url)) return;
967
+ const heldSince = row.heldSince ?? now;
968
+ if (now - heldSince > holdMs) return;
969
+ const held = { ...row, heldSince };
970
+ const list = bySection.get(section.id);
971
+ if (list) list.push({ item: held, index });
972
+ else bySection.set(section.id, [{ item: held, index }]);
973
+ labels.set(section.id, { label: section.label, at });
974
+ });
975
+ });
976
+ if (bySection.size === 0) return fresh;
977
+ const out = fresh.map((section) => {
978
+ const rows = bySection.get(section.id);
979
+ if (!rows) return section;
980
+ bySection.delete(section.id);
981
+ const prevItems = prev.find((s) => s.id === section.id)?.items ?? [];
982
+ const items = [...section.items];
983
+ for (const row of [...rows].sort((a, b) => a.index - b.index))
984
+ items.splice(anchorIndex(prevItems, row.index, items), 0, row.item);
985
+ return { ...section, items };
986
+ });
987
+ for (const [id, rows] of bySection) {
988
+ const { label, at } = labels.get(id);
989
+ out.splice(Math.min(at, out.length), 0, {
990
+ id,
991
+ label,
992
+ items: rows.sort((a, b) => a.index - b.index).map((r) => r.item)
993
+ });
994
+ }
995
+ return out;
996
+ };
931
997
  var quietly = $({ quiet: true });
932
998
  var healthSentence = (item) => {
933
999
  const glyph = displayFor(item.health).glyph.trim();
@@ -1889,7 +1955,8 @@ var ItemRow = ({
1889
1955
  prefix = "",
1890
1956
  parent,
1891
1957
  sparkFrame = 0,
1892
- cols = COLS
1958
+ cols = COLS,
1959
+ markerCols = 0
1893
1960
  }) => {
1894
1961
  const backdropped = useBackdropped();
1895
1962
  const transient = leaving ? "out" : refreshMark;
@@ -1922,13 +1989,14 @@ var ItemRow = ({
1922
1989
  const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
1923
1990
  const titleMax2 = Math.max(
1924
1991
  1,
1925
- cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - 12
1992
+ 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
1926
1993
  );
1927
1994
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1928
1995
  /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1929
1996
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1930
1997
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
1931
1998
  /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1999
+ markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
1932
2000
  /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: active, children: item.key + " " }),
1933
2001
  /* @__PURE__ */ jsx(
1934
2002
  Text3,
@@ -2401,7 +2469,8 @@ var BrowseScreen = ({
2401
2469
  onLeave,
2402
2470
  transients,
2403
2471
  onTabChange,
2404
- sidebar
2472
+ sidebar,
2473
+ liveLabel
2405
2474
  }) => {
2406
2475
  const { rows } = useWindowSize();
2407
2476
  const { exit } = useApp();
@@ -2504,6 +2573,10 @@ var BrowseScreen = ({
2504
2573
  const viewStart = viewStarts[activeId] ?? 0;
2505
2574
  const visibleCount = windowCount(section.items, viewStart, listHeight);
2506
2575
  const visibleItems = section.items.slice(viewStart, viewStart + visibleCount);
2576
+ const markerCols = section.items.reduce(
2577
+ (w, i) => i.kind === "task" ? Math.max(w, [...i.marker ?? ""].length) : w,
2578
+ 0
2579
+ );
2507
2580
  const hasMore = viewStart + visibleCount < section.items.length;
2508
2581
  const activeItem = section.items[cursor];
2509
2582
  const showFlash = (msg) => {
@@ -2525,7 +2598,8 @@ var BrowseScreen = ({
2525
2598
  const railRows = sidebar?.rows ?? [];
2526
2599
  const railActive = showRail && railFocus && railRows.length > 0;
2527
2600
  const railAt = Math.min(railCursor, Math.max(0, railRows.length - 1));
2528
- const listCols = showRail ? COLS - SIDEBAR_COLS : COLS;
2601
+ const railCols = railWidth(COLS);
2602
+ const listCols = showRail ? COLS - railCols : COLS;
2529
2603
  const [sparkFrame, setSparkFrame] = useState(0);
2530
2604
  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
2605
  // own items, which was right while the animation lived on the rows and is
@@ -3122,6 +3196,7 @@ var BrowseScreen = ({
3122
3196
  parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
3123
3197
  sparkFrame,
3124
3198
  cols: listCols,
3199
+ markerCols,
3125
3200
  gap: i > 0 && gapsAbove(section.items, viewStart + i)
3126
3201
  },
3127
3202
  `${viewStart + i}:${rowKey(item)}`
@@ -3152,6 +3227,8 @@ var BrowseScreen = ({
3152
3227
  SidePanel,
3153
3228
  {
3154
3229
  sidebar,
3230
+ liveLabel,
3231
+ width: railCols,
3155
3232
  height: listHeight,
3156
3233
  focused: railActive,
3157
3234
  cursor: railAt
@@ -3214,7 +3291,8 @@ var App = ({
3214
3291
  watchJitterMs = 0,
3215
3292
  extensions,
3216
3293
  tabHelp,
3217
- emptyHint
3294
+ emptyHint,
3295
+ liveLabel
3218
3296
  }) => {
3219
3297
  const { rows } = useWindowSize();
3220
3298
  const [state, setState] = useState({ phase: "loading" });
@@ -3340,7 +3418,15 @@ var App = ({
3340
3418
  }
3341
3419
  setSkippedForBudget(false);
3342
3420
  if (manual) setRefreshing(true);
3343
- fetcher().then((fresh) => {
3421
+ fetcher().then((fetched) => {
3422
+ const fresh = {
3423
+ ...fetched,
3424
+ sections: reconcile(
3425
+ displayedSections.current,
3426
+ fetched.sections,
3427
+ Date.now()
3428
+ )
3429
+ };
3344
3430
  if (fresh.budget) setBudget(fresh.budget);
3345
3431
  if (fresh.sidebar !== void 0) setSidebar(fresh.sidebar);
3346
3432
  if (cacheKey) writeCache(cacheKey, fresh);
@@ -3391,6 +3477,7 @@ var App = ({
3391
3477
  displayedKey.current = signatureOf(cached.sections);
3392
3478
  displayedSections.current = cached.sections;
3393
3479
  setFetchedAt(cached.at);
3480
+ if (cached.sidebar) setSidebar(cached.sidebar);
3394
3481
  setState({
3395
3482
  phase: "browse",
3396
3483
  sections: cached.sections,
@@ -3435,6 +3522,7 @@ var App = ({
3435
3522
  const shared = cacheKey ? readCache(cacheKey) : null;
3436
3523
  if (shared && shared.at > firedAt) {
3437
3524
  receive({ sections: shared.sections, login: shared.login });
3525
+ if (shared.sidebar) setSidebar(shared.sidebar);
3438
3526
  setFetchedAt(shared.at);
3439
3527
  return;
3440
3528
  }
@@ -3579,6 +3667,7 @@ var App = ({
3579
3667
  transients,
3580
3668
  onTabChange: setVisibleTab,
3581
3669
  sidebar,
3670
+ liveLabel,
3582
3671
  ciStatusState: hasCiStatus ? ciStatusState : void 0,
3583
3672
  ciJob,
3584
3673
  tabHelp,
@@ -3631,4 +3720,4 @@ var matchesFilter = (repo, filter) => {
3631
3720
  };
3632
3721
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3633
3722
 
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 };
3723
+ 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.55.0",
4
4
  "description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@kud/gh": "0.16.0",
52
- "@kud/gh-workflow": "0.11.0",
52
+ "@kud/gh-workflow": "0.12.0",
53
53
  "@kud/ink-ui": "0.29.0",
54
54
  "zx": "8.8.5"
55
55
  },