@kud/gh-ink 0.57.1 → 0.58.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
@@ -178,6 +178,14 @@ type ExtensionTarget = {
178
178
  login: string;
179
179
  onRemove?: (item: AnyItem) => void;
180
180
  showFlash?: (msg: string) => void;
181
+ query?: string;
182
+ ticketKey?: string;
183
+ };
184
+ type Command = {
185
+ id: string;
186
+ title: string;
187
+ group?: string;
188
+ run: () => void | Promise<void>;
181
189
  };
182
190
  interface InboxExtension {
183
191
  id: string;
@@ -187,6 +195,7 @@ interface InboxExtension {
187
195
  scope?: "item" | "global";
188
196
  drills?: AnyItem["kind"][];
189
197
  body: (onExit: () => void, target?: ExtensionTarget) => ReactNode;
198
+ commands?: (target: ExtensionTarget) => Command[];
190
199
  }
191
200
 
192
201
  /**
@@ -437,7 +446,7 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
437
446
  cursor: number;
438
447
  }) => React.JSX.Element;
439
448
  declare const signatureOf: (sections: Section[]) => string;
440
- declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, }: {
449
+ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, }: {
441
450
  fetcher: () => Promise<{
442
451
  sections: Section[];
443
452
  login: string;
@@ -475,6 +484,7 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
475
484
  emptyHint?: string;
476
485
  jiraBase?: string;
477
486
  jiraKeyRe?: RegExp;
487
+ jiraSetupHint?: string;
478
488
  jiraTransitions?: JiraTransition[];
479
489
  hasCiStatus?: boolean;
480
490
  ciJob?: string;
@@ -519,4 +529,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
519
529
  */
520
530
  declare const parsePatterns: (value: string) => string[];
521
531
 
522
- 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 };
532
+ 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 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
@@ -1,6 +1,6 @@
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, ProgressBar, StatusMessage, FooterHints, useListCursor, Tabs, pillWidth, Pill } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, useAppKeys, LoadingScreen, ProgressBar, StatusMessage, FooterHints, useListCursor, CommandPalette, Tabs, pillWidth, Pill } 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
6
  import { $ } from 'zx';
@@ -1521,6 +1521,11 @@ var unsubscribeFrom = async (item) => {
1521
1521
  if (!id) throw new Error(`no node id for ${item.repo}#${item.number}`);
1522
1522
  await quietly`gh api graphql -f query=${UNSUBSCRIBE_MUTATION} -f id=${id}`;
1523
1523
  };
1524
+ var openInJira = (jiraBase, key, showFlash) => {
1525
+ quietly`open ${jiraBase}/${key}`.catch(() => {
1526
+ });
1527
+ showFlash(`\u2197 Opened ${key} in Jira`);
1528
+ };
1524
1529
  var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions, onRefresh, onRemove, onOpenView, ext) => {
1525
1530
  if (item.kind === "repo-header" || item.kind === "subgroup-header" || item.kind === "show-more" || item.kind === "show-less")
1526
1531
  return [];
@@ -1728,11 +1733,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1728
1733
  actions.push({
1729
1734
  label: `Open ${jiraKey} in Jira`,
1730
1735
  hint: "t",
1731
- run: () => {
1732
- quietly`open ${jiraBase}/${jiraKey}`.catch(() => {
1733
- });
1734
- showFlash(`\u2197 Opened ${jiraKey} in Jira`);
1735
- }
1736
+ run: () => openInJira(jiraBase, jiraKey, showFlash)
1736
1737
  });
1737
1738
  }
1738
1739
  }
