@kud/gh-ink 0.68.1 → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { PrComments, PrHealthData, PrCheck, Health } from '@kud/gh';
3
- import { GHItem, AnyItem, Section, CiStatus, OriginSplit } from '@kud/gh-workflow';
3
+ import { GHItem, AnyItem, Section, CiStatus, OriginSplit, TaskRow } from '@kud/gh-workflow';
4
4
  export { AnyItem, Band, CiStatus, GHDetail, GHItem, InboxConfig, Move, NEEDS_YOU_SECTION, NeedsYou, 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
5
  import { StyledLine } from '@kud/ink-ui';
6
6
 
@@ -272,11 +272,17 @@ type PrRowProps = {
272
272
  gutter?: boolean;
273
273
  };
274
274
  /**
275
- * The trailing block, in reading order: how contested, how big, how recent.
276
- * Threads lead because they are the one cell that makes a claim on you; age
277
- * ends the row so every row ends on the date.
275
+ * The trailing block, in reading order: whose, how contested, how big, how
276
+ * recent. Author leads because it is read as a column of names rather than
277
+ * scanned per row; threads follow because they are the one cell that makes a
278
+ * claim on you; age ends the row so every row ends on the date.
279
+ *
280
+ * Fixed columns rather than inline text: `by <login>` used to sit right after
281
+ * the title, so its position floated with title length and finding one
282
+ * author's rows meant reading each line. Aligned, the eye runs down a column
283
+ * instead — the same move the 2026-09-30 alignment made for the other three.
278
284
  */
279
- declare const TRAILING: readonly ["threads", "size", "age"];
285
+ declare const TRAILING: readonly ["author", "threads", "size", "age"];
280
286
  /** Width of each trailing column, in terminal cells. Zero draws no column. */
281
287
  type TrailingColumns = Record<(typeof TRAILING)[number], number>;
282
288
  /** Which trailing columns a whole section has given up. */
@@ -554,6 +560,7 @@ type Action = {
554
560
  subActions?: Action[];
555
561
  group?: ActionGroup;
556
562
  icon?: string;
563
+ tone?: "dim" | "error";
557
564
  };
558
565
  type MenuGap = {
559
566
  gap: true;
@@ -577,6 +584,27 @@ type JiraTransition = {
577
584
  /** Offered as a second step when the transition asks for a resolution. */
578
585
  resolutions?: string[];
579
586
  };
587
+ /**
588
+ * One transition the ticket's workflow currently allows, as the host resolved
589
+ * it — the shape `jira issue move KEY --json` reports, trimmed to what the
590
+ * menu needs. `id` is carried so a future executor that can address a
591
+ * transition directly has it; today's `jira issue move` takes only the NAME,
592
+ * so execution passes `name` verbatim and `id` rides along unused.
593
+ */
594
+ type JiraAvailableTransition = {
595
+ id: string;
596
+ name: string;
597
+ to: {
598
+ name: string;
599
+ };
600
+ };
601
+ /**
602
+ * Resolve the live transitions for a ticket, called lazily when its move
603
+ * submenu opens — never at board fetch, where one call per visible ticket
604
+ * would spend the Jira quota the board just spent. A host without one keeps
605
+ * the static `jiraTransitions` list exactly as before.
606
+ */
607
+ type JiraTransitionsFor = (ticket: string) => Promise<JiraAvailableTransition[]>;
580
608
  type ExplainSection = {
581
609
  heading: string;
582
610
  lines: string[];
@@ -707,6 +735,15 @@ declare const buildActions: (item: AnyItem, login: string, showFlash: (msg: stri
707
735
  * the settlement signal.
708
736
  */
709
737
  onSettled?: (item: GHItem, result: SettledResult) => void;
738
+ /**
739
+ * Open the ticket's move submenu through the live-transitions hook. Passed
740
+ * by the browse screen (which owns the menu the rows open into) only when
741
+ * the host wired `jiraTransitionsFor`; its presence is what switches
742
+ * "Move status" from the static sub-list below to the lazy submenu. A
743
+ * host that calls buildActions directly without it gets today's static
744
+ * list, hook or no hook.
745
+ */
746
+ onOpenMove?: (item: TaskRow) => void;
710
747
  }) => Action[];
711
748
  type CiStatusState = {
712
749
  kind: "loading";
@@ -763,7 +800,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
763
800
  cols?: number;
764
801
  }) => React.JSX.Element;
765
802
  declare const signatureOf: (sections: Section[]) => string;
766
- declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
803
+ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, jiraTransitionsFor, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
767
804
  fetcher: () => Promise<{
768
805
  sections: Section[];
769
806
  login: string;
@@ -803,6 +840,7 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
803
840
  jiraKeyRe?: RegExp;
804
841
  jiraSetupHint?: string;
805
842
  jiraTransitions?: JiraTransition[];
843
+ jiraTransitionsFor?: JiraTransitionsFor;
806
844
  hasCiStatus?: boolean;
807
845
  ciJob?: string;
808
846
  ciFetcher?: () => Promise<CiStatus | null>;
@@ -871,4 +909,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
871
909
  */
872
910
  declare const parsePatterns: (value: string) => string[];
873
911
 
874
- export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, 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 MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
912
+ export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraAvailableTransition, type JiraTransition, type JiraTransitionsFor, type LiveLabel, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/dist/index.js CHANGED
@@ -839,20 +839,28 @@ var ageLabelOf = (item) => {
839
839
  const pair = agePairOf(item);
840
840
  return pair ? `${pair.activity} (${pair.lifetime})` : item.age;
841
841
  };
842
- var TRAILING = ["threads", "size", "age"];
842
+ var TRAILING = ["author", "threads", "size", "age"];
843
843
  var cellsOf = (s) => [...s].length;
844
+ var AUTHOR_CELL_MAX = 16;
845
+ var authorLabelOf = (item) => {
846
+ if (!item.author) return "";
847
+ const full = `@${item.author}`;
848
+ return full.length <= AUTHOR_CELL_MAX ? full : `${full.slice(0, AUTHOR_CELL_MAX - 1)}\u2026`;
849
+ };
844
850
  var trailingColumnsOf = (items) => items.reduce(
845
851
  (w, item) => ({
852
+ author: Math.max(w.author, cellsOf(authorLabelOf(item))),
846
853
  threads: Math.max(w.threads, cellsOf(threadsLabelOf(item))),
847
854
  size: Math.max(w.size, cellsOf(sizeOf(item) ?? "")),
848
855
  age: Math.max(w.age, cellsOf(ageLabelOf(item) ?? ""))
849
856
  }),
850
- { threads: 0, size: 0, age: 0 }
857
+ { author: 0, threads: 0, size: 0, age: 0 }
851
858
  );
852
859
  var numberColumnsOf = (items) => items.reduce((w, item) => Math.max(w, cellsOf(`#${item.number}`)), 0) + 2;
853
860
  var sectionShedOf = (rows, context) => rows.reduce((shed, row) => {
854
861
  const { givingUp } = layoutOf({ ...context, ...row });
855
862
  return {
863
+ author: shed.author || givingUp.author,
856
864
  threads: shed.threads || givingUp.threads,
857
865
  size: shed.size || givingUp.size,
858
866
  age: shed.age || givingUp.age
@@ -873,6 +881,7 @@ var layoutOf = ({
873
881
  const numCols = numberCols ?? numberColumnsOf([item]);
874
882
  const numStr = `#${item.number}`.padEnd(numCols);
875
883
  const showAuthor = !!item.author && item.author !== login;
884
+ const authorLabel = authorLabelOf(item);
876
885
  const sizeParts = sizePartsOf(item);
877
886
  const unresolvedLabel = threadsLabelOf(item);
878
887
  const agePair = agePairOf(item);
@@ -883,7 +892,7 @@ var layoutOf = ({
883
892
  const implied = [...impliedLabels(item.repo), ...uniformLabels];
884
893
  const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
885
894
  const givingUp = {
886
- author: false,
895
+ author: !!shed.author,
887
896
  size: !!shed.size,
888
897
  threads: !!shed.threads,
889
898
  age: !!shed.age,
@@ -912,10 +921,9 @@ var layoutOf = ({
912
921
  (w, k) => givingUp[k] || own[k] === 0 ? w : w + 2 + own[k],
913
922
  0
914
923
  );
915
- const author = showAuthor && !givingUp.author ? ` by ${item.author}`.length : 0;
916
924
  const pills = announcements.reduce((w, a) => w + 2 + a.label.length, 0);
917
925
  const cell = labelCell();
918
- return (gutter ? 2 : 0) + prefix.length + 2 + 2 + numCols + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + author + block + pills + pillCaps + 4;
926
+ return (gutter ? 2 : 0) + prefix.length + 2 + 2 + numCols + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + block + pills + pillCaps + 4;
919
927
  };
920
928
  const MIN_TITLE = 24;
921
929
  for (const give of [
@@ -935,6 +943,7 @@ var layoutOf = ({
935
943
  return {
936
944
  numStr,
937
945
  showAuthor,
946
+ authorLabel,
938
947
  sizeParts,
939
948
  unresolvedLabel,
940
949
  agePair,
@@ -973,6 +982,7 @@ var PrRow = ({
973
982
  const {
974
983
  numStr,
975
984
  showAuthor,
985
+ authorLabel,
976
986
  sizeParts,
977
987
  unresolvedLabel,
978
988
  agePair,
@@ -1017,8 +1027,8 @@ var PrRow = ({
1017
1027
  ),
1018
1028
  labelLabel ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: labelLabel + " " }) : null,
1019
1029
  repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: repoLabel }) : null,
1020
- showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1021
1030
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
1031
+ block.author > 0 && !givingUp.author ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, width: block.author, flexShrink: 0, children: showAuthor ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: authorLabel }) : null }) : null,
1022
1032
  block.threads > 0 && !givingUp.threads ? /* @__PURE__ */ jsx(
1023
1033
  Box,
1024
1034
  {
@@ -1436,6 +1446,116 @@ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
1436
1446
  };
1437
1447
  var quietly = $({ quiet: true });
1438
1448
  var isMenuGap = (row) => "gap" in row;
1449
+ var jiraMovesCache = /* @__PURE__ */ new Map();
1450
+ var cachedJiraMoves = (ticket, status) => {
1451
+ const entry = jiraMovesCache.get(ticket);
1452
+ return entry && entry.status === status ? entry.moves : void 0;
1453
+ };
1454
+ var storeJiraMoves = (ticket, status, moves) => {
1455
+ jiraMovesCache.set(ticket, { status, moves });
1456
+ };
1457
+ var invalidateJiraMoves = (ticket) => jiraMovesCache.delete(ticket);
1458
+ var orderJiraMoves = (available, configured) => {
1459
+ const known = /* @__PURE__ */ new Set();
1460
+ const ordered = [];
1461
+ for (const { label, transition, resolutions } of configured ?? []) {
1462
+ const live = available.find((t) => t.name === transition);
1463
+ if (!live) continue;
1464
+ known.add(live.name);
1465
+ ordered.push(
1466
+ resolutions && resolutions.length > 0 ? { label, transition: live.name, resolutions } : { label, transition: live.name }
1467
+ );
1468
+ }
1469
+ const rest = available.filter((t) => !known.has(t.name)).sort((a, b) => a.name.localeCompare(b.name)).map((t) => ({ label: t.name, transition: t.name }));
1470
+ return [...ordered, ...rest];
1471
+ };
1472
+ var jiraMoveActions = (ticket, moves, showFlash, onActed) => moves.map(
1473
+ ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
1474
+ label,
1475
+ hint: "",
1476
+ run: () => {
1477
+ },
1478
+ subActions: resolutions.map((resolution) => ({
1479
+ label: resolution,
1480
+ hint: "",
1481
+ run: () => {
1482
+ showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
1483
+ void quietly`jira issue move ${ticket} ${transition} --resolution ${resolution}`.then(() => {
1484
+ invalidateJiraMoves(ticket);
1485
+ showFlash(`\u2713 ${label} \xB7 ${resolution}`);
1486
+ onActed?.();
1487
+ }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
1488
+ }
1489
+ }))
1490
+ } : {
1491
+ label,
1492
+ hint: "",
1493
+ run: () => {
1494
+ showFlash(`\u22EF Moving to ${label}\u2026`);
1495
+ void quietly`jira issue move ${ticket} ${transition}`.then(() => {
1496
+ invalidateJiraMoves(ticket);
1497
+ showFlash(`\u2713 Moved to ${label}`);
1498
+ onActed?.();
1499
+ }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
1500
+ }
1501
+ }
1502
+ );
1503
+ var jiraLoadingActions = () => [
1504
+ { label: "Loading transitions\u2026", hint: "", tone: "dim", run: () => {
1505
+ } }
1506
+ ];
1507
+ var openJiraMoves = (deps) => {
1508
+ const {
1509
+ ticket,
1510
+ status,
1511
+ fetch,
1512
+ configured,
1513
+ open,
1514
+ isOpen,
1515
+ showFlash,
1516
+ onActed
1517
+ } = deps;
1518
+ const openOrdered = (moves) => {
1519
+ const ordered = orderJiraMoves(moves, configured);
1520
+ open(
1521
+ ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed) : [
1522
+ {
1523
+ label: "No transitions available",
1524
+ hint: "",
1525
+ tone: "dim",
1526
+ run: () => {
1527
+ }
1528
+ }
1529
+ ]
1530
+ );
1531
+ };
1532
+ const hit = cachedJiraMoves(ticket, status);
1533
+ if (hit) {
1534
+ openOrdered(hit);
1535
+ return;
1536
+ }
1537
+ const attempt = ++openJiraMovesSeq;
1538
+ open(jiraLoadingActions());
1539
+ void fetch(ticket).then(
1540
+ (moves) => {
1541
+ if (attempt !== openJiraMovesSeq || !isOpen()) return;
1542
+ storeJiraMoves(ticket, status, moves);
1543
+ openOrdered(moves);
1544
+ },
1545
+ () => {
1546
+ if (attempt !== openJiraMovesSeq || !isOpen()) return;
1547
+ open([
1548
+ {
1549
+ label: "Couldn't load transitions \u2014 Retry",
1550
+ hint: "",
1551
+ tone: "error",
1552
+ run: () => openJiraMoves(deps)
1553
+ }
1554
+ ]);
1555
+ }
1556
+ );
1557
+ };
1558
+ var openJiraMovesSeq = 0;
1439
1559
  var healthSentence = (item) => {
1440
1560
  const glyph = displayFor(item.health).glyph.trim();
1441
1561
  const d = item.detail;
@@ -2021,46 +2141,59 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
2021
2141
  } : null;
2022
2142
  if (item.kind === "task") {
2023
2143
  const base = drillAction ? [drillAction, open, copyUrl] : [open, copyUrl];
2024
- if (item.ticket && jiraTransitions && jiraTransitions.length > 0) {
2025
- base.push({
2026
- label: "Move status",
2027
- hint: "t",
2028
- // The task's act: changing its state, the one mutation this menu owns.
2029
- // Guarded by the caller's own condition above — without configured
2030
- // transitions there is no such row, and none is invented here.
2031
- group: "act",
2032
- run: () => {
2033
- },
2034
- subActions: jiraTransitions.map(
2035
- ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
2036
- label,
2037
- hint: "",
2038
- run: () => {
2039
- },
2040
- subActions: resolutions.map((resolution) => ({
2041
- label: resolution,
2144
+ if (item.ticket && (ext?.onOpenMove ?? (jiraTransitions && jiraTransitions.length > 0))) {
2145
+ if (ext?.onOpenMove) {
2146
+ const task = item;
2147
+ const openMove = ext.onOpenMove;
2148
+ base.push({
2149
+ label: "Move status",
2150
+ hint: "t",
2151
+ // The task's act: changing its state, the one mutation this menu owns.
2152
+ // Guarded by the caller's own condition above — without a hook or
2153
+ // configured transitions there is no such row, and none is invented here.
2154
+ group: "act",
2155
+ run: () => openMove(task)
2156
+ });
2157
+ } else
2158
+ base.push({
2159
+ label: "Move status",
2160
+ hint: "t",
2161
+ // The task's act: changing its state, the one mutation this menu owns.
2162
+ // Guarded by the caller's own condition above — without configured
2163
+ // transitions there is no such row, and none is invented here.
2164
+ group: "act",
2165
+ run: () => {
2166
+ },
2167
+ subActions: jiraTransitions.map(
2168
+ ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
2169
+ label,
2170
+ hint: "",
2171
+ run: () => {
2172
+ },
2173
+ subActions: resolutions.map((resolution) => ({
2174
+ label: resolution,
2175
+ hint: "",
2176
+ run: () => {
2177
+ showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
2178
+ void quietly`jira issue move ${item.ticket} ${transition} --resolution ${resolution}`.then(() => {
2179
+ showFlash(`\u2713 ${label} \xB7 ${resolution}`);
2180
+ ext?.onActed?.();
2181
+ }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
2182
+ }
2183
+ }))
2184
+ } : {
2185
+ label,
2042
2186
  hint: "",
2043
2187
  run: () => {
2044
- showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
2045
- void quietly`jira issue move ${item.ticket} ${transition} --resolution ${resolution}`.then(() => {
2046
- showFlash(`\u2713 ${label} \xB7 ${resolution}`);
2188
+ showFlash(`\u22EF Moving to ${label}\u2026`);
2189
+ void quietly`jira issue move ${item.ticket} ${transition}`.then(() => {
2190
+ showFlash(`\u2713 Moved to ${label}`);
2047
2191
  ext?.onActed?.();
2048
2192
  }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
2049
2193
  }
2050
- }))
2051
- } : {
2052
- label,
2053
- hint: "",
2054
- run: () => {
2055
- showFlash(`\u22EF Moving to ${label}\u2026`);
2056
- void quietly`jira issue move ${item.ticket} ${transition}`.then(() => {
2057
- showFlash(`\u2713 Moved to ${label}`);
2058
- ext?.onActed?.();
2059
- }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
2060
2194
  }
2061
- }
2062
- )
2063
- });
2195
+ )
2196
+ });
2064
2197
  }
2065
2198
  return byGroup(base);
2066
2199
  }
@@ -2731,7 +2864,8 @@ var ActionMenu = ({
2731
2864
  Text3,
2732
2865
  {
2733
2866
  bold: i === cursor,
2734
- color: isCloseRow(row) ? colors.error : void 0,
2867
+ color: row.tone === "error" ? colors.error : isCloseRow(row) ? colors.error : void 0,
2868
+ dimColor: row.tone === "dim" ? true : void 0,
2735
2869
  children: row.label
2736
2870
  }
2737
2871
  ) }),
@@ -3014,6 +3148,7 @@ var BrowseScreen = ({
3014
3148
  jiraKeyRe,
3015
3149
  jiraSetupHint,
3016
3150
  jiraTransitions,
3151
+ jiraTransitionsFor,
3017
3152
  onRefresh,
3018
3153
  onActed,
3019
3154
  onSettled,
@@ -3277,6 +3412,24 @@ var BrowseScreen = ({
3277
3412
  }
3278
3413
  return false;
3279
3414
  };
3415
+ const menuActionsRef = useRef(null);
3416
+ useEffect(() => {
3417
+ menuActionsRef.current = menu.actions;
3418
+ });
3419
+ const openMoveFor = (task) => {
3420
+ if (!task.ticket || !jiraTransitionsFor) return;
3421
+ const fetch = jiraTransitionsFor;
3422
+ openJiraMoves({
3423
+ ticket: task.ticket,
3424
+ status: task.status,
3425
+ fetch,
3426
+ configured: jiraTransitions,
3427
+ open: (rows2) => menu.open(rows2),
3428
+ isOpen: () => menuActionsRef.current !== null,
3429
+ showFlash,
3430
+ onActed
3431
+ });
3432
+ };
3280
3433
  const openMenu = () => {
3281
3434
  if (!activeItem || activeItem.kind === "repo-header" || activeItem.kind === "subgroup-header")
3282
3435
  return;
@@ -3293,7 +3446,13 @@ var BrowseScreen = ({
3293
3446
  onRefresh,
3294
3447
  dismissItem,
3295
3448
  openDrillView,
3296
- { extensions, onOpenExt, onActed, onSettled }
3449
+ {
3450
+ extensions,
3451
+ onOpenExt,
3452
+ onActed,
3453
+ onSettled,
3454
+ onOpenMove: jiraTransitionsFor ? openMoveFor : void 0
3455
+ }
3297
3456
  );
3298
3457
  menu.open(actions);
3299
3458
  };
@@ -3629,9 +3788,13 @@ var BrowseScreen = ({
3629
3788
  return;
3630
3789
  }
3631
3790
  }
3632
- if (input === "t" && activeItem && activeItem.kind === "task" && activeItem.ticket && jiraTransitions && jiraTransitions.length > 0) {
3791
+ if (input === "t" && activeItem && activeItem.kind === "task" && activeItem.ticket && (jiraTransitionsFor ?? (jiraTransitions && jiraTransitions.length > 0))) {
3792
+ if (jiraTransitionsFor) {
3793
+ openMoveFor(activeItem);
3794
+ return;
3795
+ }
3633
3796
  const jiraKey = activeItem.ticket;
3634
- const actions = jiraTransitions.map(
3797
+ const actions = (jiraTransitions ?? []).map(
3635
3798
  ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
3636
3799
  label,
3637
3800
  hint: "",
@@ -3903,8 +4066,13 @@ var BrowseScreen = ({
3903
4066
  !overlayOpen && search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3904
4067
  /* @__PURE__ */ jsx(Text3, { color: searchBar.glyphColor, children: ` ${searchBar.glyph} ` }),
3905
4068
  /* @__PURE__ */ jsx(Text3, { children: search.slice(0, filter.caret) }),
3906
- searchBar.caret ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
3907
- /* @__PURE__ */ jsx(Text3, { children: search.slice(filter.caret) }),
4069
+ searchBar.caret && filter.caret < search.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
4070
+ /* @__PURE__ */ jsx(Text3, { inverse: true, children: search[filter.caret] }),
4071
+ /* @__PURE__ */ jsx(Text3, { children: search.slice(filter.caret + 1) })
4072
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4073
+ searchBar.caret ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
4074
+ /* @__PURE__ */ jsx(Text3, { children: search.slice(filter.caret) })
4075
+ ] }),
3908
4076
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""} ${searchInput ? filter.hints.map(([k, label]) => `${k} ${label}`).join(" \xB7 ") : searchBar.hints}` })
3909
4077
  ] }) : !overlayOpen && repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3910
4078
  /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
@@ -4038,6 +4206,7 @@ var App = ({
4038
4206
  jiraKeyRe,
4039
4207
  jiraSetupHint,
4040
4208
  jiraTransitions,
4209
+ jiraTransitionsFor,
4041
4210
  hasCiStatus,
4042
4211
  ciJob,
4043
4212
  ciFetcher,
@@ -4489,6 +4658,7 @@ var App = ({
4489
4658
  jiraKeyRe,
4490
4659
  jiraSetupHint,
4491
4660
  jiraTransitions,
4661
+ jiraTransitionsFor,
4492
4662
  onRefresh: applyOrRefresh,
4493
4663
  onActed,
4494
4664
  onSettled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.68.1",
3
+ "version": "0.69.1",
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",
@@ -44,16 +44,17 @@
44
44
  "access": "public"
45
45
  },
46
46
  "peerDependencies": {
47
+ "@kud/ink-ui": ">=0.33.0 <1",
47
48
  "ink": ">=7",
48
49
  "react": ">=19"
49
50
  },
50
51
  "dependencies": {
51
52
  "@kud/gh": "0.20.0",
52
53
  "@kud/gh-workflow": "0.14.1",
53
- "@kud/ink-ui": "0.33.1",
54
54
  "zx": "8.8.5"
55
55
  },
56
56
  "devDependencies": {
57
+ "@kud/ink-ui": "0.34.0",
57
58
  "@types/node": "22.20.1",
58
59
  "@types/react": "19.2.17",
59
60
  "ink": "7.1.0",