@kud/gh-ink 0.45.0 → 0.45.2

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 +68 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -703,7 +703,7 @@ var healthLegend = [
703
703
 
704
704
  // src/inbox/cache.ts
705
705
  var cacheTtlMs = () => inboxConfig().cacheTtlMs;
706
- var CACHE_VERSION = 3;
706
+ var CACHE_VERSION = 4;
707
707
  var cacheDir = () => join(
708
708
  process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
709
709
  inboxConfig().cacheNamespace
@@ -1786,14 +1786,20 @@ var RepoHeaderRow = ({
1786
1786
  /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
1787
1787
  ] });
1788
1788
  };
1789
- var MERGED_HOLD_MS = 5e3;
1789
+ var MERGED_HOLD_MS = 3e3;
1790
1790
  var MERGED_FRAME_MS = 150;
1791
1791
  var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
1792
1792
  var MERGED_COLOUR = "#A371F7";
1793
1793
  var TRANSIT_HOLD_MS = 7e3;
1794
- var TRANSIT_FRAME_TICKS = 3;
1795
- var PULSE_SETTLE_MS = 6e4;
1794
+ var TRANSIT_FRAME_TICKS = 2;
1795
+ var TRANSIT_FRAME_MS = MERGED_FRAME_MS * TRANSIT_FRAME_TICKS;
1796
+ var rampFrame = (len, loopFrame, transitAt, now = Date.now()) => transitAt == null ? loopFrame % len : Math.max(
1797
+ 0,
1798
+ Math.min(len - 1, Math.floor((now - transitAt) / TRANSIT_FRAME_MS))
1799
+ );
1800
+ var PULSE_SETTLE_MS = 12e3;
1796
1801
  var PULSE_MAX_FRAMES = Math.ceil(PULSE_SETTLE_MS / MERGED_FRAME_MS);
1802
+ var PULSE_IDLE_MS = 6e4;
1797
1803
  var PULSE_SETTLED_FRAME = 3;
1798
1804
  var NO_TRANSIENTS = /* @__PURE__ */ new Map();
1799
1805
  var NO_HOLDS = /* @__PURE__ */ new Map();
