@kud/gh-ink 0.70.0 → 0.73.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 +39 -2
- package/dist/index.js +485 -63
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -468,6 +468,16 @@ interface InboxExtension {
|
|
|
468
468
|
drills?: AnyItem["kind"][];
|
|
469
469
|
body: (onExit: () => void, target?: ExtensionTarget) => ReactNode;
|
|
470
470
|
commands?: (target: ExtensionTarget) => Command[];
|
|
471
|
+
resolve?: (input: string) => (() => Promise<AnyItem>) | null;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* How a `resolve` thunk says "nothing by that name": the launcher draws its
|
|
475
|
+
* message muted, the way it draws a query that matched nothing, and keeps the
|
|
476
|
+
* input open. Any other rejection is a failure and is drawn in the error tone —
|
|
477
|
+
* the difference a reader needs between "you typed it wrong" and "try again".
|
|
478
|
+
*/
|
|
479
|
+
declare class LookupMiss extends Error {
|
|
480
|
+
constructor(message: string);
|
|
471
481
|
}
|
|
472
482
|
|
|
473
483
|
/**
|
|
@@ -594,10 +604,31 @@ type JiraTransition = {
|
|
|
594
604
|
type JiraAvailableTransition = {
|
|
595
605
|
id: string;
|
|
596
606
|
name: string;
|
|
607
|
+
/**
|
|
608
|
+
* The status the transition lands on. `category` is Jira's status category
|
|
609
|
+
* (`new`, `indeterminate`, `done`) where the host has it — the one field a
|
|
610
|
+
* `tabForStatus` can map without knowing every workflow's status names.
|
|
611
|
+
*/
|
|
597
612
|
to: {
|
|
598
613
|
name: string;
|
|
614
|
+
category?: string;
|
|
599
615
|
};
|
|
600
616
|
};
|
|
617
|
+
/**
|
|
618
|
+
* Which tab a ticket belongs in once it lands on `to`, as the host lays its
|
|
619
|
+
* tabs out — the section id, or null when the ticket leaves the board or the
|
|
620
|
+
* host cannot say. Only the host knows: one board files by status category,
|
|
621
|
+
* another by a status-to-column table, and the inbox has neither.
|
|
622
|
+
*
|
|
623
|
+
* Supplying it makes a move optimistic: the row goes to that tab the moment
|
|
624
|
+
* the move is chosen instead of at the next fetch. Null keeps the move
|
|
625
|
+
* pessimistic, exactly as it is without the prop — a row guessed off the board
|
|
626
|
+
* that turns out to still be on it is worse than one that waits for the fetch.
|
|
627
|
+
*/
|
|
628
|
+
type TabForStatus = (to: {
|
|
629
|
+
name: string;
|
|
630
|
+
category?: string;
|
|
631
|
+
}) => string | null;
|
|
601
632
|
/**
|
|
602
633
|
* Resolve the live transitions for a ticket, called lazily when its move
|
|
603
634
|
* submenu opens — never at board fetch, where one call per visible ticket
|
|
@@ -800,7 +831,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
|
800
831
|
cols?: number;
|
|
801
832
|
}) => React.JSX.Element;
|
|
802
833
|
declare const signatureOf: (sections: Section[]) => string;
|
|
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, }: {
|
|
834
|
+
declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, jiraTransitionsFor, tabForStatus, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
|
|
804
835
|
fetcher: () => Promise<{
|
|
805
836
|
sections: Section[];
|
|
806
837
|
login: string;
|
|
@@ -841,6 +872,12 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
841
872
|
jiraSetupHint?: string;
|
|
842
873
|
jiraTransitions?: JiraTransition[];
|
|
843
874
|
jiraTransitionsFor?: JiraTransitionsFor;
|
|
875
|
+
/**
|
|
876
|
+
* Which tab a moved ticket lands in — see TabForStatus. Supplying it makes
|
|
877
|
+
* moves through `jiraTransitionsFor` optimistic; leaving it out keeps every
|
|
878
|
+
* move waiting on the next fetch, as before.
|
|
879
|
+
*/
|
|
880
|
+
tabForStatus?: TabForStatus;
|
|
844
881
|
hasCiStatus?: boolean;
|
|
845
882
|
ciJob?: string;
|
|
846
883
|
ciFetcher?: () => Promise<CiStatus | null>;
|
|
@@ -909,4 +946,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
909
946
|
*/
|
|
910
947
|
declare const parsePatterns: (value: string) => string[];
|
|
911
948
|
|
|
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 };
|
|
949
|
+
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, LookupMiss, 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 TabForStatus, 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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React2, { createContext, useState, useRef, useContext, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import { useWindowSize, useInput, Text, Box } from 'ink';
|
|
3
3
|
import { colors, ScrollView, TextInput, Pill, getIconMode, FooterHints, useAppKeys, LoadingScreen, StatusMessage, useFilterMode, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } from '@kud/ink-ui';
|
|
4
|
-
import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
|
|
4
|
+
import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer, parseRef, formatRef, fetchItemNode } 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';
|
|
6
|
+
import { PIN_MARK, sizeOf, sizePartsOf, depthOf, impliedLabels, labelPriority, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, refCandidates, resolveRef } from '@kud/gh-workflow';
|
|
7
7
|
export { NEEDS_YOU_SECTION, PIN_MARK, bandOf, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
|
|
8
8
|
import { $ } from 'zx';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
@@ -1180,7 +1180,7 @@ var searchBarState = ({
|
|
|
1180
1180
|
glyph: searchGlyph(nerd),
|
|
1181
1181
|
glyphColor: typing ? colors.info : colors.muted,
|
|
1182
1182
|
caret: typing,
|
|
1183
|
-
hints: typing ? "\u2191\u2193 move \xB7 \u21B5
|
|
1183
|
+
hints: typing ? "\u2191\u2193 move \xB7 \u21B5/esc done \xB7 \u2303u clear" : "filtered \xB7 keys active \xB7 esc clear"
|
|
1184
1184
|
});
|
|
1185
1185
|
var InboxChromeContext = createContext(null);
|
|
1186
1186
|
var useChrome = (spec) => {
|
|
@@ -1194,6 +1194,189 @@ var useChrome = (spec) => {
|
|
|
1194
1194
|
}, [setChrome, scope, hintsKey]);
|
|
1195
1195
|
return chrome !== null;
|
|
1196
1196
|
};
|
|
1197
|
+
var LookupMiss = class extends Error {
|
|
1198
|
+
constructor(message) {
|
|
1199
|
+
super(message);
|
|
1200
|
+
this.name = "LookupMiss";
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
var isGH = (item) => item.kind === "pr" || item.kind === "issue";
|
|
1204
|
+
var listedAt = (sections, repo, number) => {
|
|
1205
|
+
const want = repo.toLowerCase();
|
|
1206
|
+
for (const section of sections)
|
|
1207
|
+
for (const item of section.items)
|
|
1208
|
+
if (isGH(item) && item.number === number && item.repo.toLowerCase() === want)
|
|
1209
|
+
return item;
|
|
1210
|
+
return void 0;
|
|
1211
|
+
};
|
|
1212
|
+
var listedCopy = (sections, item) => {
|
|
1213
|
+
if (isGH(item)) return listedAt(sections, item.repo, item.number);
|
|
1214
|
+
const url = "url" in item ? item.url : void 0;
|
|
1215
|
+
if (!url) return void 0;
|
|
1216
|
+
for (const section of sections)
|
|
1217
|
+
for (const row of section.items)
|
|
1218
|
+
if (row.kind === item.kind && "url" in row && row.url === url) return row;
|
|
1219
|
+
return void 0;
|
|
1220
|
+
};
|
|
1221
|
+
var settle = (sections, item) => listedCopy(sections, item) ?? (isGH(item) ? { ...item, notInInbox: true } : item);
|
|
1222
|
+
var githubClaim = (query, { activeRepo, repos, sections }) => {
|
|
1223
|
+
const ref = parseRef(query);
|
|
1224
|
+
if (!ref) return null;
|
|
1225
|
+
const near = ref.repo ? listedAt(sections, ref.repo, ref.number) : activeRepo ? listedAt(sections, activeRepo, ref.number) : void 0;
|
|
1226
|
+
const label = near ? `${near.repo}#${near.number}` : formatRef(ref);
|
|
1227
|
+
const repo = ref.repo ?? refCandidates(activeRepo, repos)[0];
|
|
1228
|
+
const text = query.trim();
|
|
1229
|
+
const browserUrl = near?.url ?? (/^https?:/i.test(text) ? text : repo ? `https://github.com/${repo}/issues/${ref.number}` : void 0);
|
|
1230
|
+
return {
|
|
1231
|
+
label,
|
|
1232
|
+
browser: browserUrl ? {
|
|
1233
|
+
label: near || !repo ? label : `${repo}#${ref.number}`,
|
|
1234
|
+
url: browserUrl
|
|
1235
|
+
} : void 0,
|
|
1236
|
+
fetch: async () => {
|
|
1237
|
+
if (near) return near;
|
|
1238
|
+
const item = await resolveRef(text, {
|
|
1239
|
+
fetchNode: (r, n) => fetchItemNode(r, n),
|
|
1240
|
+
activeRepo,
|
|
1241
|
+
repos
|
|
1242
|
+
});
|
|
1243
|
+
if (!item) throw new LookupMiss(`nothing matches "${label}"`);
|
|
1244
|
+
return item;
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
};
|
|
1248
|
+
var claimFor = (query, context, extensions = []) => {
|
|
1249
|
+
if (!query.trim()) return null;
|
|
1250
|
+
const github = githubClaim(query, context);
|
|
1251
|
+
if (github) return github;
|
|
1252
|
+
for (const ext of extensions) {
|
|
1253
|
+
const fetch = ext.resolve?.(query);
|
|
1254
|
+
if (fetch) return { label: query.trim(), fetch };
|
|
1255
|
+
}
|
|
1256
|
+
return null;
|
|
1257
|
+
};
|
|
1258
|
+
var reasonOf = (error) => {
|
|
1259
|
+
const e = error;
|
|
1260
|
+
const text = typeof e?.stderr === "string" && e.stderr.trim() || typeof e?.message === "string" && e.message || String(error);
|
|
1261
|
+
const line = text.split("\n").find((l) => l.trim()) ?? text;
|
|
1262
|
+
return line.replace(/^gh:\s*/, "").trim();
|
|
1263
|
+
};
|
|
1264
|
+
var useLauncherLookup = (palette, open) => {
|
|
1265
|
+
const [lookup, setLookup] = useState(null);
|
|
1266
|
+
const seq = useRef(0);
|
|
1267
|
+
const latest = useRef({ palette, open });
|
|
1268
|
+
latest.current = { palette, open };
|
|
1269
|
+
const start = (query, claim) => {
|
|
1270
|
+
const id = ++seq.current;
|
|
1271
|
+
const live = () => id === seq.current && latest.current.palette === query;
|
|
1272
|
+
setLookup({ query, phase: "loading", label: claim.label });
|
|
1273
|
+
claim.fetch().then(
|
|
1274
|
+
(item) => {
|
|
1275
|
+
if (!live()) return;
|
|
1276
|
+
setLookup(null);
|
|
1277
|
+
if (!latest.current.open(item))
|
|
1278
|
+
setLookup({
|
|
1279
|
+
query,
|
|
1280
|
+
phase: "failed",
|
|
1281
|
+
label: claim.label,
|
|
1282
|
+
message: `nothing here can open ${claim.label}`
|
|
1283
|
+
});
|
|
1284
|
+
},
|
|
1285
|
+
(error) => {
|
|
1286
|
+
if (!live()) return;
|
|
1287
|
+
setLookup(
|
|
1288
|
+
error instanceof LookupMiss ? {
|
|
1289
|
+
query,
|
|
1290
|
+
phase: "missed",
|
|
1291
|
+
label: claim.label,
|
|
1292
|
+
message: error.message
|
|
1293
|
+
} : {
|
|
1294
|
+
query,
|
|
1295
|
+
phase: "failed",
|
|
1296
|
+
label: claim.label,
|
|
1297
|
+
message: reasonOf(error)
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
);
|
|
1302
|
+
};
|
|
1303
|
+
const reset = () => {
|
|
1304
|
+
seq.current++;
|
|
1305
|
+
setLookup(null);
|
|
1306
|
+
};
|
|
1307
|
+
return {
|
|
1308
|
+
lookup: lookup && lookup.query === palette ? lookup : null,
|
|
1309
|
+
start,
|
|
1310
|
+
reset
|
|
1311
|
+
};
|
|
1312
|
+
};
|
|
1313
|
+
var lookupRows = (claim, lookup, query, start, openUrl) => {
|
|
1314
|
+
const ref = (s) => /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: s });
|
|
1315
|
+
if (lookup?.phase === "loading")
|
|
1316
|
+
return {
|
|
1317
|
+
exclusive: true,
|
|
1318
|
+
rows: [
|
|
1319
|
+
{
|
|
1320
|
+
id: "launcher:lookup",
|
|
1321
|
+
title: `Looking up ${claim.label}\u2026`,
|
|
1322
|
+
label: /* @__PURE__ */ jsxs(Text3, { dimColor: true, children: [
|
|
1323
|
+
"Looking up ",
|
|
1324
|
+
ref(claim.label),
|
|
1325
|
+
"\u2026"
|
|
1326
|
+
] }),
|
|
1327
|
+
run: () => {
|
|
1328
|
+
},
|
|
1329
|
+
keepOpen: true
|
|
1330
|
+
}
|
|
1331
|
+
]
|
|
1332
|
+
};
|
|
1333
|
+
if (lookup?.phase === "missed")
|
|
1334
|
+
return { exclusive: true, rows: [], message: lookup.message };
|
|
1335
|
+
if (lookup?.phase === "failed")
|
|
1336
|
+
return {
|
|
1337
|
+
exclusive: true,
|
|
1338
|
+
rows: [
|
|
1339
|
+
{
|
|
1340
|
+
id: "launcher:retry",
|
|
1341
|
+
title: `Couldn't look up ${claim.label}: ${lookup.message}`,
|
|
1342
|
+
label: /* @__PURE__ */ jsxs(Text3, { color: colors.error, children: [
|
|
1343
|
+
"\u2717 couldn't look up ",
|
|
1344
|
+
claim.label,
|
|
1345
|
+
" \xB7 ",
|
|
1346
|
+
lookup.message
|
|
1347
|
+
] }),
|
|
1348
|
+
run: () => start(query, claim),
|
|
1349
|
+
keepOpen: true
|
|
1350
|
+
}
|
|
1351
|
+
]
|
|
1352
|
+
};
|
|
1353
|
+
const rows = [
|
|
1354
|
+
{
|
|
1355
|
+
id: "launcher:open",
|
|
1356
|
+
title: `\u21B5 opens ${claim.label}`,
|
|
1357
|
+
label: /* @__PURE__ */ jsxs(Text3, { children: [
|
|
1358
|
+
"\u21B5 opens ",
|
|
1359
|
+
ref(claim.label)
|
|
1360
|
+
] }),
|
|
1361
|
+
run: () => start(query, claim),
|
|
1362
|
+
keepOpen: true
|
|
1363
|
+
}
|
|
1364
|
+
];
|
|
1365
|
+
const browser = claim.browser;
|
|
1366
|
+
if (browser)
|
|
1367
|
+
rows.push({
|
|
1368
|
+
id: "launcher:browser",
|
|
1369
|
+
title: `Open ${browser.label} in browser`,
|
|
1370
|
+
label: /* @__PURE__ */ jsxs(Text3, { children: [
|
|
1371
|
+
"Open ",
|
|
1372
|
+
ref(browser.label),
|
|
1373
|
+
" ",
|
|
1374
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: "in browser" })
|
|
1375
|
+
] }),
|
|
1376
|
+
run: () => openUrl(browser.url, browser.label)
|
|
1377
|
+
});
|
|
1378
|
+
return { exclusive: false, rows };
|
|
1379
|
+
};
|
|
1197
1380
|
|
|
1198
1381
|
// src/inbox/cache.ts
|
|
1199
1382
|
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
@@ -1285,11 +1468,11 @@ var indexRows = (sections) => {
|
|
|
1285
1468
|
};
|
|
1286
1469
|
var rowsOf = (index) => {
|
|
1287
1470
|
const out = /* @__PURE__ */ new Map();
|
|
1288
|
-
for (const [mark,
|
|
1471
|
+
for (const [mark, placed2] of index) {
|
|
1289
1472
|
const key = rowKeyOfMark(mark);
|
|
1290
1473
|
const seen = out.get(key);
|
|
1291
|
-
if (seen) seen.add(
|
|
1292
|
-
else out.set(key, /* @__PURE__ */ new Set([
|
|
1474
|
+
if (seen) seen.add(placed2.sectionId);
|
|
1475
|
+
else out.set(key, /* @__PURE__ */ new Set([placed2.sectionId]));
|
|
1293
1476
|
}
|
|
1294
1477
|
return out;
|
|
1295
1478
|
};
|
|
@@ -1444,6 +1627,73 @@ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
|
|
|
1444
1627
|
}
|
|
1445
1628
|
return out;
|
|
1446
1629
|
};
|
|
1630
|
+
|
|
1631
|
+
// src/inbox/overrides.ts
|
|
1632
|
+
var OVERRIDE_HOLD_MS = 3e4;
|
|
1633
|
+
var withFields = (item, fields) => fields ? { ...item, ...fields } : item;
|
|
1634
|
+
var placed = (item, fields) => {
|
|
1635
|
+
const row = withFields(item, fields);
|
|
1636
|
+
return { ...row, depth: 0, indent: false };
|
|
1637
|
+
};
|
|
1638
|
+
var applyOne = (sections, key, patch) => {
|
|
1639
|
+
const found = sections.flatMap((s) => s.items).find((item) => keyOf(item) === key);
|
|
1640
|
+
if (!found) return sections;
|
|
1641
|
+
if (patch.kind === "mutate")
|
|
1642
|
+
return sections.map((s) => ({
|
|
1643
|
+
...s,
|
|
1644
|
+
items: s.items.map(
|
|
1645
|
+
(item) => keyOf(item) === key ? withFields(item, patch.fields) : item
|
|
1646
|
+
)
|
|
1647
|
+
}));
|
|
1648
|
+
const without = sections.map((s) => ({
|
|
1649
|
+
...s,
|
|
1650
|
+
items: s.items.filter((item) => keyOf(item) !== key)
|
|
1651
|
+
}));
|
|
1652
|
+
if (patch.kind === "remove") return without;
|
|
1653
|
+
if (!without.some((s) => s.id === patch.toSection))
|
|
1654
|
+
return sections.map((s) => ({
|
|
1655
|
+
...s,
|
|
1656
|
+
items: s.items.map(
|
|
1657
|
+
(item) => keyOf(item) === key ? withFields(item, patch.fields) : item
|
|
1658
|
+
)
|
|
1659
|
+
}));
|
|
1660
|
+
return without.map(
|
|
1661
|
+
(s) => s.id === patch.toSection ? { ...s, items: [...s.items, placed(found, patch.fields)] } : s
|
|
1662
|
+
);
|
|
1663
|
+
};
|
|
1664
|
+
var applyOverrides = (sections, overrides) => [...overrides].reduce(
|
|
1665
|
+
(out, [key, override]) => applyOne(out, key, override.patch),
|
|
1666
|
+
sections
|
|
1667
|
+
);
|
|
1668
|
+
var fieldsMatch = (item, fields) => !fields || Object.entries(fields).every(
|
|
1669
|
+
([name, value]) => item[name] === value
|
|
1670
|
+
);
|
|
1671
|
+
var agrees = (sections, key, patch) => {
|
|
1672
|
+
const at = sections.flatMap(
|
|
1673
|
+
(s) => s.items.filter((item) => keyOf(item) === key).map((item) => ({ s, item }))
|
|
1674
|
+
);
|
|
1675
|
+
if (patch.kind === "remove") return at.length === 0;
|
|
1676
|
+
if (at.length === 0) return false;
|
|
1677
|
+
if (patch.kind === "mutate")
|
|
1678
|
+
return at.every(({ item }) => fieldsMatch(item, patch.fields));
|
|
1679
|
+
return at.some(
|
|
1680
|
+
({ s, item }) => s.id === patch.toSection && fieldsMatch(item, patch.fields)
|
|
1681
|
+
);
|
|
1682
|
+
};
|
|
1683
|
+
var pruneOverrides = (overrides, fresh, now, holdMs = OVERRIDE_HOLD_MS) => new Map(
|
|
1684
|
+
[...overrides].filter(([key, override]) => {
|
|
1685
|
+
if (override.phase !== "sent") return true;
|
|
1686
|
+
if (now - override.since > holdMs) return false;
|
|
1687
|
+
return !agrees(fresh, key, override.patch);
|
|
1688
|
+
})
|
|
1689
|
+
);
|
|
1690
|
+
var failedOverrides = (overrides) => [...overrides].filter(([, o]) => o.phase === "failed").sort(([, a], [, b]) => (b.failure?.seq ?? 0) - (a.failure?.seq ?? 0));
|
|
1691
|
+
var failureNotice = (failed) => {
|
|
1692
|
+
const newest = failed[0]?.[1];
|
|
1693
|
+
if (!newest) return null;
|
|
1694
|
+
const more = failed.length > 1 ? ` +${failed.length - 1} more` : "";
|
|
1695
|
+
return `\u2717 Couldn't ${newest.action} ${newest.label}: ${newest.failure?.reason ?? "no reason reported"}${more} w restore`;
|
|
1696
|
+
};
|
|
1447
1697
|
var quietly = $({ quiet: true });
|
|
1448
1698
|
var isMenuGap = (row) => "gap" in row;
|
|
1449
1699
|
var jiraMovesCache = /* @__PURE__ */ new Map();
|
|
@@ -1463,43 +1713,56 @@ var orderJiraMoves = (available, configured) => {
|
|
|
1463
1713
|
if (!live) continue;
|
|
1464
1714
|
known.add(live.name);
|
|
1465
1715
|
ordered.push(
|
|
1466
|
-
resolutions && resolutions.length > 0 ? { label, transition: live.name, resolutions } : { label, transition: live.name }
|
|
1716
|
+
resolutions && resolutions.length > 0 ? { label, transition: live.name, resolutions, to: live.to } : { label, transition: live.name, to: live.to }
|
|
1467
1717
|
);
|
|
1468
1718
|
}
|
|
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 }));
|
|
1719
|
+
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, to: t.to }));
|
|
1470
1720
|
return [...ordered, ...rest];
|
|
1471
1721
|
};
|
|
1472
|
-
var jiraMoveActions = (ticket, moves, showFlash, onActed) =>
|
|
1473
|
-
(
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1722
|
+
var jiraMoveActions = (ticket, moves, showFlash, onActed, optimistic) => {
|
|
1723
|
+
const execute = (to, request, done, failed) => {
|
|
1724
|
+
const held = to ? optimistic?.(to) : void 0;
|
|
1725
|
+
void request().then(() => {
|
|
1726
|
+
invalidateJiraMoves(ticket);
|
|
1727
|
+
held?.settled();
|
|
1728
|
+
showFlash(done);
|
|
1729
|
+
onActed?.();
|
|
1730
|
+
}).catch((err) => held ? held.failed(err) : showFlash(failed));
|
|
1731
|
+
};
|
|
1732
|
+
return moves.map(
|
|
1733
|
+
({ label, transition, resolutions, to }) => resolutions && resolutions.length > 0 ? {
|
|
1734
|
+
label,
|
|
1480
1735
|
hint: "",
|
|
1481
1736
|
run: () => {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1737
|
+
},
|
|
1738
|
+
subActions: resolutions.map((resolution) => ({
|
|
1739
|
+
label: resolution,
|
|
1740
|
+
hint: "",
|
|
1741
|
+
run: () => {
|
|
1742
|
+
showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
|
|
1743
|
+
execute(
|
|
1744
|
+
to,
|
|
1745
|
+
() => quietly`jira issue move ${ticket} ${transition} --resolution ${resolution}`,
|
|
1746
|
+
`\u2713 ${label} \xB7 ${resolution}`,
|
|
1747
|
+
`\u2717 Move to ${label} failed`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
}))
|
|
1751
|
+
} : {
|
|
1752
|
+
label,
|
|
1753
|
+
hint: "",
|
|
1754
|
+
run: () => {
|
|
1755
|
+
showFlash(`\u22EF Moving to ${label}\u2026`);
|
|
1756
|
+
execute(
|
|
1757
|
+
to,
|
|
1758
|
+
() => quietly`jira issue move ${ticket} ${transition}`,
|
|
1759
|
+
`\u2713 Moved to ${label}`,
|
|
1760
|
+
`\u2717 Move to ${label} failed`
|
|
1761
|
+
);
|
|
1488
1762
|
}
|
|
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
1763
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1764
|
+
);
|
|
1765
|
+
};
|
|
1503
1766
|
var jiraLoadingActions = () => [
|
|
1504
1767
|
{ label: "Loading transitions\u2026", hint: "", tone: "dim", run: () => {
|
|
1505
1768
|
} }
|
|
@@ -1513,12 +1776,13 @@ var openJiraMoves = (deps) => {
|
|
|
1513
1776
|
open,
|
|
1514
1777
|
isOpen,
|
|
1515
1778
|
showFlash,
|
|
1516
|
-
onActed
|
|
1779
|
+
onActed,
|
|
1780
|
+
optimistic
|
|
1517
1781
|
} = deps;
|
|
1518
1782
|
const openOrdered = (moves) => {
|
|
1519
1783
|
const ordered = orderJiraMoves(moves, configured);
|
|
1520
1784
|
open(
|
|
1521
|
-
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed) : [
|
|
1785
|
+
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed, optimistic) : [
|
|
1522
1786
|
{
|
|
1523
1787
|
label: "No transitions available",
|
|
1524
1788
|
hint: "",
|
|
@@ -2011,7 +2275,7 @@ var ROW_MAX_COLS = 140;
|
|
|
2011
2275
|
var OVERLAY_BG = "#22222e";
|
|
2012
2276
|
var SELECTION_BG = "#16161f";
|
|
2013
2277
|
var selectionBackground = (active, backdropped) => active && !backdropped ? SELECTION_BG : void 0;
|
|
2014
|
-
var TICKET_KEY_COLOUR =
|
|
2278
|
+
var TICKET_KEY_COLOUR = colors.ticket;
|
|
2015
2279
|
var taskKeyStyle = () => ({
|
|
2016
2280
|
color: TICKET_KEY_COLOUR,
|
|
2017
2281
|
bold: true
|
|
@@ -2595,6 +2859,8 @@ var PULSE_IDLE_MS = 6e4;
|
|
|
2595
2859
|
var PULSE_SETTLED_FRAME = 3;
|
|
2596
2860
|
var NO_TRANSIENTS = /* @__PURE__ */ new Map();
|
|
2597
2861
|
var NO_HOLDS = /* @__PURE__ */ new Map();
|
|
2862
|
+
var PENDING_GLYPH = "\u25CC";
|
|
2863
|
+
var FAILURE_NOTICE_MS = 8e3;
|
|
2598
2864
|
var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
|
|
2599
2865
|
var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
2600
2866
|
var TRANSIT_COLOUR = {
|
|
@@ -2623,6 +2889,7 @@ var ItemRow = ({
|
|
|
2623
2889
|
merged,
|
|
2624
2890
|
transient: refreshMark,
|
|
2625
2891
|
leaving,
|
|
2892
|
+
pending,
|
|
2626
2893
|
transitAt,
|
|
2627
2894
|
prefix = "",
|
|
2628
2895
|
parent,
|
|
@@ -2673,7 +2940,7 @@ var ItemRow = ({
|
|
|
2673
2940
|
] });
|
|
2674
2941
|
if (item.kind === "task") {
|
|
2675
2942
|
const note = item.note ?? "";
|
|
2676
|
-
const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
|
|
2943
|
+
const transitIcon = pending ? PENDING_GLYPH : !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
|
|
2677
2944
|
const titleMax = Math.max(
|
|
2678
2945
|
1,
|
|
2679
2946
|
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
|
|
@@ -2682,7 +2949,7 @@ var ItemRow = ({
|
|
|
2682
2949
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
|
|
2683
2950
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
2684
2951
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
2685
|
-
/* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2952
|
+
pending ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: transitIcon + " " }) : /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2686
2953
|
markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
|
|
2687
2954
|
/* @__PURE__ */ jsx(Text3, { ...taskKeyStyle(), children: item.key + " " }),
|
|
2688
2955
|
/* @__PURE__ */ jsx(
|
|
@@ -2970,12 +3237,13 @@ var HelpModal = ({
|
|
|
2970
3237
|
["u", "unsubscribe from this item"],
|
|
2971
3238
|
["x", "remove yourself as reviewer"],
|
|
2972
3239
|
...hasJira ? [
|
|
2973
|
-
["t", "Jira: move / open ticket"]
|
|
2974
|
-
["\u2303K", "launch \xB7 jump to a ticket by key"]
|
|
3240
|
+
["t", "Jira: move / open ticket"]
|
|
2975
3241
|
] : [],
|
|
3242
|
+
["\u2303K", "launch \xB7 open a ticket or PR by name"],
|
|
2976
3243
|
["/", "search"],
|
|
2977
3244
|
["f", "filter by repo"],
|
|
2978
3245
|
["r", "refresh"],
|
|
3246
|
+
["w", "restore a change that failed"],
|
|
2979
3247
|
...extensionLegend(extensions),
|
|
2980
3248
|
["?", "this help"],
|
|
2981
3249
|
["q", "quit"]
|
|
@@ -3183,7 +3451,11 @@ var BrowseScreen = ({
|
|
|
3183
3451
|
transients,
|
|
3184
3452
|
onTabChange,
|
|
3185
3453
|
sidebar,
|
|
3186
|
-
liveLabel
|
|
3454
|
+
liveLabel,
|
|
3455
|
+
optimisticMove,
|
|
3456
|
+
pendingKeys,
|
|
3457
|
+
failure,
|
|
3458
|
+
onRestore
|
|
3187
3459
|
}) => {
|
|
3188
3460
|
const { rows } = useWindowSize();
|
|
3189
3461
|
const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match, login) : secs;
|
|
@@ -3298,6 +3570,14 @@ var BrowseScreen = ({
|
|
|
3298
3570
|
if (flashTimer.current) clearTimeout(flashTimer.current);
|
|
3299
3571
|
flashTimer.current = setTimeout(() => setFlash(null), 1500);
|
|
3300
3572
|
};
|
|
3573
|
+
const [dismissedSeq, setDismissedSeq] = useState(null);
|
|
3574
|
+
const noticeUp = !!failure && dismissedSeq !== failure.seq;
|
|
3575
|
+
useEffect(() => {
|
|
3576
|
+
if (!failure) return;
|
|
3577
|
+
const seq = failure.seq;
|
|
3578
|
+
const timer = setTimeout(() => setDismissedSeq(seq), FAILURE_NOTICE_MS);
|
|
3579
|
+
return () => clearTimeout(timer);
|
|
3580
|
+
}, [failure?.seq]);
|
|
3301
3581
|
const markedTabs = useMemo(() => {
|
|
3302
3582
|
const out = /* @__PURE__ */ new Set();
|
|
3303
3583
|
if (!transients?.size) return out;
|
|
@@ -3323,6 +3603,7 @@ var BrowseScreen = ({
|
|
|
3323
3603
|
if (repoPicker) return setRepoPicker(false), true;
|
|
3324
3604
|
if (railActive) return setRailFocus(false), true;
|
|
3325
3605
|
if (menu.actions !== null) return menu.close(), true;
|
|
3606
|
+
if (noticeUp && failure) return setDismissedSeq(failure.seq), true;
|
|
3326
3607
|
if (search != null) return filter.clear(), true;
|
|
3327
3608
|
if (repoFilter.size > 0) return setRepoFilter(/* @__PURE__ */ new Set()), true;
|
|
3328
3609
|
return false;
|
|
@@ -3428,7 +3709,8 @@ var BrowseScreen = ({
|
|
|
3428
3709
|
open: (rows2) => menu.open(rows2),
|
|
3429
3710
|
isOpen: () => menuActionsRef.current !== null,
|
|
3430
3711
|
showFlash,
|
|
3431
|
-
onActed
|
|
3712
|
+
onActed,
|
|
3713
|
+
optimistic: optimisticMove ? (to) => optimisticMove(task, to) : void 0
|
|
3432
3714
|
});
|
|
3433
3715
|
};
|
|
3434
3716
|
const openMenu = () => {
|
|
@@ -3504,6 +3786,10 @@ var BrowseScreen = ({
|
|
|
3504
3786
|
return;
|
|
3505
3787
|
}
|
|
3506
3788
|
if (searchInput && !key.upArrow && !key.downArrow) return;
|
|
3789
|
+
if (input === "w" && failure && onRestore) {
|
|
3790
|
+
onRestore();
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3507
3793
|
if (input === "f" && allRepos.length > 0) {
|
|
3508
3794
|
setRepoCursor(0);
|
|
3509
3795
|
setRepoPicker(true);
|
|
@@ -3861,9 +4147,10 @@ var BrowseScreen = ({
|
|
|
3861
4147
|
["\u2190\u2192", "tab"],
|
|
3862
4148
|
["\u21B5/d", "open"],
|
|
3863
4149
|
["m", "actions"],
|
|
3864
|
-
//
|
|
3865
|
-
//
|
|
3866
|
-
|
|
4150
|
+
// Always, now that the launcher opens a pasted PR or issue too. It was
|
|
4151
|
+
// shown only where Jira was configured, when a ticket key was all it
|
|
4152
|
+
// could take and without one it could only apologise.
|
|
4153
|
+
["\u2303K", "launch"],
|
|
3867
4154
|
// Advertised only where it does something, and named for what it
|
|
3868
4155
|
// shows rather than for the furniture: nobody wants "a sidebar".
|
|
3869
4156
|
...showRail ? [["\u21E5", railTitle]] : hasRail ? [["i", railTitle]] : [],
|
|
@@ -3877,10 +4164,16 @@ var BrowseScreen = ({
|
|
|
3877
4164
|
typing: searchInput,
|
|
3878
4165
|
nerd: getIconMode() === "nerd"
|
|
3879
4166
|
});
|
|
4167
|
+
const launcherLookup = useLauncherLookup(palette, (item) => {
|
|
4168
|
+
if (!openDrillView(settle(localSections, item))) return false;
|
|
4169
|
+
setPalette(null);
|
|
4170
|
+
return true;
|
|
4171
|
+
});
|
|
3880
4172
|
const launcher = useMemo(() => {
|
|
3881
4173
|
const run = /* @__PURE__ */ new Map();
|
|
4174
|
+
const keepOpen = /* @__PURE__ */ new Set();
|
|
3882
4175
|
if (palette === null)
|
|
3883
|
-
return { items: [], run, message: void 0 };
|
|
4176
|
+
return { items: [], run, keepOpen, message: void 0 };
|
|
3884
4177
|
const query = palette.trim();
|
|
3885
4178
|
const ticketKey = jiraKeyRe ? query.toUpperCase().match(jiraKeyRe)?.[0] : void 0;
|
|
3886
4179
|
const commands = [];
|
|
@@ -3891,6 +4184,31 @@ var BrowseScreen = ({
|
|
|
3891
4184
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: where })
|
|
3892
4185
|
] });
|
|
3893
4186
|
const labels = /* @__PURE__ */ new Map();
|
|
4187
|
+
const claim = claimFor(
|
|
4188
|
+
query,
|
|
4189
|
+
{
|
|
4190
|
+
activeRepo: activeItem?.kind === "pr" || activeItem?.kind === "issue" ? activeItem.repo : void 0,
|
|
4191
|
+
repos: reposInSections(localSections),
|
|
4192
|
+
sections: localSections
|
|
4193
|
+
},
|
|
4194
|
+
extensions
|
|
4195
|
+
);
|
|
4196
|
+
const claimed = claim ? lookupRows(
|
|
4197
|
+
claim,
|
|
4198
|
+
launcherLookup.lookup,
|
|
4199
|
+
palette,
|
|
4200
|
+
launcherLookup.start,
|
|
4201
|
+
(url, label) => {
|
|
4202
|
+
quietly`open ${url}`.catch(() => {
|
|
4203
|
+
});
|
|
4204
|
+
showFlash(`\u2197 Opened ${label} in browser`);
|
|
4205
|
+
}
|
|
4206
|
+
) : void 0;
|
|
4207
|
+
for (const row of claimed?.rows ?? []) {
|
|
4208
|
+
commands.push({ id: row.id, title: row.title, run: row.run });
|
|
4209
|
+
labels.set(row.id, row.label);
|
|
4210
|
+
if (row.keepOpen) keepOpen.add(row.id);
|
|
4211
|
+
}
|
|
3894
4212
|
if (ticketKey && jiraBase) {
|
|
3895
4213
|
const row = {
|
|
3896
4214
|
kind: "task",
|
|
@@ -3926,16 +4244,26 @@ var BrowseScreen = ({
|
|
|
3926
4244
|
commands.push({ ...command, id: `${ext.id}:${command.id}` });
|
|
3927
4245
|
}
|
|
3928
4246
|
}
|
|
4247
|
+
if (claimed?.exclusive) commands.splice(claimed.rows.length);
|
|
3929
4248
|
for (const command of commands) run.set(command.id, command.run);
|
|
3930
|
-
const message = !jiraBase ? `Jira not configured${jiraSetupHint ? ` \xB7 ${jiraSetupHint}` : ""}` : query && !ticketKey ? `
|
|
4249
|
+
const message = claimed ? claimed.message : !jiraBase ? `Jira not configured${jiraSetupHint ? ` \xB7 ${jiraSetupHint}` : ""}` : query && !ticketKey ? `nothing matches "${query}"` : void 0;
|
|
3931
4250
|
const items = commands.map((command) => ({
|
|
3932
4251
|
id: command.id,
|
|
3933
4252
|
title: command.title,
|
|
3934
4253
|
group: command.group,
|
|
3935
4254
|
label: labels.get(command.id)
|
|
3936
4255
|
}));
|
|
3937
|
-
return { items, run, message };
|
|
3938
|
-
}, [
|
|
4256
|
+
return { items, run, keepOpen, message };
|
|
4257
|
+
}, [
|
|
4258
|
+
palette,
|
|
4259
|
+
activeItem,
|
|
4260
|
+
extensions,
|
|
4261
|
+
jiraBase,
|
|
4262
|
+
jiraKeyRe,
|
|
4263
|
+
jiraSetupHint,
|
|
4264
|
+
localSections,
|
|
4265
|
+
launcherLookup.lookup
|
|
4266
|
+
]);
|
|
3939
4267
|
const chrome = useContext(InboxChromeContext);
|
|
3940
4268
|
const overlayOpen = overlayBody != null;
|
|
3941
4269
|
const overlay = palette !== null ? /* @__PURE__ */ jsx(
|
|
@@ -3943,15 +4271,18 @@ var BrowseScreen = ({
|
|
|
3943
4271
|
{
|
|
3944
4272
|
items: launcher.items,
|
|
3945
4273
|
query: palette,
|
|
3946
|
-
onQueryChange:
|
|
4274
|
+
onQueryChange: (query) => {
|
|
4275
|
+
launcherLookup.reset();
|
|
4276
|
+
setPalette(query);
|
|
4277
|
+
},
|
|
3947
4278
|
onSelect: (id) => {
|
|
3948
4279
|
const run = launcher.run.get(id);
|
|
3949
|
-
setPalette(null);
|
|
4280
|
+
if (!launcher.keepOpen.has(id)) setPalette(null);
|
|
3950
4281
|
void run?.();
|
|
3951
4282
|
},
|
|
3952
4283
|
onClose: () => setPalette(null),
|
|
3953
4284
|
message: launcher.message,
|
|
3954
|
-
placeholder: "ticket
|
|
4285
|
+
placeholder: "ticket, PR, owner/repo#N or URL\u2026",
|
|
3955
4286
|
width: Math.min(60, Math.max(30, listCols - 2)),
|
|
3956
4287
|
maxRows: Math.max(3, listHeight - 6),
|
|
3957
4288
|
hints: [
|
|
@@ -4054,7 +4385,8 @@ var BrowseScreen = ({
|
|
|
4054
4385
|
s.id,
|
|
4055
4386
|
pulseSettled ? PULSE_SETTLED_FRAME : sparkFrame
|
|
4056
4387
|
),
|
|
4057
|
-
markerColor: colors.accent
|
|
4388
|
+
markerColor: colors.accent,
|
|
4389
|
+
group: s.group
|
|
4058
4390
|
}))
|
|
4059
4391
|
}
|
|
4060
4392
|
) }),
|
|
@@ -4105,6 +4437,7 @@ var BrowseScreen = ({
|
|
|
4105
4437
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
4106
4438
|
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
4107
4439
|
transient: transientOf(transients, item, section.id),
|
|
4440
|
+
pending: !!pendingKeys?.has(keyOf(item) ?? ""),
|
|
4108
4441
|
transitAt: "url" in item ? transitSince?.get(item.url) : void 0,
|
|
4109
4442
|
prefix: treePrefix(section.items, viewStart + i),
|
|
4110
4443
|
parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
|
|
@@ -4153,7 +4486,7 @@ var BrowseScreen = ({
|
|
|
4153
4486
|
}
|
|
4154
4487
|
) : null
|
|
4155
4488
|
] }),
|
|
4156
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : overlayOpen ? (
|
|
4489
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: noticeUp && failure ? /* @__PURE__ */ jsx(Text3, { color: colors.error, children: failure.notice }) : flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : overlayOpen ? (
|
|
4157
4490
|
// The overlay's own verbs, plus the way back. `page="nested"` is what
|
|
4158
4491
|
// appends `⌫ back` and `q quit`, so a body passes only its own keys
|
|
4159
4492
|
// through `useChrome` — the same contract ink-ui's `Page` offers its
|
|
@@ -4202,6 +4535,7 @@ var App = ({
|
|
|
4202
4535
|
jiraSetupHint,
|
|
4203
4536
|
jiraTransitions,
|
|
4204
4537
|
jiraTransitionsFor,
|
|
4538
|
+
tabForStatus,
|
|
4205
4539
|
hasCiStatus,
|
|
4206
4540
|
ciJob,
|
|
4207
4541
|
ciFetcher,
|
|
@@ -4277,12 +4611,14 @@ var App = ({
|
|
|
4277
4611
|
const transitTabs = useRef(/* @__PURE__ */ new Map());
|
|
4278
4612
|
const [heldSince, setHeldSince] = useState(NO_HOLDS);
|
|
4279
4613
|
const [visibleTab, setVisibleTab] = useState("");
|
|
4280
|
-
const showData = (sections, login) => {
|
|
4614
|
+
const showData = (sections, login, fetched = true) => {
|
|
4281
4615
|
const before = displayedSections.current;
|
|
4282
4616
|
displayedSections.current = sections;
|
|
4283
4617
|
displayedKey.current = signatureOf(sections);
|
|
4284
|
-
|
|
4285
|
-
|
|
4618
|
+
if (fetched) {
|
|
4619
|
+
setPending(null);
|
|
4620
|
+
setFetchedAt(Date.now());
|
|
4621
|
+
}
|
|
4286
4622
|
if (before.length === 0) {
|
|
4287
4623
|
transitTabs.current = /* @__PURE__ */ new Map();
|
|
4288
4624
|
setTransients(NO_TRANSIENTS);
|
|
@@ -4371,7 +4707,7 @@ var App = ({
|
|
|
4371
4707
|
if (new Date(known.resetAt).getTime() <= Date.now()) return true;
|
|
4372
4708
|
return known.remaining >= Math.max(known.cost, 1) * 2;
|
|
4373
4709
|
};
|
|
4374
|
-
const revalidate = (manual = false) => {
|
|
4710
|
+
const revalidate = (manual = false, apply = false) => {
|
|
4375
4711
|
const gated = !manual && !!displayedKey.current;
|
|
4376
4712
|
if (gated && !canAffordAuto()) {
|
|
4377
4713
|
setSkippedForBudget(true);
|
|
@@ -4395,7 +4731,7 @@ var App = ({
|
|
|
4395
4731
|
setFetchedAt(Date.now());
|
|
4396
4732
|
setRefreshError(null);
|
|
4397
4733
|
if (hasCiStatus) applyCiStatus(fresh.ciStatus ?? null);
|
|
4398
|
-
receive(fresh);
|
|
4734
|
+
receive(fresh, apply);
|
|
4399
4735
|
}).catch((err) => {
|
|
4400
4736
|
setRefreshing(false);
|
|
4401
4737
|
const message = explainGhAction(err);
|
|
@@ -4406,8 +4742,22 @@ var App = ({
|
|
|
4406
4742
|
setRefreshError({ message, at: Date.now() });
|
|
4407
4743
|
});
|
|
4408
4744
|
};
|
|
4409
|
-
const receive = (
|
|
4745
|
+
const receive = (truth, apply = false) => {
|
|
4746
|
+
overrides.current = pruneOverrides(
|
|
4747
|
+
overrides.current,
|
|
4748
|
+
truth.sections,
|
|
4749
|
+
Date.now()
|
|
4750
|
+
);
|
|
4751
|
+
setOverrideRev((n) => n + 1);
|
|
4752
|
+
const fresh = {
|
|
4753
|
+
...truth,
|
|
4754
|
+
sections: applyOverrides(truth.sections, overrides.current)
|
|
4755
|
+
};
|
|
4410
4756
|
const freshKey = signatureOf(fresh.sections);
|
|
4757
|
+
if (apply && displayedKey.current) {
|
|
4758
|
+
showData(fresh.sections, fresh.login);
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4411
4761
|
if (!displayedKey.current) {
|
|
4412
4762
|
if (fresh.sections.length === 0) {
|
|
4413
4763
|
setState({ phase: "empty" });
|
|
@@ -4427,6 +4777,74 @@ var App = ({
|
|
|
4427
4777
|
if (pending) showData(pending.sections, pending.login);
|
|
4428
4778
|
else revalidate(true);
|
|
4429
4779
|
};
|
|
4780
|
+
const overrides = useRef(/* @__PURE__ */ new Map());
|
|
4781
|
+
const [overrideRev, setOverrideRev] = useState(0);
|
|
4782
|
+
const failureSeq = useRef(0);
|
|
4783
|
+
const pendingKeys = useMemo(
|
|
4784
|
+
() => new Set(
|
|
4785
|
+
[...overrides.current].filter(([, o]) => o.phase === "inflight").map(([key]) => key)
|
|
4786
|
+
),
|
|
4787
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4788
|
+
[overrideRev]
|
|
4789
|
+
);
|
|
4790
|
+
const failure = useMemo(() => {
|
|
4791
|
+
const failed = failedOverrides(overrides.current);
|
|
4792
|
+
const notice = failureNotice(failed);
|
|
4793
|
+
const seq = failed[0]?.[1].failure?.seq;
|
|
4794
|
+
return notice && seq != null ? { notice, seq } : void 0;
|
|
4795
|
+
}, [overrideRev]);
|
|
4796
|
+
const hold = (key, override) => {
|
|
4797
|
+
overrides.current = new Map(overrides.current).set(key, override);
|
|
4798
|
+
setOverrideRev((n) => n + 1);
|
|
4799
|
+
const one = /* @__PURE__ */ new Map([[key, override]]);
|
|
4800
|
+
if (state.phase === "browse")
|
|
4801
|
+
showData(
|
|
4802
|
+
applyOverrides(displayedSections.current, one),
|
|
4803
|
+
state.login,
|
|
4804
|
+
false
|
|
4805
|
+
);
|
|
4806
|
+
setPending(
|
|
4807
|
+
(p) => p ? { ...p, sections: applyOverrides(p.sections, one) } : p
|
|
4808
|
+
);
|
|
4809
|
+
const answer = (next) => {
|
|
4810
|
+
if (overrides.current.get(key) !== override) return;
|
|
4811
|
+
overrides.current = new Map(overrides.current).set(key, {
|
|
4812
|
+
...override,
|
|
4813
|
+
...next
|
|
4814
|
+
});
|
|
4815
|
+
setOverrideRev((n) => n + 1);
|
|
4816
|
+
};
|
|
4817
|
+
return {
|
|
4818
|
+
// Answered yes: only now does the hold clock matter — see OVERRIDE_HOLD_MS.
|
|
4819
|
+
settled: () => answer({ phase: "sent", since: Date.now() }),
|
|
4820
|
+
failed: (err) => answer({
|
|
4821
|
+
phase: "failed",
|
|
4822
|
+
failure: { reason: explainGhAction(err), seq: ++failureSeq.current }
|
|
4823
|
+
})
|
|
4824
|
+
};
|
|
4825
|
+
};
|
|
4826
|
+
const optimisticMove = tabForStatus ? (task, to) => {
|
|
4827
|
+
const key = keyOf(task);
|
|
4828
|
+
const toSection = tabForStatus(to);
|
|
4829
|
+
if (!key || !toSection) return void 0;
|
|
4830
|
+
return hold(key, {
|
|
4831
|
+
patch: { kind: "move", toSection, fields: { status: to.name } },
|
|
4832
|
+
action: "move",
|
|
4833
|
+
label: task.ticket ?? task.key,
|
|
4834
|
+
since: Date.now(),
|
|
4835
|
+
phase: "inflight"
|
|
4836
|
+
});
|
|
4837
|
+
} : void 0;
|
|
4838
|
+
const restore = () => {
|
|
4839
|
+
const newest = failedOverrides(overrides.current)[0];
|
|
4840
|
+
if (!newest) return;
|
|
4841
|
+
const next = new Map(overrides.current);
|
|
4842
|
+
next.delete(newest[0]);
|
|
4843
|
+
overrides.current = next;
|
|
4844
|
+
setOverrideRev((n) => n + 1);
|
|
4845
|
+
if (cacheKey) invalidateCache(cacheKey);
|
|
4846
|
+
revalidate(true, true);
|
|
4847
|
+
};
|
|
4430
4848
|
const onActed = () => {
|
|
4431
4849
|
if (cacheKey) invalidateCache(cacheKey);
|
|
4432
4850
|
setTimeout(() => applyOrRefresh(), 1500);
|
|
@@ -4651,6 +5069,10 @@ var App = ({
|
|
|
4651
5069
|
jiraSetupHint,
|
|
4652
5070
|
jiraTransitions,
|
|
4653
5071
|
jiraTransitionsFor,
|
|
5072
|
+
optimisticMove,
|
|
5073
|
+
pendingKeys,
|
|
5074
|
+
failure,
|
|
5075
|
+
onRestore: restore,
|
|
4654
5076
|
onRefresh: applyOrRefresh,
|
|
4655
5077
|
onActed,
|
|
4656
5078
|
onSettled,
|
|
@@ -4727,4 +5149,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
4727
5149
|
};
|
|
4728
5150
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
4729
5151
|
|
|
4730
|
-
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, 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 };
|
|
5152
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, LookupMiss, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.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",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@kud/ink-ui": ">=0.
|
|
47
|
+
"@kud/ink-ui": ">=0.36.0 <1",
|
|
48
48
|
"ink": ">=7",
|
|
49
49
|
"react": ">=19"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@kud/gh": "0.
|
|
53
|
-
"@kud/gh-workflow": "0.
|
|
52
|
+
"@kud/gh": "0.21.0",
|
|
53
|
+
"@kud/gh-workflow": "0.16.0",
|
|
54
54
|
"zx": "8.8.5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@kud/ink-ui": "0.
|
|
57
|
+
"@kud/ink-ui": "0.36.0",
|
|
58
58
|
"@types/node": "22.20.1",
|
|
59
59
|
"@types/react": "19.2.17",
|
|
60
60
|
"ink": "7.1.0",
|