@@ -2289,7 +2290,10 @@ var HelpModal = ({
2289
2290
  ["p", "open repo in new pane"],
2290
2291
  ["u", "unsubscribe from this item"],
2291
2292
  ["x", "remove yourself as reviewer"],
2292
- ...hasJira ? [["t", "Jira: move / open ticket"]] : [],
2293
+ ...hasJira ? [
2294
+ ["t", "Jira: move / open ticket"],
2295
+ ["\u2303K", "launch \xB7 jump to a ticket by key"]
2296
+ ] : [],
2293
2297
  ["/", "search"],
2294
2298
  ["f", "filter by repo"],
2295
2299
  ["r", "refresh"],
@@ -2464,6 +2468,7 @@ var BrowseScreen = ({
2464
2468
  login,
2465
2469
  jiraBase,
2466
2470
  jiraKeyRe,
2471
+ jiraSetupHint,
2467
2472
  jiraTransitions,
2468
2473
  onRefresh,
2469
2474
  onActed,
@@ -2520,6 +2525,7 @@ var BrowseScreen = ({
2520
2525
  const [helpScroll, setHelpScroll] = useState(0);
2521
2526
  const [helpRange, setHelpRange] = useState({ max: 0, page: 1 });
2522
2527
  const [explain, setExplain] = useState(false);
2528
+ const [palette, setPalette] = useState(null);
2523
2529
  const filterActive = search != null || repoFilter.size > 0;
2524
2530
  const reserveCiRow = ciStatusState != null;
2525
2531
  const ciStatus = ciStatusState?.kind === "ready" ? ciStatusState.status : null;
@@ -2628,6 +2634,7 @@ var BrowseScreen = ({
2628
2634
  const railActive = showRail && railFocus && railRows.length > 0;
2629
2635
  const railAt = Math.min(railCursor, Math.max(0, railRows.length - 1));
2630
2636
  const peel = () => {
2637
+ if (palette !== null) return setPalette(null), true;
2631
2638
  if (help) return setHelp(false), true;
2632
2639
  if (explain) return setExplain(false), true;
2633
2640
  if (repoPicker) return setRepoPicker(false), true;
@@ -2645,8 +2652,8 @@ var BrowseScreen = ({
2645
2652
  };
2646
2653
  });
2647
2654
  useEffect(() => {
2648
- onTyping?.(searchInput);
2649
- }, [searchInput, onTyping]);
2655
+ onTyping?.(searchInput || palette !== null);
2656
+ }, [searchInput, palette, onTyping]);
2650
2657
  const railCols = railWidth(COLS);
2651
2658
  const listCols = showRail ? COLS - railCols : COLS;
2652
2659
  const [sparkFrame, setSparkFrame] = useState(0);
@@ -2734,7 +2741,12 @@ var BrowseScreen = ({
2734
2741
  lastKeyAt.current = Date.now();
2735
2742
  if (idleFor >= PULSE_IDLE_MS && sparkKey) setPulseArm((n) => n + 1);
2736
2743
  if (hidden) return;
2744
+ if (key.ctrl && input === "k") {
2745
+ setPalette((open) => open === null ? "" : null);
2746
+ return;
2747
+ }
2737
2748
  if (key.ctrl || key.meta) return;
2749
+ if (palette !== null) return;
2738
2750
  if (help) {
2739
2751
  if (helpRange.max > 0) {
2740
2752
  const step2 = (n) => setHelpScroll((s) => Math.min(helpRange.max, Math.max(0, s + n)));
@@ -3111,6 +3123,9 @@ var BrowseScreen = ({
3111
3123
  ["\u2190\u2192", "tab"],
3112
3124
  ["\u21B5/d", "open"],
3113
3125
  ["m", "actions"],
3126
+ // Only where Jira is configured: without it the launcher can only say
3127
+ // so, and a footer key that opens an apology is a broken feature.
3128
+ ...jiraBase ? [["\u2303K", "launch"]] : [],
3114
3129
  // Advertised only where it does something, and named for what it
3115
3130
  // shows rather than for the furniture: nobody wants "a sidebar".
3116
3131
  ...showRail ? [["\u21E5", sidebar.title.toLowerCase()]] : sidebar ? [["i", sidebar.title.toLowerCase()]] : [],
@@ -3120,8 +3135,89 @@ var BrowseScreen = ({
3120
3135
  const matchCount = section.items.filter(
3121
3136
  (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
3122
3137
  ).length;
3138
+ const launcher = useMemo(() => {
3139
+ const run = /* @__PURE__ */ new Map();
3140
+ if (palette === null)
3141
+ return { items: [], run, message: void 0 };
3142
+ const query = palette.trim();
3143
+ const ticketKey = jiraKeyRe ? query.toUpperCase().match(jiraKeyRe)?.[0] : void 0;
3144
+ const commands = [];
3145
+ const rich = (key, where) => /* @__PURE__ */ jsxs(Text3, { children: [
3146
+ "Open ",
3147
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: key }),
3148
+ " ",
3149
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: where })
3150
+ ] });
3151
+ const labels = /* @__PURE__ */ new Map();
3152
+ if (ticketKey && jiraBase) {
3153
+ const row = {
3154
+ kind: "task",
3155
+ key: ticketKey,
3156
+ ticket: ticketKey,
3157
+ summary: "",
3158
+ url: `${jiraBase}/${ticketKey}`,
3159
+ status: "",
3160
+ age: ""
3161
+ };
3162
+ if (drillExtensionFor(row, extensions)) {
3163
+ commands.push({
3164
+ id: "launcher:here",
3165
+ title: `Open ${ticketKey} here`,
3166
+ run: () => void openDrillView(row)
3167
+ });
3168
+ labels.set("launcher:here", rich(ticketKey, "here"));
3169
+ }
3170
+ commands.push({
3171
+ id: "launcher:jira",
3172
+ title: `Open ${ticketKey} in Jira`,
3173
+ run: () => openInJira(jiraBase, ticketKey, showFlash)
3174
+ });
3175
+ labels.set("launcher:jira", rich(ticketKey, "in Jira"));
3176
+ }
3177
+ const target = {
3178
+ ...extensionTargetFor(activeItem),
3179
+ query: palette,
3180
+ ticketKey
3181
+ };
3182
+ for (const ext of extensions ?? []) {
3183
+ for (const command of ext.commands?.(target) ?? []) {
3184
+ commands.push({ ...command, id: `${ext.id}:${command.id}` });
3185
+ }
3186
+ }
3187
+ for (const command of commands) run.set(command.id, command.run);
3188
+ const message = !jiraBase ? `Jira not configured${jiraSetupHint ? ` \xB7 ${jiraSetupHint}` : ""}` : query && !ticketKey ? `no ticket matches "${query}"` : void 0;
3189
+ const items = commands.map((command) => ({
3190
+ id: command.id,
3191
+ title: command.title,
3192
+ group: command.group,
3193
+ label: labels.get(command.id)
3194
+ }));
3195
+ return { items, run, message };
3196
+ }, [palette, activeItem, extensions, jiraBase, jiraKeyRe, jiraSetupHint]);
3123
3197
  if (hidden) return null;
3124
- const overlay = help ? /* @__PURE__ */ jsx(
3198
+ const overlay = palette !== null ? /* @__PURE__ */ jsx(
3199
+ CommandPalette,
3200
+ {
3201
+ items: launcher.items,
3202
+ query: palette,
3203
+ onQueryChange: setPalette,
3204
+ onSelect: (id) => {
3205
+ const run = launcher.run.get(id);
3206
+ setPalette(null);
3207
+ void run?.();
3208
+ },
3209
+ onClose: () => setPalette(null),
3210
+ message: launcher.message,
3211
+ placeholder: "ticket key\u2026",
3212
+ width: Math.min(60, Math.max(30, listCols - 2)),
3213
+ maxRows: Math.max(3, listHeight - 6),
3214
+ hints: [
3215
+ ["\u2191\u2193", "move"],
3216
+ ["\u23CE", "open"],
3217
+ ["esc", "close"]
3218
+ ]
3219
+ }
3220
+ ) : help ? /* @__PURE__ */ jsx(
3125
3221
  HelpModal,
3126
3222
  {
3127
3223
  extensions,
@@ -3323,6 +3419,7 @@ var App = ({
3323
3419
  origin,
3324
3420
  jiraBase,
3325
3421
  jiraKeyRe,
3422
+ jiraSetupHint,
3326
3423
  jiraTransitions,
3327
3424
  hasCiStatus,
3328
3425
  ciJob,
@@ -3711,6 +3808,7 @@ var App = ({
3711
3808
  login: state.login,
3712
3809
  jiraBase,
3713
3810
  jiraKeyRe,
3811
+ jiraSetupHint,
3714
3812
  jiraTransitions,
3715
3813
  onRefresh: applyOrRefresh,
3716
3814
  onActed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.57.1",
3
+ "version": "0.58.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",
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@kud/gh": "0.17.0",
52
52
  "@kud/gh-workflow": "0.12.1",
53
- "@kud/ink-ui": "0.29.0",
53
+ "@kud/ink-ui": "0.30.0",
54
54
  "zx": "8.8.5"
55
55
  },
56
56
  "devDependencies": {