@kud/gh-ink 0.72.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 +29 -2
- package/dist/index.js +240 -47
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -604,10 +604,31 @@ type JiraTransition = {
|
|
|
604
604
|
type JiraAvailableTransition = {
|
|
605
605
|
id: string;
|
|
606
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
|
+
*/
|
|
607
612
|
to: {
|
|
608
613
|
name: string;
|
|
614
|
+
category?: string;
|
|
609
615
|
};
|
|
610
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;
|
|
611
632
|
/**
|
|
612
633
|
* Resolve the live transitions for a ticket, called lazily when its move
|
|
613
634
|
* submenu opens — never at board fetch, where one call per visible ticket
|
|
@@ -810,7 +831,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
|
810
831
|
cols?: number;
|
|
811
832
|
}) => React.JSX.Element;
|
|
812
833
|
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, }: {
|
|
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, }: {
|
|
814
835
|
fetcher: () => Promise<{
|
|
815
836
|
sections: Section[];
|
|
816
837
|
login: string;
|
|
@@ -851,6 +872,12 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
851
872
|
jiraSetupHint?: string;
|
|
852
873
|
jiraTransitions?: JiraTransition[];
|
|
853
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;
|
|
854
881
|
hasCiStatus?: boolean;
|
|
855
882
|
ciJob?: string;
|
|
856
883
|
ciFetcher?: () => Promise<CiStatus | null>;
|
|
@@ -919,4 +946,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
919
946
|
*/
|
|
920
947
|
declare const parsePatterns: (value: string) => string[];
|
|
921
948
|
|
|
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 };
|
|
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
|
@@ -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) => {
|
|
@@ -1468,11 +1468,11 @@ var indexRows = (sections) => {
|
|
|
1468
1468
|
};
|
|
1469
1469
|
var rowsOf = (index) => {
|
|
1470
1470
|
const out = /* @__PURE__ */ new Map();
|
|
1471
|
-
for (const [mark,
|
|
1471
|
+
for (const [mark, placed2] of index) {
|
|
1472
1472
|
const key = rowKeyOfMark(mark);
|
|
1473
1473
|
const seen = out.get(key);
|
|
1474
|
-
if (seen) seen.add(
|
|
1475
|
-
else out.set(key, /* @__PURE__ */ new Set([
|
|
1474
|
+
if (seen) seen.add(placed2.sectionId);
|
|
1475
|
+
else out.set(key, /* @__PURE__ */ new Set([placed2.sectionId]));
|
|
1476
1476
|
}
|
|
1477
1477
|
return out;
|
|
1478
1478
|
};
|
|
@@ -1627,6 +1627,73 @@ var reconcile = (prev, fresh, now, holdMs = HOLD_MS) => {
|
|
|
1627
1627
|
}
|
|
1628
1628
|
return out;
|
|
1629
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
|
+
};
|
|
1630
1697
|
var quietly = $({ quiet: true });
|
|
1631
1698
|
var isMenuGap = (row) => "gap" in row;
|
|
1632
1699
|
var jiraMovesCache = /* @__PURE__ */ new Map();
|
|
@@ -1646,43 +1713,56 @@ var orderJiraMoves = (available, configured) => {
|
|
|
1646
1713
|
if (!live) continue;
|
|
1647
1714
|
known.add(live.name);
|
|
1648
1715
|
ordered.push(
|
|
1649
|
-
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 }
|
|
1650
1717
|
);
|
|
1651
1718
|
}
|
|
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 }));
|
|
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 }));
|
|
1653
1720
|
return [...ordered, ...rest];
|
|
1654
1721
|
};
|
|
1655
|
-
var jiraMoveActions = (ticket, moves, showFlash, onActed) =>
|
|
1656
|
-
(
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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,
|
|
1663
1735
|
hint: "",
|
|
1664
1736
|
run: () => {
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
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
|
+
);
|
|
1671
1762
|
}
|
|
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
1763
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1764
|
+
);
|
|
1765
|
+
};
|
|
1686
1766
|
var jiraLoadingActions = () => [
|
|
1687
1767
|
{ label: "Loading transitions\u2026", hint: "", tone: "dim", run: () => {
|
|
1688
1768
|
} }
|
|
@@ -1696,12 +1776,13 @@ var openJiraMoves = (deps) => {
|
|
|
1696
1776
|
open,
|
|
1697
1777
|
isOpen,
|
|
1698
1778
|
showFlash,
|
|
1699
|
-
onActed
|
|
1779
|
+
onActed,
|
|
1780
|
+
optimistic
|
|
1700
1781
|
} = deps;
|
|
1701
1782
|
const openOrdered = (moves) => {
|
|
1702
1783
|
const ordered = orderJiraMoves(moves, configured);
|
|
1703
1784
|
open(
|
|
1704
|
-
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed) : [
|
|
1785
|
+
ordered.length > 0 ? jiraMoveActions(ticket, ordered, showFlash, onActed, optimistic) : [
|
|
1705
1786
|
{
|
|
1706
1787
|
label: "No transitions available",
|
|
1707
1788
|
hint: "",
|
|
@@ -2194,7 +2275,7 @@ var ROW_MAX_COLS = 140;
|
|
|
2194
2275
|
var OVERLAY_BG = "#22222e";
|
|
2195
2276
|
var SELECTION_BG = "#16161f";
|
|
2196
2277
|
var selectionBackground = (active, backdropped) => active && !backdropped ? SELECTION_BG : void 0;
|
|
2197
|
-
var TICKET_KEY_COLOUR =
|
|
2278
|
+
var TICKET_KEY_COLOUR = colors.ticket;
|
|
2198
2279
|
var taskKeyStyle = () => ({
|
|
2199
2280
|
color: TICKET_KEY_COLOUR,
|
|
2200
2281
|
bold: true
|
|
@@ -2778,6 +2859,8 @@ var PULSE_IDLE_MS = 6e4;
|
|
|
2778
2859
|
var PULSE_SETTLED_FRAME = 3;
|
|
2779
2860
|
var NO_TRANSIENTS = /* @__PURE__ */ new Map();
|
|
2780
2861
|
var NO_HOLDS = /* @__PURE__ */ new Map();
|
|
2862
|
+
var PENDING_GLYPH = "\u25CC";
|
|
2863
|
+
var FAILURE_NOTICE_MS = 8e3;
|
|
2781
2864
|
var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
|
|
2782
2865
|
var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
2783
2866
|
var TRANSIT_COLOUR = {
|
|
@@ -2806,6 +2889,7 @@ var ItemRow = ({
|
|
|
2806
2889
|
merged,
|
|
2807
2890
|
transient: refreshMark,
|
|
2808
2891
|
leaving,
|
|
2892
|
+
pending,
|
|
2809
2893
|
transitAt,
|
|
2810
2894
|
prefix = "",
|
|
2811
2895
|
parent,
|
|
@@ -2856,7 +2940,7 @@ var ItemRow = ({
|
|
|
2856
2940
|
] });
|
|
2857
2941
|
if (item.kind === "task") {
|
|
2858
2942
|
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";
|
|
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";
|
|
2860
2944
|
const titleMax = Math.max(
|
|
2861
2945
|
1,
|
|
2862
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
|
|
@@ -2865,7 +2949,7 @@ var ItemRow = ({
|
|
|
2865
2949
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
|
|
2866
2950
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
2867
2951
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
2868
|
-
/* @__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 + " " }),
|
|
2869
2953
|
markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
|
|
2870
2954
|
/* @__PURE__ */ jsx(Text3, { ...taskKeyStyle(), children: item.key + " " }),
|
|
2871
2955
|
/* @__PURE__ */ jsx(
|
|
@@ -3159,6 +3243,7 @@ var HelpModal = ({
|
|
|
3159
3243
|
["/", "search"],
|
|
3160
3244
|
["f", "filter by repo"],
|
|
3161
3245
|
["r", "refresh"],
|
|
3246
|
+
["w", "restore a change that failed"],
|
|
3162
3247
|
...extensionLegend(extensions),
|
|
3163
3248
|
["?", "this help"],
|
|
3164
3249
|
["q", "quit"]
|
|
@@ -3366,7 +3451,11 @@ var BrowseScreen = ({
|
|
|
3366
3451
|
transients,
|
|
3367
3452
|
onTabChange,
|
|
3368
3453
|
sidebar,
|
|
3369
|
-
liveLabel
|
|
3454
|
+
liveLabel,
|
|
3455
|
+
optimisticMove,
|
|
3456
|
+
pendingKeys,
|
|
3457
|
+
failure,
|
|
3458
|
+
onRestore
|
|
3370
3459
|
}) => {
|
|
3371
3460
|
const { rows } = useWindowSize();
|
|
3372
3461
|
const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match, login) : secs;
|
|
@@ -3481,6 +3570,14 @@ var BrowseScreen = ({
|
|
|
3481
3570
|
if (flashTimer.current) clearTimeout(flashTimer.current);
|
|
3482
3571
|
flashTimer.current = setTimeout(() => setFlash(null), 1500);
|
|
3483
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]);
|
|
3484
3581
|
const markedTabs = useMemo(() => {
|
|
3485
3582
|
const out = /* @__PURE__ */ new Set();
|
|
3486
3583
|
if (!transients?.size) return out;
|
|
@@ -3506,6 +3603,7 @@ var BrowseScreen = ({
|
|
|
3506
3603
|
if (repoPicker) return setRepoPicker(false), true;
|
|
3507
3604
|
if (railActive) return setRailFocus(false), true;
|
|
3508
3605
|
if (menu.actions !== null) return menu.close(), true;
|
|
3606
|
+
if (noticeUp && failure) return setDismissedSeq(failure.seq), true;
|
|
3509
3607
|
if (search != null) return filter.clear(), true;
|
|
3510
3608
|
if (repoFilter.size > 0) return setRepoFilter(/* @__PURE__ */ new Set()), true;
|
|
3511
3609
|
return false;
|
|
@@ -3611,7 +3709,8 @@ var BrowseScreen = ({
|
|
|
3611
3709
|
open: (rows2) => menu.open(rows2),
|
|
3612
3710
|
isOpen: () => menuActionsRef.current !== null,
|
|
3613
3711
|
showFlash,
|
|
3614
|
-
onActed
|
|
3712
|
+
onActed,
|
|
3713
|
+
optimistic: optimisticMove ? (to) => optimisticMove(task, to) : void 0
|
|
3615
3714
|
});
|
|
3616
3715
|
};
|
|
3617
3716
|
const openMenu = () => {
|
|
@@ -3687,6 +3786,10 @@ var BrowseScreen = ({
|
|
|
3687
3786
|
return;
|
|
3688
3787
|
}
|
|
3689
3788
|
if (searchInput && !key.upArrow && !key.downArrow) return;
|
|
3789
|
+
if (input === "w" && failure && onRestore) {
|
|
3790
|
+
onRestore();
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3690
3793
|
if (input === "f" && allRepos.length > 0) {
|
|
3691
3794
|
setRepoCursor(0);
|
|
3692
3795
|
setRepoPicker(true);
|
|
@@ -4334,6 +4437,7 @@ var BrowseScreen = ({
|
|
|
4334
4437
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
4335
4438
|
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
4336
4439
|
transient: transientOf(transients, item, section.id),
|
|
4440
|
+
pending: !!pendingKeys?.has(keyOf(item) ?? ""),
|
|
4337
4441
|
transitAt: "url" in item ? transitSince?.get(item.url) : void 0,
|
|
4338
4442
|
prefix: treePrefix(section.items, viewStart + i),
|
|
4339
4443
|
parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
|
|
@@ -4382,7 +4486,7 @@ var BrowseScreen = ({
|
|
|
4382
4486
|
}
|
|
4383
4487
|
) : null
|
|
4384
4488
|
] }),
|
|
4385
|
-
/* @__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 ? (
|
|
4386
4490
|
// The overlay's own verbs, plus the way back. `page="nested"` is what
|
|
4387
4491
|
// appends `⌫ back` and `q quit`, so a body passes only its own keys
|
|
4388
4492
|
// through `useChrome` — the same contract ink-ui's `Page` offers its
|
|
@@ -4431,6 +4535,7 @@ var App = ({
|
|
|
4431
4535
|
jiraSetupHint,
|
|
4432
4536
|
jiraTransitions,
|
|
4433
4537
|
jiraTransitionsFor,
|
|
4538
|
+
tabForStatus,
|
|
4434
4539
|
hasCiStatus,
|
|
4435
4540
|
ciJob,
|
|
4436
4541
|
ciFetcher,
|
|
@@ -4506,12 +4611,14 @@ var App = ({
|
|
|
4506
4611
|
const transitTabs = useRef(/* @__PURE__ */ new Map());
|
|
4507
4612
|
const [heldSince, setHeldSince] = useState(NO_HOLDS);
|
|
4508
4613
|
const [visibleTab, setVisibleTab] = useState("");
|
|
4509
|
-
const showData = (sections, login) => {
|
|
4614
|
+
const showData = (sections, login, fetched = true) => {
|
|
4510
4615
|
const before = displayedSections.current;
|
|
4511
4616
|
displayedSections.current = sections;
|
|
4512
4617
|
displayedKey.current = signatureOf(sections);
|
|
4513
|
-
|
|
4514
|
-
|
|
4618
|
+
if (fetched) {
|
|
4619
|
+
setPending(null);
|
|
4620
|
+
setFetchedAt(Date.now());
|
|
4621
|
+
}
|
|
4515
4622
|
if (before.length === 0) {
|
|
4516
4623
|
transitTabs.current = /* @__PURE__ */ new Map();
|
|
4517
4624
|
setTransients(NO_TRANSIENTS);
|
|
@@ -4600,7 +4707,7 @@ var App = ({
|
|
|
4600
4707
|
if (new Date(known.resetAt).getTime() <= Date.now()) return true;
|
|
4601
4708
|
return known.remaining >= Math.max(known.cost, 1) * 2;
|
|
4602
4709
|
};
|
|
4603
|
-
const revalidate = (manual = false) => {
|
|
4710
|
+
const revalidate = (manual = false, apply = false) => {
|
|
4604
4711
|
const gated = !manual && !!displayedKey.current;
|
|
4605
4712
|
if (gated && !canAffordAuto()) {
|
|
4606
4713
|
setSkippedForBudget(true);
|
|
@@ -4624,7 +4731,7 @@ var App = ({
|
|
|
4624
4731
|
setFetchedAt(Date.now());
|
|
4625
4732
|
setRefreshError(null);
|
|
4626
4733
|
if (hasCiStatus) applyCiStatus(fresh.ciStatus ?? null);
|
|
4627
|
-
receive(fresh);
|
|
4734
|
+
receive(fresh, apply);
|
|
4628
4735
|
}).catch((err) => {
|
|
4629
4736
|
setRefreshing(false);
|
|
4630
4737
|
const message = explainGhAction(err);
|
|
@@ -4635,8 +4742,22 @@ var App = ({
|
|
|
4635
4742
|
setRefreshError({ message, at: Date.now() });
|
|
4636
4743
|
});
|
|
4637
4744
|
};
|
|
4638
|
-
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
|
+
};
|
|
4639
4756
|
const freshKey = signatureOf(fresh.sections);
|
|
4757
|
+
if (apply && displayedKey.current) {
|
|
4758
|
+
showData(fresh.sections, fresh.login);
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4640
4761
|
if (!displayedKey.current) {
|
|
4641
4762
|
if (fresh.sections.length === 0) {
|
|
4642
4763
|
setState({ phase: "empty" });
|
|
@@ -4656,6 +4777,74 @@ var App = ({
|
|
|
4656
4777
|
if (pending) showData(pending.sections, pending.login);
|
|
4657
4778
|
else revalidate(true);
|
|
4658
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
|
+
};
|
|
4659
4848
|
const onActed = () => {
|
|
4660
4849
|
if (cacheKey) invalidateCache(cacheKey);
|
|
4661
4850
|
setTimeout(() => applyOrRefresh(), 1500);
|
|
@@ -4880,6 +5069,10 @@ var App = ({
|
|
|
4880
5069
|
jiraSetupHint,
|
|
4881
5070
|
jiraTransitions,
|
|
4882
5071
|
jiraTransitionsFor,
|
|
5072
|
+
optimisticMove,
|
|
5073
|
+
pendingKeys,
|
|
5074
|
+
failure,
|
|
5075
|
+
onRestore: restore,
|
|
4883
5076
|
onRefresh: applyOrRefresh,
|
|
4884
5077
|
onActed,
|
|
4885
5078
|
onSettled,
|
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,7 +44,7 @@
|
|
|
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
|
},
|
|
@@ -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.36.0",
|
|
58
58
|
"@types/node": "22.20.1",
|
|
59
59
|
"@types/react": "19.2.17",
|
|
60
60
|
"ink": "7.1.0",
|