@kud/gh-ink 0.34.0 → 0.36.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 +45 -0
- package/dist/index.js +82 -29
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -140,6 +140,31 @@ type GHItem = {
|
|
|
140
140
|
* handing back a `lastActor` that no longer claims a turn.
|
|
141
141
|
*/
|
|
142
142
|
pinned?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* What this row STANDS FOR, when that differs from what it is. Absent means
|
|
145
|
+
* the row is a unit of work and is counted as one.
|
|
146
|
+
*
|
|
147
|
+
* `"container"` marks a row that exists to carry context rather than to be
|
|
148
|
+
* done: the initiative a story hangs under, drawn as a real row because it is
|
|
149
|
+
* still selectable, openable, and worth seeing until it closes — but not
|
|
150
|
+
* itself a thing on anyone's plate. Four stories under one container is four
|
|
151
|
+
* items, not five.
|
|
152
|
+
*
|
|
153
|
+
* Deliberately not `uncounted`. A host knows what a row IS; it should not have
|
|
154
|
+
* to know what the tab badge does with that, and a field named after one
|
|
155
|
+
* consumer starts lying the moment a second one reads it.
|
|
156
|
+
*
|
|
157
|
+
* Orthogonal to `depth`, which says where the row hangs rather than what it
|
|
158
|
+
* stands for. A container is a genuine depth-0 row with genuine depth-1
|
|
159
|
+
* children; spelling it as a depth would mean lying about the tree to fix a
|
|
160
|
+
* number, and every site that draws indentation reads that lie as truth.
|
|
161
|
+
*
|
|
162
|
+
* A union with one member rather than a boolean. `indent` was a boolean that
|
|
163
|
+
* turned out to need a scalar, and widening it cost a deprecation that is
|
|
164
|
+
* still in this file. Adding a second role here is additive; turning a boolean
|
|
165
|
+
* into a union is not.
|
|
166
|
+
*/
|
|
167
|
+
role?: "container";
|
|
143
168
|
/**
|
|
144
169
|
* How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
|
|
145
170
|
* grandchild. Absent means 0.
|
|
@@ -165,6 +190,8 @@ type TaskRow = {
|
|
|
165
190
|
url: string;
|
|
166
191
|
status: string;
|
|
167
192
|
age: string;
|
|
193
|
+
/** What this row stands for. See `GHItem.role`. */
|
|
194
|
+
role?: "container";
|
|
168
195
|
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
169
196
|
depth?: number;
|
|
170
197
|
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
@@ -375,6 +402,24 @@ declare const openInTab: (cmd: string) => Promise<void>;
|
|
|
375
402
|
declare const runInPaneHorizontal: (cmd: string) => Promise<void>;
|
|
376
403
|
declare const runHere: (cmd: string) => void;
|
|
377
404
|
declare const COLS: number;
|
|
405
|
+
/**
|
|
406
|
+
* What the tab badge and the header total both count: rows that are somebody's
|
|
407
|
+
* work, at the top level.
|
|
408
|
+
*
|
|
409
|
+
* The exclusions are two different kinds of thing. Headers and the
|
|
410
|
+
* show-more/show-less affordances are furniture — not entities at all, which is
|
|
411
|
+
* why the search and repo filters drop them too. This function used to exclude
|
|
412
|
+
* only the two header kinds while its three siblings excluded all four, so a
|
|
413
|
+
* collapsed tail sitting at the top level counted as an item.
|
|
414
|
+
* `role: "container"` rows ARE entities, selectable and openable; they simply
|
|
415
|
+
* are not work. An epic is the case that forced the distinction: real, owned,
|
|
416
|
+
* and on screen until it closes, but the work is the stories under it, and
|
|
417
|
+
* counting it as well reported five things to do where there were four.
|
|
418
|
+
*
|
|
419
|
+
* One function, both consumers, on purpose. The badge and the whole-board total
|
|
420
|
+
* are the same claim at two scales; computed apart they drift, and a header that
|
|
421
|
+
* disagrees with the sum of its own tabs is worse than either number alone.
|
|
422
|
+
*/
|
|
378
423
|
declare const topLevelCount: (s: Section) => number;
|
|
379
424
|
declare const drillCmd: (item: AnyItem) => string | null;
|
|
380
425
|
declare const explainGhAction: (e: unknown) => string;
|
package/dist/index.js
CHANGED
|
@@ -676,6 +676,9 @@ var invalidateCache = (key) => {
|
|
|
676
676
|
};
|
|
677
677
|
|
|
678
678
|
// src/inbox/diff.ts
|
|
679
|
+
var MARK_SEP = "\0";
|
|
680
|
+
var markKey = (sectionId, rowKey) => `${sectionId}${MARK_SEP}${rowKey}`;
|
|
681
|
+
var rowKeyOfMark = (mark) => mark.slice(mark.indexOf(MARK_SEP) + 1);
|
|
679
682
|
var keyOf = (item) => {
|
|
680
683
|
if (item.kind === "pr" || item.kind === "issue") return item.url;
|
|
681
684
|
if (item.kind === "task") {
|
|
@@ -707,11 +710,27 @@ var indexRows = (sections) => {
|
|
|
707
710
|
for (const section of sections) {
|
|
708
711
|
section.items.forEach((item, index) => {
|
|
709
712
|
const key = keyOf(item);
|
|
710
|
-
if (key)
|
|
713
|
+
if (key)
|
|
714
|
+
out.set(markKey(section.id, key), {
|
|
715
|
+
item,
|
|
716
|
+
sectionId: section.id,
|
|
717
|
+
index
|
|
718
|
+
});
|
|
711
719
|
});
|
|
712
720
|
}
|
|
713
721
|
return out;
|
|
714
722
|
};
|
|
723
|
+
var rowsOf = (index) => {
|
|
724
|
+
const out = /* @__PURE__ */ new Map();
|
|
725
|
+
for (const [mark, placed] of index) {
|
|
726
|
+
const key = rowKeyOfMark(mark);
|
|
727
|
+
const seen = out.get(key);
|
|
728
|
+
if (seen) seen.add(placed.sectionId);
|
|
729
|
+
else out.set(key, /* @__PURE__ */ new Set([placed.sectionId]));
|
|
730
|
+
}
|
|
731
|
+
return out;
|
|
732
|
+
};
|
|
733
|
+
var sameSections = (a, b) => a.size === b.size && [...a].every((id) => b.has(id));
|
|
715
734
|
var spliceBack = (next, leaving) => {
|
|
716
735
|
if (leaving.length === 0) return next;
|
|
717
736
|
const bySection = /* @__PURE__ */ new Map();
|
|
@@ -732,30 +751,43 @@ var spliceBack = (next, leaving) => {
|
|
|
732
751
|
var diffSections = (prev, next) => {
|
|
733
752
|
const before = indexRows(prev);
|
|
734
753
|
const after = indexRows(next);
|
|
754
|
+
const boardBefore = rowsOf(before);
|
|
755
|
+
const boardAfter = rowsOf(after);
|
|
735
756
|
const transients = /* @__PURE__ */ new Map();
|
|
736
757
|
const leaving = [];
|
|
737
|
-
for (const [
|
|
738
|
-
|
|
739
|
-
|
|
758
|
+
for (const [mark] of after)
|
|
759
|
+
if (!before.has(mark))
|
|
760
|
+
transients.set(
|
|
761
|
+
mark,
|
|
762
|
+
boardBefore.has(rowKeyOfMark(mark)) ? "moved-in" : "in"
|
|
763
|
+
);
|
|
764
|
+
for (const [mark, was] of before) {
|
|
765
|
+
const now = after.get(mark);
|
|
740
766
|
if (!now) {
|
|
741
|
-
transients.set(
|
|
767
|
+
transients.set(
|
|
768
|
+
mark,
|
|
769
|
+
boardAfter.has(rowKeyOfMark(mark)) ? "moved-out" : "out"
|
|
770
|
+
);
|
|
742
771
|
leaving.push(was);
|
|
743
772
|
} else if (renderedState(was.item) !== renderedState(now.item)) {
|
|
744
|
-
transients.set(
|
|
773
|
+
transients.set(mark, "changed");
|
|
745
774
|
}
|
|
746
775
|
}
|
|
747
776
|
let added = 0;
|
|
748
777
|
let removed = 0;
|
|
749
|
-
|
|
750
|
-
for (const [, kind] of transients)
|
|
751
|
-
if (kind === "
|
|
752
|
-
|
|
753
|
-
|
|
778
|
+
const changedRows = /* @__PURE__ */ new Set();
|
|
779
|
+
for (const [mark, kind] of transients)
|
|
780
|
+
if (kind === "changed") changedRows.add(rowKeyOfMark(mark));
|
|
781
|
+
for (const [key, sections] of boardAfter) {
|
|
782
|
+
const had = boardBefore.get(key);
|
|
783
|
+
if (!had) added += 1;
|
|
784
|
+
else if (!sameSections(had, sections)) changedRows.add(key);
|
|
754
785
|
}
|
|
786
|
+
for (const key of boardBefore.keys()) if (!boardAfter.has(key)) removed += 1;
|
|
755
787
|
return {
|
|
756
788
|
transients,
|
|
757
789
|
union: spliceBack(next, leaving),
|
|
758
|
-
counts: { added, removed, changed }
|
|
790
|
+
counts: { added, removed, changed: changedRows.size }
|
|
759
791
|
};
|
|
760
792
|
};
|
|
761
793
|
var summariseDiff = (counts) => {
|
|
@@ -765,17 +797,19 @@ var summariseDiff = (counts) => {
|
|
|
765
797
|
if (counts.changed) parts.push(`${counts.changed} moved`);
|
|
766
798
|
return parts.join(" \xB7 ");
|
|
767
799
|
};
|
|
768
|
-
var transientOf = (transients, item) => {
|
|
800
|
+
var transientOf = (transients, item, sectionId) => {
|
|
769
801
|
if (!transients?.size) return void 0;
|
|
770
802
|
const key = keyOf(item);
|
|
771
|
-
return key ? transients.get(key) : void 0;
|
|
803
|
+
return key ? transients.get(markKey(sectionId, key)) : void 0;
|
|
772
804
|
};
|
|
773
805
|
var tabsOfMarks = (sections, marks) => {
|
|
774
806
|
const out = /* @__PURE__ */ new Map();
|
|
775
807
|
for (const section of sections)
|
|
776
808
|
for (const item of section.items) {
|
|
777
809
|
const key = keyOf(item);
|
|
778
|
-
if (key
|
|
810
|
+
if (!key) continue;
|
|
811
|
+
const mark = markKey(section.id, key);
|
|
812
|
+
if (marks.has(mark)) out.set(mark, section.id);
|
|
779
813
|
}
|
|
780
814
|
return out;
|
|
781
815
|
};
|
|
@@ -1391,7 +1425,7 @@ var Backdrop = ({
|
|
|
1391
1425
|
}
|
|
1392
1426
|
) : children });
|
|
1393
1427
|
var topLevelCount = (s) => s.items.filter(
|
|
1394
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && depthOf(i) === 0
|
|
1428
|
+
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && !("role" in i && i.role === "container") && depthOf(i) === 0
|
|
1395
1429
|
).length;
|
|
1396
1430
|
var drillCmd = (item) => {
|
|
1397
1431
|
if (item.kind === "task")
|
|
@@ -1835,13 +1869,26 @@ var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
|
1835
1869
|
var TRANSIT_COLOUR = {
|
|
1836
1870
|
in: "#3FB950",
|
|
1837
1871
|
out: "#8B949E",
|
|
1838
|
-
changed: "#FF8700"
|
|
1872
|
+
changed: "#FF8700",
|
|
1873
|
+
// A move is neither an arrival nor a departure, so it takes the colour of the
|
|
1874
|
+
// third thing that can happen to a row you already had: it changed. Both ends
|
|
1875
|
+
// of one move wear the same colour and the same word, which is what lets you
|
|
1876
|
+
// recognise the row you just watched leave when you land on the tab it went to.
|
|
1877
|
+
"moved-in": "#FF8700",
|
|
1878
|
+
"moved-out": "#FF8700"
|
|
1839
1879
|
};
|
|
1840
1880
|
var TRANSIT_LABEL = {
|
|
1841
1881
|
in: "NEW",
|
|
1842
1882
|
out: "GONE",
|
|
1843
|
-
changed: "UPDATED"
|
|
1844
|
-
|
|
1883
|
+
changed: "UPDATED",
|
|
1884
|
+
// Never GONE. The row is still on the board, one tab over, and telling you it
|
|
1885
|
+
// has gone is the marker lying about work you still own — which was the whole
|
|
1886
|
+
// failure: an epic moving tab said nothing at all and simply vanished.
|
|
1887
|
+
"moved-in": "MOVED",
|
|
1888
|
+
"moved-out": "MOVED"
|
|
1889
|
+
};
|
|
1890
|
+
var isDeparture = (t) => t === "out" || t === "moved-out";
|
|
1891
|
+
var isArrival = (t) => t === "in" || t === "moved-in";
|
|
1845
1892
|
var LEAVING_HOLD_MS = 2500;
|
|
1846
1893
|
var TAB_MARK = "\u25CF";
|
|
1847
1894
|
var tabLabel = (label, marked, id) => marked.size === 0 ? label : `${marked.has(id) ? TAB_MARK : " "} ${label}`;
|
|
@@ -1881,7 +1928,7 @@ var ItemRow = ({
|
|
|
1881
1928
|
] });
|
|
1882
1929
|
if (item.kind === "task") {
|
|
1883
1930
|
const note = item.note ?? "";
|
|
1884
|
-
const transitIcon = !transient ? " " : transient
|
|
1931
|
+
const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[sparkFrame % TRANSIT_OUT_FRAMES.length] : isArrival(transient) ? TRANSIT_IN_FRAMES[sparkFrame % TRANSIT_IN_FRAMES.length] : "\u25C9";
|
|
1885
1932
|
const transitLabel2 = transient ? TRANSIT_LABEL[transient] : "";
|
|
1886
1933
|
const titleMax2 = Math.max(
|
|
1887
1934
|
20,
|
|
@@ -1896,8 +1943,8 @@ var ItemRow = ({
|
|
|
1896
1943
|
/* @__PURE__ */ jsx(
|
|
1897
1944
|
Text2,
|
|
1898
1945
|
{
|
|
1899
|
-
bold: active || transient
|
|
1900
|
-
dimColor: transient
|
|
1946
|
+
bold: active || !!transient && isArrival(transient),
|
|
1947
|
+
dimColor: !!transient && isDeparture(transient),
|
|
1901
1948
|
strikethrough: transient === "out",
|
|
1902
1949
|
children: truncate(item.summary, titleMax2)
|
|
1903
1950
|
}
|
|
@@ -1907,7 +1954,7 @@ var ItemRow = ({
|
|
|
1907
1954
|
] });
|
|
1908
1955
|
}
|
|
1909
1956
|
const { glyph: healthIcon, color: healthColor2 } = healthDisplay[item.health];
|
|
1910
|
-
const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient
|
|
1957
|
+
const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient && isDeparture(transient) ? TRANSIT_OUT_FRAMES[sparkFrame % TRANSIT_OUT_FRAMES.length] : transient && isArrival(transient) ? TRANSIT_IN_FRAMES[sparkFrame % TRANSIT_IN_FRAMES.length] : healthIcon;
|
|
1911
1958
|
const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
|
|
1912
1959
|
const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
|
|
1913
1960
|
const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, "#FF8700"] : !login || !item.lastActor ? [" ", "white"] : spokeLast ? ["\u2192", "#888888"] : ["\u2190", "#FF8700"];
|
|
@@ -1936,8 +1983,8 @@ var ItemRow = ({
|
|
|
1936
1983
|
/* @__PURE__ */ jsx(
|
|
1937
1984
|
Text2,
|
|
1938
1985
|
{
|
|
1939
|
-
bold: active || transient
|
|
1940
|
-
dimColor: transient
|
|
1986
|
+
bold: active || !!transient && isArrival(transient),
|
|
1987
|
+
dimColor: !!transient && isDeparture(transient),
|
|
1941
1988
|
strikethrough: transient === "out",
|
|
1942
1989
|
children: truncate(item.title, titleMax) + " "
|
|
1943
1990
|
}
|
|
@@ -2379,11 +2426,12 @@ var BrowseScreen = ({
|
|
|
2379
2426
|
const out = /* @__PURE__ */ new Set();
|
|
2380
2427
|
if (!transients?.size) return out;
|
|
2381
2428
|
for (const s of localSections)
|
|
2382
|
-
if (s.items.some((item) => transientOf(transients, item
|
|
2429
|
+
if (s.items.some((item) => transientOf(transients, item, s.id)))
|
|
2430
|
+
out.add(s.id);
|
|
2383
2431
|
return out;
|
|
2384
2432
|
}, [localSections, transients]);
|
|
2385
2433
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
2386
|
-
const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || !!transients?.size && section.items.some((item) => transientOf(transients, item));
|
|
2434
|
+
const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || !!transients?.size && section.items.some((item) => transientOf(transients, item, section.id));
|
|
2387
2435
|
useEffect(() => {
|
|
2388
2436
|
if (!sparkling) return;
|
|
2389
2437
|
const id = setInterval(() => setSparkFrame((f) => f + 1), MERGED_FRAME_MS);
|
|
@@ -2853,7 +2901,7 @@ var BrowseScreen = ({
|
|
|
2853
2901
|
login,
|
|
2854
2902
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
2855
2903
|
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
2856
|
-
transient: transientOf(transients, item),
|
|
2904
|
+
transient: transientOf(transients, item, section.id),
|
|
2857
2905
|
prefix: treePrefix(section.items, viewStart + i),
|
|
2858
2906
|
parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
|
|
2859
2907
|
sparkFrame,
|
|
@@ -2986,7 +3034,12 @@ var App = ({
|
|
|
2986
3034
|
const settleTab = (tabId, marks) => {
|
|
2987
3035
|
const keys = [...transitTabs.current].filter(([, tab]) => tab === tabId).map(([key]) => key);
|
|
2988
3036
|
for (const key of keys) transitTabs.current.delete(key);
|
|
2989
|
-
const gone = new Set(
|
|
3037
|
+
const gone = new Set(
|
|
3038
|
+
keys.filter((key) => {
|
|
3039
|
+
const mark = marks.get(key);
|
|
3040
|
+
return !!mark && isDeparture(mark);
|
|
3041
|
+
}).map(rowKeyOfMark)
|
|
3042
|
+
);
|
|
2990
3043
|
setTransients((prev) => {
|
|
2991
3044
|
const next = new Map(prev);
|
|
2992
3045
|
for (const key of keys) next.delete(key);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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",
|