@kud/gh-ink 0.72.0 → 0.74.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 +78 -2
- package/dist/index.js +393 -165
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -526,6 +526,27 @@ declare const writeCache: (key: string, data: {
|
|
|
526
526
|
sidebar?: Rails;
|
|
527
527
|
}) => void;
|
|
528
528
|
|
|
529
|
+
/**
|
|
530
|
+
* What an action did to a row, said before the server has agreed to it.
|
|
531
|
+
*
|
|
532
|
+
* Three shapes cover every action the inbox takes on a row: it leaves the list
|
|
533
|
+
* (close, done), it changes tab (a Jira move), or it changes in place (labels,
|
|
534
|
+
* assignee, read). A patch names the END STATE rather than the step that gets
|
|
535
|
+
* there, which is what lets it be reapplied on top of every fetch: "SHOP-1234
|
|
536
|
+
* belongs in review, wearing In Review" is as true on the third fetch as on the
|
|
537
|
+
* first, where "move it one tab right" would walk it off the board.
|
|
538
|
+
*/
|
|
539
|
+
type Patch = {
|
|
540
|
+
kind: "remove";
|
|
541
|
+
} | {
|
|
542
|
+
kind: "move";
|
|
543
|
+
toSection: string;
|
|
544
|
+
fields?: Record<string, unknown>;
|
|
545
|
+
} | {
|
|
546
|
+
kind: "mutate";
|
|
547
|
+
fields: Record<string, unknown>;
|
|
548
|
+
};
|
|
549
|
+
|
|
529
550
|
type DetailContext = {
|
|
530
551
|
item: GHItem;
|
|
531
552
|
kind: "pr" | "issue";
|
|
@@ -604,10 +625,50 @@ type JiraTransition = {
|
|
|
604
625
|
type JiraAvailableTransition = {
|
|
605
626
|
id: string;
|
|
606
627
|
name: string;
|
|
628
|
+
/**
|
|
629
|
+
* The status the transition lands on. `category` is Jira's status category
|
|
630
|
+
* (`new`, `indeterminate`, `done`) where the host has it — the one field a
|
|
631
|
+
* `tabForStatus` can map without knowing every workflow's status names.
|
|
632
|
+
*/
|
|
607
633
|
to: {
|
|
608
634
|
name: string;
|
|
635
|
+
category?: string;
|
|
609
636
|
};
|
|
610
637
|
};
|
|
638
|
+
/**
|
|
639
|
+
* Which tab a ticket belongs in once it lands on `to`, as the host lays its
|
|
640
|
+
* tabs out — the section id, or null when the ticket leaves the board or the
|
|
641
|
+
* host cannot say. Only the host knows: one board files by status category,
|
|
642
|
+
* another by a status-to-column table, and the inbox has neither.
|
|
643
|
+
*
|
|
644
|
+
* Supplying it makes a move optimistic: the row goes to that tab the moment
|
|
645
|
+
* the move is chosen instead of at the next fetch. Null keeps the move
|
|
646
|
+
* pessimistic, exactly as it is without the prop — a row guessed off the board
|
|
647
|
+
* that turns out to still be on it is worse than one that waits for the fetch.
|
|
648
|
+
*/
|
|
649
|
+
type TabForStatus = (to: {
|
|
650
|
+
name: string;
|
|
651
|
+
category?: string;
|
|
652
|
+
}) => string | null;
|
|
653
|
+
/**
|
|
654
|
+
* What a move hands back when the screen took it optimistically: the request's
|
|
655
|
+
* two possible answers, reported so the pending patch can stop showing `◌` or
|
|
656
|
+
* be marked failed. Absent when the move was not taken optimistically, and the
|
|
657
|
+
* caller falls back to its own flashes.
|
|
658
|
+
*/
|
|
659
|
+
type OptimisticHandle = {
|
|
660
|
+
settled: () => void;
|
|
661
|
+
failed: (err: unknown) => void;
|
|
662
|
+
};
|
|
663
|
+
/**
|
|
664
|
+
* A row action said ahead of the server: the end state it leaves the row in,
|
|
665
|
+
* and the verb the failure line names it by — `close` reads back as
|
|
666
|
+
* "Couldn't close #412".
|
|
667
|
+
*/
|
|
668
|
+
type OptimisticChange = {
|
|
669
|
+
patch: Patch;
|
|
670
|
+
action: string;
|
|
671
|
+
};
|
|
611
672
|
/**
|
|
612
673
|
* Resolve the live transitions for a ticket, called lazily when its move
|
|
613
674
|
* submenu opens — never at board fetch, where one call per visible ticket
|
|
@@ -745,6 +806,15 @@ declare const buildActions: (item: AnyItem, login: string, showFlash: (msg: stri
|
|
|
745
806
|
* the settlement signal.
|
|
746
807
|
*/
|
|
747
808
|
onSettled?: (item: GHItem, result: SettledResult) => void;
|
|
809
|
+
/**
|
|
810
|
+
* Take the row's action ahead of the server — see `OptimisticChange`. A
|
|
811
|
+
* handle back means the row is already where the action puts it, so the
|
|
812
|
+
* request's answer goes to the handle and a failure goes ONLY there: the
|
|
813
|
+
* failed patch has its own sticky line with `w` beside it, and a flash
|
|
814
|
+
* naming the same failure would be a second voice saying it less well.
|
|
815
|
+
* Absent, every action keeps the pessimistic shape it had.
|
|
816
|
+
*/
|
|
817
|
+
optimistic?: (item: AnyItem, change: OptimisticChange) => OptimisticHandle | undefined;
|
|
748
818
|
/**
|
|
749
819
|
* Open the ticket's move submenu through the live-transitions hook. Passed
|
|
750
820
|
* by the browse screen (which owns the menu the rows open into) only when
|
|
@@ -810,7 +880,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
|
810
880
|
cols?: number;
|
|
811
881
|
}) => React.JSX.Element;
|
|
812
882
|
declare const signatureOf: (sections: Section[]) => string;
|
|
813
|
-
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, }: {
|
|
883
|
+
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, }: {
|
|
814
884
|
fetcher: () => Promise<{
|
|
815
885
|
sections: Section[];
|
|
816
886
|
login: string;
|
|
@@ -851,6 +921,12 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
851
921
|
jiraSetupHint?: string;
|
|
852
922
|
jiraTransitions?: JiraTransition[];
|
|
853
923
|
jiraTransitionsFor?: JiraTransitionsFor;
|
|
924
|
+
/**
|
|
925
|
+
* Which tab a moved ticket lands in — see TabForStatus. Supplying it makes
|
|
926
|
+
* moves through `jiraTransitionsFor` optimistic; leaving it out keeps every
|
|
927
|
+
* move waiting on the next fetch, as before.
|
|
928
|
+
*/
|
|
929
|
+
tabForStatus?: TabForStatus;
|
|
854
930
|
hasCiStatus?: boolean;
|
|
855
931
|
ciJob?: string;
|
|
856
932
|
ciFetcher?: () => Promise<CiStatus | null>;
|
|
@@ -919,4 +995,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
919
995
|
*/
|
|
920
996
|
declare const parsePatterns: (value: string) => string[];
|
|
921
997
|
|
|
922
|
-
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 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 };
|
|
998
|
+
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
|
@@ -1054,9 +1054,9 @@ var PrRow = ({
|
|
|
1054
1054
|
justifyContent: "flex-end",
|
|
1055
1055
|
flexShrink: 0,
|
|
1056
1056
|
children: sizeParts ? /* @__PURE__ */ jsxs(Text3, { children: [
|
|
1057
|
-
/* @__PURE__ */ jsx(Text3, { color: colors.
|
|
1057
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.added, children: sizeParts.added }),
|
|
1058
1058
|
" ",
|
|
1059
|
-
/* @__PURE__ */ jsx(Text3, { color: colors.
|
|
1059
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.removed, children: sizeParts.removed })
|
|
1060
1060
|
] }) : null
|
|
1061
1061
|
}
|
|
1062
1062
|
) : null,
|
|
@@ -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) => {
|
|
@@ -1335,20 +1335,12 @@ var lookupRows = (claim, lookup, query, start, openUrl) => {
|
|
|
1335
1335
|
if (lookup?.phase === "failed")
|
|
1336
1336
|
return {
|
|
1337
1337
|
exclusive: true,
|
|
1338
|
-
rows: [
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
claim.label,
|
|
1345
|
-
" \xB7 ",
|
|
1346
|
-
lookup.message
|
|
1347
|
-
] }),
|
|
1348
|
-
run: () => start(query, claim),
|
|
1349
|
-
keepOpen: true
|
|
1350
|
-
}
|
|
1351
|
-
]
|
|
1338
|
+
rows: [],
|
|
1339
|
+
message: {
|
|
1340
|
+
text: `couldn't look up ${claim.label} \xB7 ${lookup.message} \xB7 \u21B5 retry`,
|
|
1341
|
+
tone: "error",
|
|
1342
|
+
onSubmit: () => start(query, claim)
|
|
1343
|
+
}
|
|
1352
1344
|
};
|
|
1353
1345
|
const rows = [
|
|
1354
1346
|
{
|
|
@@ -1468,11 +1460,11 @@ var indexRows = (sections) => {
|
|
|
1468
1460
|
};
|
|
1469
1461
|
var rowsOf = (index) => {
|
|
1470
1462
|
const out = /* @__PURE__ */ new Map();
|
|
1471
|
-
for (const [mark,
|
|
1463
|
+
for (const [mark, placed2] of index) {
|
|
1472
1464
|
const key = rowKeyOfMark(mark);
|
|
1473
1465
|
const seen = out.get(key);
|
|
1474
|
-
if (seen) seen.add(
|
|
1475
|
-
else out.set(key, /* @__PURE__ */ new Set([
|
|
1466
|
+
if (seen) seen.add(placed2.sectionId);
|
|
1467
|
+
else out.set(key, /* @__PURE__ */ new Set([placed2.sectionId]));
|
|
1476
1468
|
}
|
|
1477
1469
|
return out;
|
|
1478
1470
|
};
|
|
@@ -1627,8 +1619,76 @@ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
|
|
|
1627
1619
|
}
|
|
1628
1620
|
return out;
|
|
1629
1621
|
};
|
|
1622
|
+
|
|
1623
|
+
// src/inbox/overrides.ts
|
|
1624
|
+
var OVERRIDE_HOLD_MS = 3e4;
|
|
1625
|
+
var withFields = (item, fields) => fields ? { ...item, ...fields } : item;
|
|
1626
|
+
var placed = (item, fields) => {
|
|
1627
|
+
const row = withFields(item, fields);
|
|
1628
|
+
return { ...row, depth: 0, indent: false };
|
|
1629
|
+
};
|
|
1630
|
+
var applyOne = (sections, key, patch) => {
|
|
1631
|
+
const found = sections.flatMap((s) => s.items).find((item) => keyOf(item) === key);
|
|
1632
|
+
if (!found) return sections;
|
|
1633
|
+
if (patch.kind === "mutate")
|
|
1634
|
+
return sections.map((s) => ({
|
|
1635
|
+
...s,
|
|
1636
|
+
items: s.items.map(
|
|
1637
|
+
(item) => keyOf(item) === key ? withFields(item, patch.fields) : item
|
|
1638
|
+
)
|
|
1639
|
+
}));
|
|
1640
|
+
const without = sections.map((s) => ({
|
|
1641
|
+
...s,
|
|
1642
|
+
items: s.items.filter((item) => keyOf(item) !== key)
|
|
1643
|
+
}));
|
|
1644
|
+
if (patch.kind === "remove") return without;
|
|
1645
|
+
if (!without.some((s) => s.id === patch.toSection))
|
|
1646
|
+
return sections.map((s) => ({
|
|
1647
|
+
...s,
|
|
1648
|
+
items: s.items.map(
|
|
1649
|
+
(item) => keyOf(item) === key ? withFields(item, patch.fields) : item
|
|
1650
|
+
)
|
|
1651
|
+
}));
|
|
1652
|
+
return without.map(
|
|
1653
|
+
(s) => s.id === patch.toSection ? { ...s, items: [...s.items, placed(found, patch.fields)] } : s
|
|
1654
|
+
);
|
|
1655
|
+
};
|
|
1656
|
+
var applyOverrides = (sections, overrides) => [...overrides].reduce(
|
|
1657
|
+
(out, [key, override]) => applyOne(out, key, override.patch),
|
|
1658
|
+
sections
|
|
1659
|
+
);
|
|
1660
|
+
var fieldsMatch = (item, fields) => !fields || Object.entries(fields).every(
|
|
1661
|
+
([name, value]) => item[name] === value
|
|
1662
|
+
);
|
|
1663
|
+
var agrees = (sections, key, patch) => {
|
|
1664
|
+
const at = sections.flatMap(
|
|
1665
|
+
(s) => s.items.filter((item) => keyOf(item) === key).map((item) => ({ s, item }))
|
|
1666
|
+
);
|
|
1667
|
+
if (patch.kind === "remove") return at.length === 0;
|
|
1668
|
+
if (at.length === 0) return false;
|
|
1669
|
+
if (patch.kind === "mutate")
|
|
1670
|
+
return at.every(({ item }) => fieldsMatch(item, patch.fields));
|
|
1671
|
+
return at.some(
|
|
1672
|
+
({ s, item }) => s.id === patch.toSection && fieldsMatch(item, patch.fields)
|
|
1673
|
+
);
|
|
1674
|
+
};
|
|
1675
|
+
var pruneOverrides = (overrides, fresh, now, holdMs = OVERRIDE_HOLD_MS) => new Map(
|
|
1676
|
+
[...overrides].filter(([key, override]) => {
|
|
1677
|
+
if (override.phase !== "sent") return true;
|
|
1678
|
+
if (now - override.since > holdMs) return false;
|
|
1679
|
+
return !agrees(fresh, key, override.patch);
|
|
1680
|
+
})
|
|
1681
|
+
);
|
|
1682
|
+
var failedOverrides = (overrides) => [...overrides].filter(([, o]) => o.phase === "failed").sort(([, a], [, b]) => (b.failure?.seq ?? 0) - (a.failure?.seq ?? 0));
|
|
1683
|
+
var failureNotice = (failed) => {
|
|
1684
|
+
const newest = failed[0]?.[1];
|
|
1685
|
+
if (!newest) return null;
|
|
1686
|
+
const more = failed.length > 1 ? ` +${failed.length - 1} more` : "";
|
|
1687
|
+
return `\u2717 Couldn't ${newest.action} ${newest.label}: ${newest.failure?.reason ?? "no reason reported"}${more} w restore`;
|
|
1688
|
+
};
|
|
1630
1689
|
var quietly = $({ quiet: true });
|
|
1631
1690
|
var isMenuGap = (row) => "gap" in row;
|
|
1691
|
+
var rowLabelOf = (item) => item.kind === "task" ? item.ticket ?? item.key : item.kind === "pr" || item.kind === "issue" ? `#${item.number}` : "";
|
|
1632
1692
|
var jiraMovesCache = /* @__PURE__ */ new Map();
|
|
1633
1693
|
var cachedJiraMoves = (ticket, status) => {
|
|
1634
1694
|
const entry = jiraMovesCache.get(ticket);
|
|
@@ -1646,43 +1706,56 @@ var orderJiraMoves = (available, configured) => {
|
|
|
1646
1706
|
if (!live) continue;
|
|
1647
1707
|
known.add(live.name);
|
|
1648
1708
|
ordered.push(
|
|
1649
|
-
resolutions && resolutions.length > 0 ? { label, transition: live.name, resolutions } : { label, transition: live.name }
|
|
1709
|
+
resolutions && resolutions.length > 0 ? { label, transition: live.name, resolutions, to: live.to } : { label, transition: live.name, to: live.to }
|
|
1650
1710
|
);
|
|
1651
1711
|
}
|
|
1652
|
-
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 }));
|
|
1712
|
+
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 }));
|
|
1653
1713
|
return [...ordered, ...rest];
|
|
1654
1714
|
};
|
|
1655
|
-
var jiraMoveActions = (ticket, moves, showFlash, onActed) =>
|
|
1656
|
-
(
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1715
|
+
var jiraMoveActions = (ticket, moves, showFlash, onActed, optimistic) => {
|
|
1716
|
+
const execute = (to, request, done, failed) => {
|
|
1717
|
+
const held = to ? optimistic?.(to) : void 0;
|
|
1718
|
+
void request().then(() => {
|
|
1719
|
+
invalidateJiraMoves(ticket);
|
|
1720
|
+
held?.settled();
|
|
1721
|
+
showFlash(done);
|
|
1722
|
+
onActed?.();
|
|
1723
|
+
}).catch((err) => held ? held.failed(err) : showFlash(failed));
|
|
1724
|
+
};
|
|
1725
|
+
return moves.map(
|
|
1726
|
+
({ label, transition, resolutions, to }) => resolutions && resolutions.length > 0 ? {
|
|
1727
|
+
label,
|
|
1728
|
+
hint: "",
|
|
1729
|
+
run: () => {
|
|
1730
|
+
},
|
|
1731
|
+
subActions: resolutions.map((resolution) => ({
|
|
1732
|
+
label: resolution,
|
|
1733
|
+
hint: "",
|
|
1734
|
+
run: () => {
|
|
1735
|
+
showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
|
|
1736
|
+
execute(
|
|
1737
|
+
to,
|
|
1738
|
+
() => quietly`jira issue move ${ticket} ${transition} --resolution ${resolution}`,
|
|
1739
|
+
`\u2713 ${label} \xB7 ${resolution}`,
|
|
1740
|
+
`\u2717 Move to ${label} failed`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
}))
|
|
1744
|
+
} : {
|
|
1745
|
+
label,
|
|
1663
1746
|
hint: "",
|
|
1664
1747
|
run: () => {
|
|
1665
|
-
showFlash(`\u22EF
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1748
|
+
showFlash(`\u22EF Moving to ${label}\u2026`);
|
|
1749
|
+
execute(
|
|
1750
|
+
to,
|
|
1751
|
+
() => quietly`jira issue move ${ticket} ${transition}`,
|
|
1752
|
+
`\u2713 Moved to ${label}`,
|
|
1753
|
+
`\u2717 Move to ${label} failed`
|
|
1754
|
+
);
|
|
1671
1755
|
}
|
|
1672
|
-
}))
|
|
1673
|
-
} : {
|
|
1674
|
-
label,
|
|
1675
|
-
hint: "",
|
|
1676
|
-
run: () => {
|
|
1677
|
-
showFlash(`\u22EF Moving to ${label}\u2026`);
|
|
1678
|
-
void quietly`jira issue move ${ticket} ${transition}`.then(() => {
|
|
1679
|
-
invalidateJiraMoves(ticket);
|
|
1680
|
-
showFlash(`\u2713 Moved to ${label}`);
|
|
1681
|
-
onActed?.();
|
|
1682
|
-
}).catch(() => showFlash(`\u2717 Move to ${label} failed`));
|
|
1683
1756
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1757
|
+
);
|
|
1758
|
+
};
|
|
1686
1759
|
var jiraLoadingActions = () => [
|
|
1687
1760
|
{ label: "Loading transitions\u2026", hint: "", tone: "dim", run: () => {
|
|
1688
1761
|
} }
|
|
@@ -1696,12 +1769,13 @@ var openJiraMoves = (deps) => {
|
|
|
1696
1769
|
open,
|
|
1697
1770
|
isOpen,
|
|
1698
1771
|
showFlash,
|
|
1699
|
-
onActed
|
|
1772
|
+
onActed,
|
|
1773
|
+
optimistic
|
|
1700
1774
|
} = deps;
|
|
1701
1775
|
const openOrdered = (moves) => {
|
|
1702
1776
|
const ordered = orderJiraMoves(moves, configured);
|
|
1703
1777
|
open(
|
|
1704
|
-
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed) : [
|
|
1778
|
+
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed, optimistic) : [
|
|
1705
1779
|
{
|
|
1706
1780
|
label: "No transitions available",
|
|
1707
1781
|
hint: "",
|
|
@@ -2194,7 +2268,7 @@ var ROW_MAX_COLS = 140;
|
|
|
2194
2268
|
var OVERLAY_BG = "#22222e";
|
|
2195
2269
|
var SELECTION_BG = "#16161f";
|
|
2196
2270
|
var selectionBackground = (active, backdropped) => active && !backdropped ? SELECTION_BG : void 0;
|
|
2197
|
-
var TICKET_KEY_COLOUR =
|
|
2271
|
+
var TICKET_KEY_COLOUR = colors.ticket;
|
|
2198
2272
|
var taskKeyStyle = () => ({
|
|
2199
2273
|
color: TICKET_KEY_COLOUR,
|
|
2200
2274
|
bold: true
|
|
@@ -2216,6 +2290,19 @@ var workCount = (s) => {
|
|
|
2216
2290
|
}
|
|
2217
2291
|
return n;
|
|
2218
2292
|
};
|
|
2293
|
+
var countedSections = (sections, departing) => {
|
|
2294
|
+
if (!departing?.size) return sections;
|
|
2295
|
+
const sent = new Set(sections.map((s) => s.id));
|
|
2296
|
+
return sections.map((s) => {
|
|
2297
|
+
const items = s.items.filter((item) => {
|
|
2298
|
+
const key = keyOf(item);
|
|
2299
|
+
if (!key || !departing.has(key)) return true;
|
|
2300
|
+
const to = departing.get(key);
|
|
2301
|
+
return to != null && (to === s.id || !sent.has(to));
|
|
2302
|
+
});
|
|
2303
|
+
return items.length === s.items.length ? s : { ...s, items };
|
|
2304
|
+
});
|
|
2305
|
+
};
|
|
2219
2306
|
var drillCmd = (item) => {
|
|
2220
2307
|
if (item.kind === "task")
|
|
2221
2308
|
return item.ticket ? `jira issue view ${item.ticket}` : null;
|
|
@@ -2279,6 +2366,7 @@ var openInJira = (jiraBase, key, showFlash) => {
|
|
|
2279
2366
|
});
|
|
2280
2367
|
showFlash(`\u2197 Opened ${key} in Jira`);
|
|
2281
2368
|
};
|
|
2369
|
+
var REMOVE_ME_AS_REVIEWER = "Remove me as reviewer";
|
|
2282
2370
|
var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions, onRefresh, onRemove, onOpenView, ext) => {
|
|
2283
2371
|
if (item.kind === "repo-header" || item.kind === "subgroup-header" || item.kind === "show-more" || item.kind === "show-less")
|
|
2284
2372
|
return [];
|
|
@@ -2383,6 +2471,32 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2383
2471
|
}
|
|
2384
2472
|
const actions = drillAction ? [drillAction, open, copyUrl] : [open, copyUrl];
|
|
2385
2473
|
const closing = [];
|
|
2474
|
+
const removeAhead = (opts) => {
|
|
2475
|
+
const row = item;
|
|
2476
|
+
const held = ext?.optimistic?.(row, {
|
|
2477
|
+
patch: { kind: "remove" },
|
|
2478
|
+
action: opts.action
|
|
2479
|
+
});
|
|
2480
|
+
onRemove?.(row);
|
|
2481
|
+
showFlash(opts.taking);
|
|
2482
|
+
void opts.request().then(
|
|
2483
|
+
() => {
|
|
2484
|
+
held?.settled();
|
|
2485
|
+
if (opts.done) showFlash(opts.done);
|
|
2486
|
+
if (held || opts.refreshes) ext?.onActed?.();
|
|
2487
|
+
if (opts.settles) ext?.onSettled?.(row, { ok: true });
|
|
2488
|
+
},
|
|
2489
|
+
(e) => {
|
|
2490
|
+
if (held) held.failed(e);
|
|
2491
|
+
else {
|
|
2492
|
+
showFlash(opts.refused(e));
|
|
2493
|
+
onRefresh?.();
|
|
2494
|
+
}
|
|
2495
|
+
if (opts.settles)
|
|
2496
|
+
ext?.onSettled?.(row, { ok: false, reason: firstLine(e) });
|
|
2497
|
+
}
|
|
2498
|
+
);
|
|
2499
|
+
};
|
|
2386
2500
|
actions.push({
|
|
2387
2501
|
label: "Copy repo name",
|
|
2388
2502
|
hint: "r",
|
|
@@ -2405,7 +2519,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2405
2519
|
});
|
|
2406
2520
|
if (item.kind === "pr" && item.standing === "queued" && login) {
|
|
2407
2521
|
actions.push({
|
|
2408
|
-
label:
|
|
2522
|
+
label: REMOVE_ME_AS_REVIEWER,
|
|
2409
2523
|
hint: "x",
|
|
2410
2524
|
// Quiet, beside Unsubscribe: both are "less of this PR for me" — no more
|
|
2411
2525
|
// review requests, no more notifications. Guarded by the caller's own
|
|
@@ -2413,25 +2527,17 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2413
2527
|
// review request there is no such row, and none is invented here.
|
|
2414
2528
|
group: "quiet",
|
|
2415
2529
|
icon: "\uF468",
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
onRefresh?.();
|
|
2428
|
-
ext?.onSettled?.(item, {
|
|
2429
|
-
ok: false,
|
|
2430
|
-
reason: firstLine(e)
|
|
2431
|
-
});
|
|
2432
|
-
}
|
|
2433
|
-
);
|
|
2434
|
-
}
|
|
2530
|
+
// Optimistic, the same shape Close uses. A row that lingers for a round
|
|
2531
|
+
// trip reads as a broken keypress.
|
|
2532
|
+
run: () => removeAhead({
|
|
2533
|
+
action: "remove you from",
|
|
2534
|
+
request: () => quietly`gh pr edit ${item.number} --repo ${item.repo} --remove-reviewer ${login}`,
|
|
2535
|
+
taking: `\u22EF Removing you from #${item.number}\u2026`,
|
|
2536
|
+
done: `\u2713 Removed you as reviewer on #${item.number}`,
|
|
2537
|
+
refused: (e) => `\u2717 #${item.number}: ${explainGhAction(e)}`,
|
|
2538
|
+
settles: true,
|
|
2539
|
+
refreshes: true
|
|
2540
|
+
})
|
|
2435
2541
|
});
|
|
2436
2542
|
}
|
|
2437
2543
|
if (item.kind === "pr" && item.branch) {
|
|
@@ -2503,23 +2609,13 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2503
2609
|
label: `Close #${item.number}`,
|
|
2504
2610
|
hint: "",
|
|
2505
2611
|
group: "confirm",
|
|
2506
|
-
run: () => {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
(e) => {
|
|
2514
|
-
showFlash(`\u2717 Close failed \u2014 restoring #${item.number}`);
|
|
2515
|
-
onRefresh?.();
|
|
2516
|
-
ext?.onSettled?.(item, {
|
|
2517
|
-
ok: false,
|
|
2518
|
-
reason: firstLine(e)
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
);
|
|
2522
|
-
}
|
|
2612
|
+
run: () => removeAhead({
|
|
2613
|
+
action: "close",
|
|
2614
|
+
request: () => quietly`gh issue close ${item.number} --repo ${item.repo}`,
|
|
2615
|
+
taking: `\u2713 Closed #${item.number}`,
|
|
2616
|
+
refused: () => `\u2717 Close failed \u2014 restoring #${item.number}`,
|
|
2617
|
+
settles: true
|
|
2618
|
+
})
|
|
2523
2619
|
},
|
|
2524
2620
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2525
2621
|
} }
|
|
@@ -2570,23 +2666,13 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2570
2666
|
label: `Close #${item.number}`,
|
|
2571
2667
|
hint: "",
|
|
2572
2668
|
group: "confirm",
|
|
2573
|
-
run: () => {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
(e) => {
|
|
2581
|
-
showFlash(`\u2717 Close failed \u2014 restoring #${item.number}`);
|
|
2582
|
-
onRefresh?.();
|
|
2583
|
-
ext?.onSettled?.(item, {
|
|
2584
|
-
ok: false,
|
|
2585
|
-
reason: firstLine(e)
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
);
|
|
2589
|
-
}
|
|
2669
|
+
run: () => removeAhead({
|
|
2670
|
+
action: "close",
|
|
2671
|
+
request: () => quietly`gh pr close ${item.number} --repo ${item.repo}`,
|
|
2672
|
+
taking: `\u2713 Closed #${item.number}`,
|
|
2673
|
+
refused: () => `\u2717 Close failed \u2014 restoring #${item.number}`,
|
|
2674
|
+
settles: true
|
|
2675
|
+
})
|
|
2590
2676
|
},
|
|
2591
2677
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2592
2678
|
} }
|
|
@@ -2607,16 +2693,23 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2607
2693
|
label: `Close #${item.number} and delete ${item.branch}`,
|
|
2608
2694
|
hint: "",
|
|
2609
2695
|
group: "confirm",
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2696
|
+
// The patch covers the close and nothing else. A branch that will
|
|
2697
|
+
// not delete leaves a PR that IS closed, so reporting "Couldn't
|
|
2698
|
+
// close #412" and offering `w` would restore a row the server no
|
|
2699
|
+
// longer has; the branch gets its own flash instead.
|
|
2700
|
+
run: () => removeAhead({
|
|
2701
|
+
action: "close",
|
|
2702
|
+
request: async () => {
|
|
2703
|
+
await quietly`gh pr close ${item.number} --repo ${item.repo}`;
|
|
2704
|
+
void quietly`gh api -X DELETE ${`repos/${item.repo}/git/refs/heads/${item.branch}`}`.catch(
|
|
2705
|
+
() => showFlash(
|
|
2706
|
+
`\u2717 Closed #${item.number}, but couldn't delete ${item.branch}`
|
|
2707
|
+
)
|
|
2708
|
+
);
|
|
2709
|
+
},
|
|
2710
|
+
taking: `\u2713 Closed #${item.number} and deleted ${item.branch}`,
|
|
2711
|
+
refused: () => `\u2717 Close + delete failed \u2014 restoring`
|
|
2712
|
+
})
|
|
2620
2713
|
},
|
|
2621
2714
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2622
2715
|
} }
|
|
@@ -2778,6 +2871,8 @@ var PULSE_IDLE_MS = 6e4;
|
|
|
2778
2871
|
var PULSE_SETTLED_FRAME = 3;
|
|
2779
2872
|
var NO_TRANSIENTS = /* @__PURE__ */ new Map();
|
|
2780
2873
|
var NO_HOLDS = /* @__PURE__ */ new Map();
|
|
2874
|
+
var PENDING_GLYPH = "\u25CC";
|
|
2875
|
+
var FAILURE_NOTICE_MS = 8e3;
|
|
2781
2876
|
var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
|
|
2782
2877
|
var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
2783
2878
|
var TRANSIT_COLOUR = {
|
|
@@ -2806,6 +2901,7 @@ var ItemRow = ({
|
|
|
2806
2901
|
merged,
|
|
2807
2902
|
transient: refreshMark,
|
|
2808
2903
|
leaving,
|
|
2904
|
+
pending,
|
|
2809
2905
|
transitAt,
|
|
2810
2906
|
prefix = "",
|
|
2811
2907
|
parent,
|
|
@@ -2856,7 +2952,7 @@ var ItemRow = ({
|
|
|
2856
2952
|
] });
|
|
2857
2953
|
if (item.kind === "task") {
|
|
2858
2954
|
const note = item.note ?? "";
|
|
2859
|
-
const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
|
|
2955
|
+
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";
|
|
2860
2956
|
const titleMax = Math.max(
|
|
2861
2957
|
1,
|
|
2862
2958
|
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
|
|
@@ -2865,7 +2961,7 @@ var ItemRow = ({
|
|
|
2865
2961
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
|
|
2866
2962
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
2867
2963
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
2868
|
-
/* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2964
|
+
pending ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: transitIcon + " " }) : /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2869
2965
|
markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
|
|
2870
2966
|
/* @__PURE__ */ jsx(Text3, { ...taskKeyStyle(), children: item.key + " " }),
|
|
2871
2967
|
/* @__PURE__ */ jsx(
|
|
@@ -2899,7 +2995,7 @@ var ItemRow = ({
|
|
|
2899
2995
|
const icon = merged ? {
|
|
2900
2996
|
glyph: MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length],
|
|
2901
2997
|
color: MERGED_COLOUR
|
|
2902
|
-
} : transient ? {
|
|
2998
|
+
} : pending ? { glyph: PENDING_GLYPH, color: colors.muted } : transient ? {
|
|
2903
2999
|
glyph: isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : displayFor(item.health).glyph,
|
|
2904
3000
|
color: TRANSIT_COLOUR[transient]
|
|
2905
3001
|
} : void 0;
|
|
@@ -2959,17 +3055,18 @@ var useActionMenu = () => {
|
|
|
2959
3055
|
};
|
|
2960
3056
|
const handleKey = (key) => {
|
|
2961
3057
|
if (!actions) return false;
|
|
3058
|
+
const live = (n) => n >= 0 && n < actions.length && !isMenuGap(actions[n]);
|
|
2962
3059
|
if (key.upArrow)
|
|
2963
3060
|
setCursor((c) => {
|
|
2964
3061
|
let n = c - 1;
|
|
2965
3062
|
while (n > 0 && isMenuGap(actions[n])) n -= 1;
|
|
2966
|
-
return
|
|
3063
|
+
return live(n) ? n : c;
|
|
2967
3064
|
});
|
|
2968
3065
|
if (key.downArrow)
|
|
2969
3066
|
setCursor((c) => {
|
|
2970
3067
|
let n = c + 1;
|
|
2971
3068
|
while (n < actions.length - 1 && isMenuGap(actions[n])) n += 1;
|
|
2972
|
-
return
|
|
3069
|
+
return live(n) ? n : c;
|
|
2973
3070
|
});
|
|
2974
3071
|
if (key.return) {
|
|
2975
3072
|
const row = actions[cursor];
|
|
@@ -3159,6 +3256,7 @@ var HelpModal = ({
|
|
|
3159
3256
|
["/", "search"],
|
|
3160
3257
|
["f", "filter by repo"],
|
|
3161
3258
|
["r", "refresh"],
|
|
3259
|
+
["w", "restore a change that failed"],
|
|
3162
3260
|
...extensionLegend(extensions),
|
|
3163
3261
|
["?", "this help"],
|
|
3164
3262
|
["q", "quit"]
|
|
@@ -3366,7 +3464,13 @@ var BrowseScreen = ({
|
|
|
3366
3464
|
transients,
|
|
3367
3465
|
onTabChange,
|
|
3368
3466
|
sidebar,
|
|
3369
|
-
liveLabel
|
|
3467
|
+
liveLabel,
|
|
3468
|
+
optimisticMove,
|
|
3469
|
+
optimistic,
|
|
3470
|
+
departing,
|
|
3471
|
+
pendingKeys,
|
|
3472
|
+
failure,
|
|
3473
|
+
onRestore
|
|
3370
3474
|
}) => {
|
|
3371
3475
|
const { rows } = useWindowSize();
|
|
3372
3476
|
const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match, login) : secs;
|
|
@@ -3481,6 +3585,22 @@ var BrowseScreen = ({
|
|
|
3481
3585
|
if (flashTimer.current) clearTimeout(flashTimer.current);
|
|
3482
3586
|
flashTimer.current = setTimeout(() => setFlash(null), 1500);
|
|
3483
3587
|
};
|
|
3588
|
+
const [dismissedSeq, setDismissedSeq] = useState(null);
|
|
3589
|
+
const noticeUp = !!failure && dismissedSeq !== failure.seq;
|
|
3590
|
+
useEffect(() => {
|
|
3591
|
+
if (!failure) return;
|
|
3592
|
+
const seq = failure.seq;
|
|
3593
|
+
const timer = setTimeout(() => setDismissedSeq(seq), FAILURE_NOTICE_MS);
|
|
3594
|
+
return () => clearTimeout(timer);
|
|
3595
|
+
}, [failure?.seq]);
|
|
3596
|
+
const counted = useMemo(
|
|
3597
|
+
() => countedSections(localSections, departing),
|
|
3598
|
+
[localSections, departing]
|
|
3599
|
+
);
|
|
3600
|
+
const countOf = (id) => {
|
|
3601
|
+
const s = counted.find((c) => c.id === id);
|
|
3602
|
+
return s ? workCount(s) : 0;
|
|
3603
|
+
};
|
|
3484
3604
|
const markedTabs = useMemo(() => {
|
|
3485
3605
|
const out = /* @__PURE__ */ new Set();
|
|
3486
3606
|
if (!transients?.size) return out;
|
|
@@ -3506,6 +3626,7 @@ var BrowseScreen = ({
|
|
|
3506
3626
|
if (repoPicker) return setRepoPicker(false), true;
|
|
3507
3627
|
if (railActive) return setRailFocus(false), true;
|
|
3508
3628
|
if (menu.actions !== null) return menu.close(), true;
|
|
3629
|
+
if (noticeUp && failure) return setDismissedSeq(failure.seq), true;
|
|
3509
3630
|
if (search != null) return filter.clear(), true;
|
|
3510
3631
|
if (repoFilter.size > 0) return setRepoFilter(/* @__PURE__ */ new Set()), true;
|
|
3511
3632
|
return false;
|
|
@@ -3611,34 +3732,38 @@ var BrowseScreen = ({
|
|
|
3611
3732
|
open: (rows2) => menu.open(rows2),
|
|
3612
3733
|
isOpen: () => menuActionsRef.current !== null,
|
|
3613
3734
|
showFlash,
|
|
3614
|
-
onActed
|
|
3735
|
+
onActed,
|
|
3736
|
+
optimistic: optimisticMove ? (to) => optimisticMove(task, to) : void 0
|
|
3615
3737
|
});
|
|
3616
3738
|
};
|
|
3739
|
+
const actionsFor = (item, flash2) => buildActions(
|
|
3740
|
+
item,
|
|
3741
|
+
login,
|
|
3742
|
+
flash2,
|
|
3743
|
+
jiraBase,
|
|
3744
|
+
jiraKeyRe,
|
|
3745
|
+
jiraTransitions,
|
|
3746
|
+
onRefresh,
|
|
3747
|
+
dismissItem,
|
|
3748
|
+
openDrillView,
|
|
3749
|
+
{
|
|
3750
|
+
extensions,
|
|
3751
|
+
onOpenExt,
|
|
3752
|
+
onActed,
|
|
3753
|
+
onSettled,
|
|
3754
|
+
optimistic,
|
|
3755
|
+
onOpenMove: jiraTransitionsFor ? openMoveFor : void 0
|
|
3756
|
+
}
|
|
3757
|
+
);
|
|
3617
3758
|
const openMenu = () => {
|
|
3618
3759
|
if (!activeItem || activeItem.kind === "repo-header" || activeItem.kind === "subgroup-header")
|
|
3619
3760
|
return;
|
|
3620
|
-
|
|
3621
|
-
activeItem,
|
|
3622
|
-
login,
|
|
3623
|
-
(msg) => {
|
|
3761
|
+
menu.open(
|
|
3762
|
+
actionsFor(activeItem, (msg) => {
|
|
3624
3763
|
menu.close();
|
|
3625
3764
|
showFlash(msg);
|
|
3626
|
-
}
|
|
3627
|
-
jiraBase,
|
|
3628
|
-
jiraKeyRe,
|
|
3629
|
-
jiraTransitions,
|
|
3630
|
-
onRefresh,
|
|
3631
|
-
dismissItem,
|
|
3632
|
-
openDrillView,
|
|
3633
|
-
{
|
|
3634
|
-
extensions,
|
|
3635
|
-
onOpenExt,
|
|
3636
|
-
onActed,
|
|
3637
|
-
onSettled,
|
|
3638
|
-
onOpenMove: jiraTransitionsFor ? openMoveFor : void 0
|
|
3639
|
-
}
|
|
3765
|
+
})
|
|
3640
3766
|
);
|
|
3641
|
-
menu.open(actions);
|
|
3642
3767
|
};
|
|
3643
3768
|
useInput((input, key) => {
|
|
3644
3769
|
const idleFor = Date.now() - lastKeyAt.current;
|
|
@@ -3687,6 +3812,10 @@ var BrowseScreen = ({
|
|
|
3687
3812
|
return;
|
|
3688
3813
|
}
|
|
3689
3814
|
if (searchInput && !key.upArrow && !key.downArrow) return;
|
|
3815
|
+
if (input === "w" && failure && onRestore) {
|
|
3816
|
+
onRestore();
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3690
3819
|
if (input === "f" && allRepos.length > 0) {
|
|
3691
3820
|
setRepoCursor(0);
|
|
3692
3821
|
setRepoPicker(true);
|
|
@@ -3926,20 +4055,7 @@ var BrowseScreen = ({
|
|
|
3926
4055
|
return;
|
|
3927
4056
|
}
|
|
3928
4057
|
if (input === "x" && activeItem.kind === "pr" && activeItem.standing === "queued" && login) {
|
|
3929
|
-
|
|
3930
|
-
showFlash(`\u22EF Removing you from #${activeItem.number}\u2026`);
|
|
3931
|
-
void quietly`gh pr edit ${activeItem.number} --repo ${activeItem.repo} --remove-reviewer ${login}`.then(
|
|
3932
|
-
() => {
|
|
3933
|
-
showFlash(`\u2713 Removed you as reviewer on #${activeItem.number}`);
|
|
3934
|
-
onActed?.();
|
|
3935
|
-
onSettled?.(activeItem, { ok: true });
|
|
3936
|
-
},
|
|
3937
|
-
(e) => {
|
|
3938
|
-
showFlash(`\u2717 #${activeItem.number}: ${explainGhAction(e)}`);
|
|
3939
|
-
onRefresh?.();
|
|
3940
|
-
onSettled?.(activeItem, { ok: false, reason: firstLine(e) });
|
|
3941
|
-
}
|
|
3942
|
-
);
|
|
4058
|
+
actionsFor(activeItem, showFlash).find((a) => a.label === REMOVE_ME_AS_REVIEWER)?.run();
|
|
3943
4059
|
return;
|
|
3944
4060
|
}
|
|
3945
4061
|
if (input === "b" && activeItem.kind === "pr" && activeItem.branch) {
|
|
@@ -4228,7 +4344,7 @@ var BrowseScreen = ({
|
|
|
4228
4344
|
InboxHeader,
|
|
4229
4345
|
{
|
|
4230
4346
|
brand,
|
|
4231
|
-
sections:
|
|
4347
|
+
sections: counted,
|
|
4232
4348
|
login,
|
|
4233
4349
|
scopeLabel: overlayOpen ? chrome?.scope ?? origin?.label : origin?.label,
|
|
4234
4350
|
budgetLabel: budgetNotice(budget)?.label,
|
|
@@ -4260,7 +4376,7 @@ var BrowseScreen = ({
|
|
|
4260
4376
|
items: localSections.map((s) => ({
|
|
4261
4377
|
value: s.id,
|
|
4262
4378
|
label: s.label,
|
|
4263
|
-
count:
|
|
4379
|
+
count: countOf(s.id),
|
|
4264
4380
|
/*
|
|
4265
4381
|
* The denominator, and only where it can still be true.
|
|
4266
4382
|
*
|
|
@@ -4334,6 +4450,7 @@ var BrowseScreen = ({
|
|
|
4334
4450
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
4335
4451
|
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
4336
4452
|
transient: transientOf(transients, item, section.id),
|
|
4453
|
+
pending: !!pendingKeys?.has(keyOf(item) ?? ""),
|
|
4337
4454
|
transitAt: "url" in item ? transitSince?.get(item.url) : void 0,
|
|
4338
4455
|
prefix: treePrefix(section.items, viewStart + i),
|
|
4339
4456
|
parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
|
|
@@ -4382,7 +4499,7 @@ var BrowseScreen = ({
|
|
|
4382
4499
|
}
|
|
4383
4500
|
) : null
|
|
4384
4501
|
] }),
|
|
4385
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : overlayOpen ? (
|
|
4502
|
+
/* @__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 ? (
|
|
4386
4503
|
// The overlay's own verbs, plus the way back. `page="nested"` is what
|
|
4387
4504
|
// appends `⌫ back` and `q quit`, so a body passes only its own keys
|
|
4388
4505
|
// through `useChrome` — the same contract ink-ui's `Page` offers its
|
|
@@ -4431,6 +4548,7 @@ var App = ({
|
|
|
4431
4548
|
jiraSetupHint,
|
|
4432
4549
|
jiraTransitions,
|
|
4433
4550
|
jiraTransitionsFor,
|
|
4551
|
+
tabForStatus,
|
|
4434
4552
|
hasCiStatus,
|
|
4435
4553
|
ciJob,
|
|
4436
4554
|
ciFetcher,
|
|
@@ -4506,12 +4624,14 @@ var App = ({
|
|
|
4506
4624
|
const transitTabs = useRef(/* @__PURE__ */ new Map());
|
|
4507
4625
|
const [heldSince, setHeldSince] = useState(NO_HOLDS);
|
|
4508
4626
|
const [visibleTab, setVisibleTab] = useState("");
|
|
4509
|
-
const showData = (sections, login) => {
|
|
4627
|
+
const showData = (sections, login, fetched = true) => {
|
|
4510
4628
|
const before = displayedSections.current;
|
|
4511
4629
|
displayedSections.current = sections;
|
|
4512
4630
|
displayedKey.current = signatureOf(sections);
|
|
4513
|
-
|
|
4514
|
-
|
|
4631
|
+
if (fetched) {
|
|
4632
|
+
setPending(null);
|
|
4633
|
+
setFetchedAt(Date.now());
|
|
4634
|
+
}
|
|
4515
4635
|
if (before.length === 0) {
|
|
4516
4636
|
transitTabs.current = /* @__PURE__ */ new Map();
|
|
4517
4637
|
setTransients(NO_TRANSIENTS);
|
|
@@ -4600,7 +4720,7 @@ var App = ({
|
|
|
4600
4720
|
if (new Date(known.resetAt).getTime() <= Date.now()) return true;
|
|
4601
4721
|
return known.remaining >= Math.max(known.cost, 1) * 2;
|
|
4602
4722
|
};
|
|
4603
|
-
const revalidate = (manual = false) => {
|
|
4723
|
+
const revalidate = (manual = false, apply = false) => {
|
|
4604
4724
|
const gated = !manual && !!displayedKey.current;
|
|
4605
4725
|
if (gated && !canAffordAuto()) {
|
|
4606
4726
|
setSkippedForBudget(true);
|
|
@@ -4624,7 +4744,7 @@ var App = ({
|
|
|
4624
4744
|
setFetchedAt(Date.now());
|
|
4625
4745
|
setRefreshError(null);
|
|
4626
4746
|
if (hasCiStatus) applyCiStatus(fresh.ciStatus ?? null);
|
|
4627
|
-
receive(fresh);
|
|
4747
|
+
receive(fresh, apply);
|
|
4628
4748
|
}).catch((err) => {
|
|
4629
4749
|
setRefreshing(false);
|
|
4630
4750
|
const message = explainGhAction(err);
|
|
@@ -4635,8 +4755,22 @@ var App = ({
|
|
|
4635
4755
|
setRefreshError({ message, at: Date.now() });
|
|
4636
4756
|
});
|
|
4637
4757
|
};
|
|
4638
|
-
const receive = (
|
|
4758
|
+
const receive = (truth, apply = false) => {
|
|
4759
|
+
overrides.current = pruneOverrides(
|
|
4760
|
+
overrides.current,
|
|
4761
|
+
truth.sections,
|
|
4762
|
+
Date.now()
|
|
4763
|
+
);
|
|
4764
|
+
setOverrideRev((n) => n + 1);
|
|
4765
|
+
const fresh = {
|
|
4766
|
+
...truth,
|
|
4767
|
+
sections: applyOverrides(truth.sections, overrides.current)
|
|
4768
|
+
};
|
|
4639
4769
|
const freshKey = signatureOf(fresh.sections);
|
|
4770
|
+
if (apply && displayedKey.current) {
|
|
4771
|
+
showData(fresh.sections, fresh.login);
|
|
4772
|
+
return;
|
|
4773
|
+
}
|
|
4640
4774
|
if (!displayedKey.current) {
|
|
4641
4775
|
if (fresh.sections.length === 0) {
|
|
4642
4776
|
setState({ phase: "empty" });
|
|
@@ -4656,6 +4790,94 @@ var App = ({
|
|
|
4656
4790
|
if (pending) showData(pending.sections, pending.login);
|
|
4657
4791
|
else revalidate(true);
|
|
4658
4792
|
};
|
|
4793
|
+
const overrides = useRef(/* @__PURE__ */ new Map());
|
|
4794
|
+
const [overrideRev, setOverrideRev] = useState(0);
|
|
4795
|
+
const failureSeq = useRef(0);
|
|
4796
|
+
const pendingKeys = useMemo(
|
|
4797
|
+
() => new Set(
|
|
4798
|
+
[...overrides.current].filter(([, o]) => o.phase === "inflight").map(([key]) => key)
|
|
4799
|
+
),
|
|
4800
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4801
|
+
[overrideRev]
|
|
4802
|
+
);
|
|
4803
|
+
const failure = useMemo(() => {
|
|
4804
|
+
const failed = failedOverrides(overrides.current);
|
|
4805
|
+
const notice = failureNotice(failed);
|
|
4806
|
+
const seq = failed[0]?.[1].failure?.seq;
|
|
4807
|
+
return notice && seq != null ? { notice, seq } : void 0;
|
|
4808
|
+
}, [overrideRev]);
|
|
4809
|
+
const hold = (key, override) => {
|
|
4810
|
+
overrides.current = new Map(overrides.current).set(key, override);
|
|
4811
|
+
setOverrideRev((n) => n + 1);
|
|
4812
|
+
const one = /* @__PURE__ */ new Map([[key, override]]);
|
|
4813
|
+
if (state.phase === "browse")
|
|
4814
|
+
showData(
|
|
4815
|
+
applyOverrides(displayedSections.current, one),
|
|
4816
|
+
state.login,
|
|
4817
|
+
false
|
|
4818
|
+
);
|
|
4819
|
+
setPending(
|
|
4820
|
+
(p) => p ? { ...p, sections: applyOverrides(p.sections, one) } : p
|
|
4821
|
+
);
|
|
4822
|
+
const answer = (next) => {
|
|
4823
|
+
if (overrides.current.get(key) !== override) return;
|
|
4824
|
+
overrides.current = new Map(overrides.current).set(key, {
|
|
4825
|
+
...override,
|
|
4826
|
+
...next
|
|
4827
|
+
});
|
|
4828
|
+
setOverrideRev((n) => n + 1);
|
|
4829
|
+
};
|
|
4830
|
+
return {
|
|
4831
|
+
// Answered yes: only now does the hold clock matter — see OVERRIDE_HOLD_MS.
|
|
4832
|
+
settled: () => answer({ phase: "sent", since: Date.now() }),
|
|
4833
|
+
failed: (err) => answer({
|
|
4834
|
+
phase: "failed",
|
|
4835
|
+
failure: { reason: explainGhAction(err), seq: ++failureSeq.current }
|
|
4836
|
+
})
|
|
4837
|
+
};
|
|
4838
|
+
};
|
|
4839
|
+
const optimisticMove = tabForStatus ? (task, to) => {
|
|
4840
|
+
const key = keyOf(task);
|
|
4841
|
+
const toSection = tabForStatus(to);
|
|
4842
|
+
if (!key || !toSection) return void 0;
|
|
4843
|
+
return hold(key, {
|
|
4844
|
+
patch: { kind: "move", toSection, fields: { status: to.name } },
|
|
4845
|
+
action: "move",
|
|
4846
|
+
label: task.ticket ?? task.key,
|
|
4847
|
+
since: Date.now(),
|
|
4848
|
+
phase: "inflight"
|
|
4849
|
+
});
|
|
4850
|
+
} : void 0;
|
|
4851
|
+
const optimistic = (item, change) => {
|
|
4852
|
+
const key = keyOf(item);
|
|
4853
|
+
if (!key) return void 0;
|
|
4854
|
+
return hold(key, {
|
|
4855
|
+
patch: change.patch,
|
|
4856
|
+
action: change.action,
|
|
4857
|
+
label: rowLabelOf(item),
|
|
4858
|
+
since: Date.now(),
|
|
4859
|
+
phase: "inflight"
|
|
4860
|
+
});
|
|
4861
|
+
};
|
|
4862
|
+
const departing = useMemo(
|
|
4863
|
+
() => new Map(
|
|
4864
|
+
[...overrides.current].flatMap(
|
|
4865
|
+
([key, { patch }]) => patch.kind === "remove" ? [[key, null]] : patch.kind === "move" ? [[key, patch.toSection]] : []
|
|
4866
|
+
)
|
|
4867
|
+
),
|
|
4868
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4869
|
+
[overrideRev]
|
|
4870
|
+
);
|
|
4871
|
+
const restore = () => {
|
|
4872
|
+
const newest = failedOverrides(overrides.current)[0];
|
|
4873
|
+
if (!newest) return;
|
|
4874
|
+
const next = new Map(overrides.current);
|
|
4875
|
+
next.delete(newest[0]);
|
|
4876
|
+
overrides.current = next;
|
|
4877
|
+
setOverrideRev((n) => n + 1);
|
|
4878
|
+
if (cacheKey) invalidateCache(cacheKey);
|
|
4879
|
+
revalidate(true, true);
|
|
4880
|
+
};
|
|
4659
4881
|
const onActed = () => {
|
|
4660
4882
|
if (cacheKey) invalidateCache(cacheKey);
|
|
4661
4883
|
setTimeout(() => applyOrRefresh(), 1500);
|
|
@@ -4880,6 +5102,12 @@ var App = ({
|
|
|
4880
5102
|
jiraSetupHint,
|
|
4881
5103
|
jiraTransitions,
|
|
4882
5104
|
jiraTransitionsFor,
|
|
5105
|
+
optimisticMove,
|
|
5106
|
+
optimistic,
|
|
5107
|
+
departing,
|
|
5108
|
+
pendingKeys,
|
|
5109
|
+
failure,
|
|
5110
|
+
onRestore: restore,
|
|
4883
5111
|
onRefresh: applyOrRefresh,
|
|
4884
5112
|
onActed,
|
|
4885
5113
|
onSettled,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.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,7 +44,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@kud/ink-ui": ">=0.
|
|
47
|
+
"@kud/ink-ui": ">=0.38.0 <1",
|
|
48
48
|
"ink": ">=7",
|
|
49
49
|
"react": ">=19"
|
|
50
50
|
},
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"zx": "8.8.5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@kud/ink-ui": "0.
|
|
57
|
+
"@kud/ink-ui": "0.38.0",
|
|
58
58
|
"@types/node": "22.20.1",
|
|
59
59
|
"@types/react": "19.2.17",
|
|
60
60
|
"ink": "7.1.0",
|