@kud/gh-ink 0.35.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.
Files changed (2) hide show
  1. package/dist/index.js +81 -28
  2. package/package.json +1 -1
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) out.set(key, { item, sectionId: section.id, index });
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 [key] of after) if (!before.has(key)) transients.set(key, "in");
738
- for (const [key, was] of before) {
739
- const now = after.get(key);
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(key, "out");
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(key, "changed");
773
+ transients.set(mark, "changed");
745
774
  }
746
775
  }
747
776
  let added = 0;
748
777
  let removed = 0;
749
- let changed = 0;
750
- for (const [, kind] of transients) {
751
- if (kind === "in") added++;
752
- else if (kind === "out") removed++;
753
- else changed++;
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 && marks.has(key)) out.set(key, section.id);
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
  };
@@ -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 === "out" ? TRANSIT_OUT_FRAMES[sparkFrame % TRANSIT_OUT_FRAMES.length] : transient === "in" ? TRANSIT_IN_FRAMES[sparkFrame % TRANSIT_IN_FRAMES.length] : "\u25C9";
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 === "in",
1900
- dimColor: transient === "out",
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 === "out" ? TRANSIT_OUT_FRAMES[sparkFrame % TRANSIT_OUT_FRAMES.length] : transient === "in" ? TRANSIT_IN_FRAMES[sparkFrame % TRANSIT_IN_FRAMES.length] : healthIcon;
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 === "in",
1940
- dimColor: transient === "out",
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))) out.add(s.id);
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(keys.filter((key) => marks.get(key) === "out"));
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.35.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",