@@ -1825,6 +1831,7 @@ var ItemRow = ({
1825
1831
  merged,
1826
1832
  transient: refreshMark,
1827
1833
  leaving,
1834
+ transitAt,
1828
1835
  prefix = "",
1829
1836
  parent,
1830
1837
  sparkFrame = 0,
@@ -1833,6 +1840,7 @@ var ItemRow = ({
1833
1840
  const backdropped = useBackdropped();
1834
1841
  const transient = leaving ? "out" : refreshMark;
1835
1842
  const transitFrame = Math.floor(sparkFrame / TRANSIT_FRAME_TICKS);
1843
+ const rampAt = (len) => rampFrame(len, transitFrame, transitAt);
1836
1844
  const farewellLabel = leaving ? TRANSIT_LABEL.out : "";
1837
1845
  if (item.kind === "repo-header")
1838
1846
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
@@ -1857,7 +1865,7 @@ var ItemRow = ({
1857
1865
  ] });
1858
1866
  if (item.kind === "task") {
1859
1867
  const note = item.note ?? "";
1860
- const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[transitFrame % TRANSIT_OUT_FRAMES.length] : isArrival(transient) ? TRANSIT_IN_FRAMES[transitFrame % TRANSIT_IN_FRAMES.length] : "\u25C9";
1868
+ const transitIcon = !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
1861
1869
  const titleMax2 = Math.max(
1862
1870
  1,
1863
1871
  cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - 12
@@ -1889,7 +1897,7 @@ var ItemRow = ({
1889
1897
  ] });
1890
1898
  }
1891
1899
  const { glyph: healthIcon, color: healthColor2 } = healthDisplay[item.health];
1892
- const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient && isDeparture(transient) ? TRANSIT_OUT_FRAMES[transitFrame % TRANSIT_OUT_FRAMES.length] : transient && isArrival(transient) ? TRANSIT_IN_FRAMES[transitFrame % TRANSIT_IN_FRAMES.length] : healthIcon;
1900
+ const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient && isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : transient && isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : healthIcon;
1893
1901
  const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
1894
1902
  const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
1895
1903
  const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, "#FF8700"] : !login || !item.lastActor ? [" ", "white"] : spokeLast ? ["\u2192", "#888888"] : ["\u2190", "#FF8700"];
@@ -2311,6 +2319,7 @@ var BrowseScreen = ({
2311
2319
  brand,
2312
2320
  mergedUrls,
2313
2321
  leavingUrls,
2322
+ transitSince,
2314
2323
  onLeave,
2315
2324
  transients,
2316
2325
  onTabChange,
@@ -2414,7 +2423,7 @@ var BrowseScreen = ({
2414
2423
  out.add(s.id);
2415
2424
  return out;
2416
2425
  }, [localSections, transients]);
2417
- const [railOpen, setRailOpen] = useState(true);
2426
+ const [railOpen, setRailOpen] = useState(false);
2418
2427
  const showRail = railOpen && !!sidebar;
2419
2428
  const [railFocus, setRailFocus] = useState(false);
2420
2429
  const [railCursor, setRailCursor] = useState(0);
@@ -2434,20 +2443,28 @@ var BrowseScreen = ({
2434
2443
  mergedUrls?.length ?? 0,
2435
2444
  leavingUrls?.length ?? 0
2436
2445
  ].join("|") : "";
2446
+ const [pulseArm, setPulseArm] = useState(0);
2447
+ const lastKeyAt = useRef(Date.now());
2448
+ const pulseKey = sparkKey ? `${sparkKey}#${pulseArm}` : "";
2449
+ const [pulseSettled, setPulseSettled] = useState(false);
2437
2450
  useEffect(() => {
2438
- if (!sparkKey) return;
2451
+ if (!pulseKey) return;
2452
+ setPulseSettled(false);
2439
2453
  let frames = 0;
2440
2454
  const id = setInterval(() => {
2441
2455
  frames += 1;
2442
2456
  if (frames >= PULSE_MAX_FRAMES) {
2443
2457
  clearInterval(id);
2444
- setSparkFrame(PULSE_SETTLED_FRAME);
2458
+ setPulseSettled(true);
2445
2459
  return;
2446
2460
  }
2447
2461
  setSparkFrame((f) => f + 1);
2448
2462
  }, MERGED_FRAME_MS);
2449
2463
  return () => clearInterval(id);
2450
- }, [sparkKey]);
2464
+ }, [pulseKey]);
2465
+ useEffect(() => {
2466
+ if (sparkKey) setPulseArm((n) => n + 1);
2467
+ }, [tabIdx]);
2451
2468
  useEffect(() => {
2452
2469
  if (refreshError) showFlash(`\u2717 ${refreshError.message}`);
2453
2470
  }, [refreshError]);
@@ -2482,6 +2499,9 @@ var BrowseScreen = ({
2482
2499
  menu.open(actions);
2483
2500
  };
2484
2501
  useInput((input, key) => {
2502
+ const idleFor = Date.now() - lastKeyAt.current;
2503
+ lastKeyAt.current = Date.now();
2504
+ if (idleFor >= PULSE_IDLE_MS && sparkKey) setPulseArm((n) => n + 1);
2485
2505
  if (hidden) return;
2486
2506
  if (key.ctrl || key.meta) return;
2487
2507
  if (help) {
@@ -2945,7 +2965,11 @@ var BrowseScreen = ({
2945
2965
  // bar — and free to animate for exactly that reason.
2946
2966
  // The ticker undivided — see TAB_PULSE. The row marks' /3 buys quiet
2947
2967
  // for motion sitting beside text, and this is not that.
2948
- marker: tabMarker(markedTabs, s.id, sparkFrame),
2968
+ marker: tabMarker(
2969
+ markedTabs,
2970
+ s.id,
2971
+ pulseSettled ? PULSE_SETTLED_FRAME : sparkFrame
2972
+ ),
2949
2973
  markerColor: "#FF8700"
2950
2974
  }))
2951
2975
  }
@@ -2979,6 +3003,7 @@ var BrowseScreen = ({
2979
3003
  merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2980
3004
  leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
2981
3005
  transient: transientOf(transients, item, section.id),
3006
+ transitAt: "url" in item ? transitSince?.get(item.url) : void 0,
2982
3007
  prefix: treePrefix(section.items, viewStart + i),
2983
3008
  parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
2984
3009
  sparkFrame,
@@ -3316,8 +3341,24 @@ var App = ({
3316
3341
  const [skippedForBudget, setSkippedForBudget] = useState(false);
3317
3342
  const [mergedUrls, setMergedUrls] = useState([]);
3318
3343
  const [leavingUrls, setLeavingUrls] = useState([]);
3344
+ const leavingSince = useRef(/* @__PURE__ */ new Map());
3345
+ const transitSince = useMemo(() => {
3346
+ const out = new Map(leavingSince.current);
3347
+ for (const [url, tab] of transitTabs.current) {
3348
+ const at = heldSince.get(tab);
3349
+ if (at != null && !out.has(url)) out.set(url, at);
3350
+ }
3351
+ return out;
3352
+ }, [heldSince, transients, leavingUrls]);
3319
3353
  const holdTimers = useRef([]);
3320
3354
  useEffect(() => () => holdTimers.current.forEach(clearTimeout), []);
3355
+ const holdFor = (fn, ms) => {
3356
+ const id = setTimeout(() => {
3357
+ holdTimers.current = holdTimers.current.filter((t) => t !== id);
3358
+ fn();
3359
+ }, ms);
3360
+ holdTimers.current.push(id);
3361
+ };
3321
3362
  if (state.phase === "loading" || state.phase === "empty" || state.phase === "failed")
3322
3363
  return /* @__PURE__ */ jsxs(
3323
3364
  Box,
@@ -3367,31 +3408,30 @@ var App = ({
3367
3408
  const toBrowse = () => setState({ phase: "browse", sections: state.sections, login: state.login });
3368
3409
  const markLeaving = (target) => {
3369
3410
  if (state.phase !== "browse") toBrowse();
3411
+ if (!leavingSince.current.has(target.url))
3412
+ leavingSince.current.set(target.url, Date.now());
3370
3413
  setLeavingUrls(
3371
3414
  (prev) => prev.includes(target.url) ? prev : [...prev, target.url]
3372
3415
  );
3373
- holdTimers.current.push(
3374
- setTimeout(() => {
3375
- setLeavingUrls((prev) => prev.filter((u) => u !== target.url));
3376
- setState(
3377
- (s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
3378
- );
3379
- }, LEAVING_HOLD_MS)
3380
- );
3416
+ holdFor(() => {
3417
+ leavingSince.current.delete(target.url);
3418
+ setLeavingUrls((prev) => prev.filter((u) => u !== target.url));
3419
+ setState(
3420
+ (s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
3421
+ );
3422
+ }, LEAVING_HOLD_MS);
3381
3423
  };
3382
3424
  const markMerged = (target) => {
3383
3425
  toBrowse();
3384
3426
  setMergedUrls(
3385
3427
  (prev) => prev.includes(target.url) ? prev : [...prev, target.url]
3386
3428
  );
3387
- holdTimers.current.push(
3388
- setTimeout(() => {
3389
- setMergedUrls((prev) => prev.filter((u) => u !== target.url));
3390
- setState(
3391
- (s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
3392
- );
3393
- }, MERGED_HOLD_MS)
3394
- );
3429
+ holdFor(() => {
3430
+ setMergedUrls((prev) => prev.filter((u) => u !== target.url));
3431
+ setState(
3432
+ (s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
3433
+ );
3434
+ }, MERGED_HOLD_MS);
3395
3435
  };
3396
3436
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3397
3437
  /* @__PURE__ */ jsx(
@@ -3417,6 +3457,7 @@ var App = ({
3417
3457
  hidden: overlay !== null,
3418
3458
  mergedUrls,
3419
3459
  leavingUrls,
3460
+ transitSince,
3420
3461
  onLeave: markLeaving,
3421
3462
  transients,
3422
3463
  onTabChange: setVisibleTab,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.45.0",
3
+ "version": "0.45.2",
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",