@develemit/web-hooks 0.3.1 → 0.5.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.cjs CHANGED
@@ -25,19 +25,29 @@ __export(src_exports, {
25
25
  CLOSE_DISTANCE_PX: () => CLOSE_DISTANCE_PX,
26
26
  COARSE_POINTER_QUERY: () => COARSE_POINTER_QUERY,
27
27
  DEFAULT_IGNORE_SELECTOR: () => DEFAULT_IGNORE_SELECTOR,
28
+ DEFAULT_TAIL_THRESHOLD: () => DEFAULT_TAIL_THRESHOLD,
29
+ FAILURE_PATTERNS: () => FAILURE_PATTERNS,
28
30
  FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
29
31
  FORWARD_ZONE_PX: () => FORWARD_ZONE_PX,
30
32
  MAX_SWIPE_DURATION_MS: () => MAX_SWIPE_DURATION_MS,
33
+ NOISE_PATTERNS: () => NOISE_PATTERNS,
31
34
  OPEN_DISTANCE_PX: () => OPEN_DISTANCE_PX,
32
35
  REDUCED_MOTION_QUERY: () => REDUCED_MOTION_QUERY,
33
36
  VERTICAL_CANCEL_PX: () => VERTICAL_CANCEL_PX,
34
37
  canShareFiles: () => canShareFiles,
35
38
  computeKeyboardInset: () => computeKeyboardInset,
36
39
  copyText: () => copyText,
40
+ createAnsiParser: () => createAnsiParser,
41
+ createAnsiStream: () => createAnsiStream,
37
42
  createDebouncer: () => createDebouncer,
43
+ createLineBuffer: () => createLineBuffer,
38
44
  deriveUpdateState: () => deriveUpdateState,
39
45
  detectApple: () => detectApple,
40
46
  detectPlatform: () => detectPlatform,
47
+ distanceFromEnd: () => distanceFromEnd,
48
+ filterLines: () => filterLines,
49
+ findFailures: () => findFailures,
50
+ firstFailureIndex: () => firstFailureIndex,
41
51
  focusableWithin: () => focusableWithin,
42
52
  getPushSupport: () => getPushSupport,
43
53
  handleBeforeUnload: () => handleBeforeUnload,
@@ -48,23 +58,32 @@ __export(src_exports, {
48
58
  isInsideHorizontalScroller: () => isInsideHorizontalScroller,
49
59
  isIos: () => isIos,
50
60
  isIosSafari: () => isIosSafari,
61
+ isNearEnd: () => isNearEnd,
62
+ isNoise: () => isNoise,
51
63
  isOutside: () => isOutside,
52
64
  isPrimaryPress: () => isPrimaryPress,
53
65
  isShareCancel: () => isShareCancel,
54
66
  isStandalone: () => isStandalone,
67
+ isUserScrollUp: () => isUserScrollUp,
55
68
  lockScroll: () => lockScroll,
56
69
  matchHotkey: () => matchHotkey,
57
70
  nextDelay: () => nextDelay,
58
71
  nextFocusTarget: () => nextFocusTarget,
59
72
  nextHeight: () => nextHeight,
73
+ parseAnsi: () => parseAnsi,
60
74
  parseHotkey: () => parseHotkey,
61
75
  readText: () => readText,
62
76
  resolveSwipeIntent: () => resolveSwipeIntent,
63
77
  shareOrCopy: () => shareOrCopy,
64
78
  shareTextFor: () => shareTextFor,
65
79
  shouldResubscribe: () => shouldResubscribe,
80
+ shouldStick: () => shouldStick,
81
+ splitByMatch: () => splitByMatch,
66
82
  startInstallPromptCapture: () => startInstallPromptCapture,
83
+ stripAnsi: () => stripAnsi,
84
+ tailMeaningful: () => tailMeaningful,
67
85
  unlockScroll: () => unlockScroll,
86
+ unreadDelta: () => unreadDelta,
68
87
  unregisterAll: () => unregisterAll,
69
88
  urlBase64ToUint8Array: () => urlBase64ToUint8Array,
70
89
  useAutoGrowTextarea: () => useAutoGrowTextarea,
@@ -75,6 +94,7 @@ __export(src_exports, {
75
94
  useDrawerSwipe: () => useDrawerSwipe,
76
95
  useEscape: () => useEscape,
77
96
  useFocusTrap: () => useFocusTrap,
97
+ useFollowTail: () => useFollowTail,
78
98
  useHotkey: () => useHotkey,
79
99
  useInstallPrompt: () => useInstallPrompt,
80
100
  useIsStandalone: () => useIsStandalone,
@@ -560,9 +580,9 @@ function useVisibilityPolling(fn, intervalMs, { enabled = true, immediate = true
560
580
  function schedule() {
561
581
  clearTimer();
562
582
  if (!active || isHidden()) return;
563
- timer = setTimeout(run, nextDelay(intervalMs, optionsRef.current.jitter, Math.random));
583
+ timer = setTimeout(run2, nextDelay(intervalMs, optionsRef.current.jitter, Math.random));
564
584
  }
565
- async function run() {
585
+ async function run2() {
566
586
  timer = null;
567
587
  if (!active || runningRef.current || isHidden()) return;
568
588
  runningRef.current = true;
@@ -579,13 +599,13 @@ function useVisibilityPolling(fn, intervalMs, { enabled = true, immediate = true
579
599
  clearTimer();
580
600
  } else if (optionsRef.current.runOnVisible) {
581
601
  clearTimer();
582
- void run();
602
+ void run2();
583
603
  } else if (!runningRef.current) {
584
604
  schedule();
585
605
  }
586
606
  }
587
607
  scheduleRef.current = schedule;
588
- if (optionsRef.current.immediate && !isHidden()) void run();
608
+ if (optionsRef.current.immediate && !isHidden()) void run2();
589
609
  else schedule();
590
610
  document.addEventListener("visibilitychange", onVisibilityChange);
591
611
  return () => {
@@ -606,7 +626,7 @@ function createDebouncer(getFn, getMs) {
606
626
  timer = void 0;
607
627
  pending = void 0;
608
628
  }
609
- function run() {
629
+ function run2() {
610
630
  const args = pending;
611
631
  clear();
612
632
  if (args) getFn()(...args);
@@ -615,11 +635,11 @@ function createDebouncer(getFn, getMs) {
615
635
  call(...args) {
616
636
  if (timer !== void 0) clearTimeout(timer);
617
637
  pending = args;
618
- timer = setTimeout(run, getMs());
638
+ timer = setTimeout(run2, getMs());
619
639
  },
620
640
  cancel: clear,
621
641
  flush() {
622
- if (pending) run();
642
+ if (pending) run2();
623
643
  },
624
644
  isPending: () => pending !== void 0
625
645
  };
@@ -1462,25 +1482,627 @@ function usePushSubscription(options) {
1462
1482
  }, []);
1463
1483
  return { support, permission, subscribed, busy, error, subscribe: subscribe3, unsubscribe };
1464
1484
  }
1485
+
1486
+ // src/terminal-output/ansi-sgr.ts
1487
+ var FLAG_ON = {
1488
+ 1: "bold",
1489
+ 2: "dim",
1490
+ 3: "italic",
1491
+ 4: "underline",
1492
+ 7: "inverse"
1493
+ };
1494
+ var FLAG_OFF = {
1495
+ 22: ["bold", "dim"],
1496
+ 23: ["italic"],
1497
+ 24: ["underline"],
1498
+ 27: ["inverse"]
1499
+ };
1500
+ function byte(n) {
1501
+ return Math.max(0, Math.min(255, n));
1502
+ }
1503
+ function extendedColor(args) {
1504
+ if (args[0] === 5 && args.length >= 2) return { kind: "ansi", index: byte(args[1] ?? 0) };
1505
+ if (args[0] === 2 && args.length >= 4) {
1506
+ const [r = 0, g = 0, b = 0] = args.slice(-3);
1507
+ return { kind: "rgb", r: byte(r), g: byte(g), b: byte(b) };
1508
+ }
1509
+ return null;
1510
+ }
1511
+ function setKey(style, key, value) {
1512
+ if (value === void 0 || value === false) delete style[key];
1513
+ else style[key] = value;
1514
+ }
1515
+ function applySgr(style, params) {
1516
+ const next = { ...style };
1517
+ const tokens = params === "" ? ["0"] : params.split(";");
1518
+ for (let i = 0; i < tokens.length; i++) {
1519
+ const parts = (tokens[i] ?? "").split(":").map((p) => p === "" ? 0 : Number(p));
1520
+ const code = parts[0] ?? 0;
1521
+ if (Number.isNaN(code)) continue;
1522
+ if (code === 38 || code === 48) {
1523
+ const key = code === 38 ? "fg" : "bg";
1524
+ let args = parts.slice(1);
1525
+ if (parts.length === 1) {
1526
+ args = tokens.slice(i + 1, i + 5).map(Number);
1527
+ const used = args[0] === 5 ? 2 : args[0] === 2 ? 4 : 0;
1528
+ i += used;
1529
+ args = args.slice(0, used);
1530
+ }
1531
+ const color = extendedColor(args);
1532
+ if (color) setKey(next, key, color);
1533
+ continue;
1534
+ }
1535
+ if (code === 0) {
1536
+ for (const k of Object.keys(next)) delete next[k];
1537
+ } else if (FLAG_ON[code]) setKey(next, FLAG_ON[code], true);
1538
+ else if (FLAG_OFF[code]) (FLAG_OFF[code] ?? []).forEach((k) => setKey(next, k, void 0));
1539
+ else if (code >= 30 && code <= 37) setKey(next, "fg", { kind: "ansi", index: code - 30 });
1540
+ else if (code === 39) setKey(next, "fg", void 0);
1541
+ else if (code >= 40 && code <= 47) setKey(next, "bg", { kind: "ansi", index: code - 40 });
1542
+ else if (code === 49) setKey(next, "bg", void 0);
1543
+ else if (code >= 90 && code <= 97) setKey(next, "fg", { kind: "ansi", index: code - 90 + 8 });
1544
+ else if (code >= 100 && code <= 107) setKey(next, "bg", { kind: "ansi", index: code - 100 + 8 });
1545
+ }
1546
+ return next;
1547
+ }
1548
+ function colorEquals(a, b) {
1549
+ if (a === b) return true;
1550
+ if (!a || !b || a.kind !== b.kind) return false;
1551
+ if (a.kind === "ansi") return a.index === b.index;
1552
+ const o = b;
1553
+ return a.r === o.r && a.g === o.g && a.b === o.b;
1554
+ }
1555
+ function styleEquals(a, b) {
1556
+ return a === b || colorEquals(a.fg, b.fg) && colorEquals(a.bg, b.bg) && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.italic === !!b.italic && !!a.underline === !!b.underline && !!a.inverse === !!b.inverse;
1557
+ }
1558
+
1559
+ // src/terminal-output/ansi-screen.ts
1560
+ var BLANK = {};
1561
+ var newRow = () => ({ cells: [], shared: false });
1562
+ var Screen = class _Screen {
1563
+ rows = [newRow()];
1564
+ row = 0;
1565
+ col = 0;
1566
+ line() {
1567
+ const row = this.rows[this.row] ??= newRow();
1568
+ if (row.shared) {
1569
+ row.cells = row.cells.slice();
1570
+ row.shared = false;
1571
+ }
1572
+ row.segments = void 0;
1573
+ return row.cells;
1574
+ }
1575
+ write(ch, style, href) {
1576
+ const line = this.line();
1577
+ while (line.length < this.col) line.push({ ch: " ", style: BLANK });
1578
+ line[this.col] = { ch, style, href };
1579
+ this.col++;
1580
+ }
1581
+ newline() {
1582
+ this.moveRow(this.row + 1);
1583
+ this.col = 0;
1584
+ }
1585
+ carriageReturn() {
1586
+ this.col = 0;
1587
+ }
1588
+ backspace() {
1589
+ this.col = Math.max(0, this.col - 1);
1590
+ }
1591
+ moveRow(row) {
1592
+ this.row = Math.max(0, row);
1593
+ while (this.rows.length <= this.row) this.rows.push(newRow());
1594
+ }
1595
+ moveRowBy(delta) {
1596
+ this.moveRow(this.row + delta);
1597
+ }
1598
+ setColumn(col) {
1599
+ this.col = Math.max(0, col);
1600
+ }
1601
+ /** `ESC[nK`: 0 cursor to end, 1 start to cursor, 2 whole line. */
1602
+ eraseLine(mode) {
1603
+ const line = this.line();
1604
+ if (mode === 2) this.rows[this.row] = newRow();
1605
+ else if (mode === 1) {
1606
+ for (let i = 0; i < Math.min(this.col + 1, line.length); i++) line[i] = { ch: " ", style: BLANK };
1607
+ } else if (this.col < line.length) line.length = this.col;
1608
+ }
1609
+ /** `ESC[0J`: erase from the cursor to the end of the screen. */
1610
+ eraseBelow() {
1611
+ this.eraseLine(0);
1612
+ this.rows.length = this.row + 1;
1613
+ }
1614
+ lines() {
1615
+ return this.rows.map((row) => row.segments ??= toSegments(row.cells));
1616
+ }
1617
+ get rowCount() {
1618
+ return this.rows.length;
1619
+ }
1620
+ get cursorRow() {
1621
+ return this.row;
1622
+ }
1623
+ /** Forgets the first `count` rows (never the cursor's row) and returns how many went. */
1624
+ dropTop(count2) {
1625
+ const n = Math.max(0, Math.min(count2, this.row));
1626
+ this.rows.splice(0, n);
1627
+ this.row -= n;
1628
+ return n;
1629
+ }
1630
+ /** An independent copy, so a partial line can be rendered without committing it. */
1631
+ clone() {
1632
+ const copy = new _Screen();
1633
+ for (const row of this.rows) row.shared = true;
1634
+ copy.rows = this.rows.map((r) => ({ ...r }));
1635
+ copy.row = this.row;
1636
+ copy.col = this.col;
1637
+ return copy;
1638
+ }
1639
+ };
1640
+ function toSegments(cells) {
1641
+ const out = [];
1642
+ let current = null;
1643
+ let currentStyle = null;
1644
+ for (const cell of cells) {
1645
+ if (current && currentStyle && current.href === cell.href && styleEquals(currentStyle, cell.style)) {
1646
+ current.text += cell.ch;
1647
+ continue;
1648
+ }
1649
+ current = { text: cell.ch, ...cell.style };
1650
+ if (cell.href) current.href = cell.href;
1651
+ currentStyle = cell.style;
1652
+ out.push(current);
1653
+ }
1654
+ return out;
1655
+ }
1656
+
1657
+ // src/terminal-output/ansi.ts
1658
+ var ESCAPE_SOURCE = "\\x1b(?:\\[([0-?]*)([ -/]*)([@-~])|\\]([^\\x07\\x1b]*)(?:\\x07|\\x1b\\\\)|[ -/]+[0-~]|[0-~])";
1659
+ var DEFAULT_SCHEMES = ["http", "https", "file"];
1660
+ function stripAnsi(text) {
1661
+ return text.replace(new RegExp(ESCAPE_SOURCE, "g"), "");
1662
+ }
1663
+ function allowedHref(uri, schemes) {
1664
+ const match = /^([a-z][a-z0-9+.-]*):/i.exec(uri);
1665
+ return match && schemes.includes((match[1] ?? "").toLowerCase()) ? uri : void 0;
1666
+ }
1667
+ function writeText(screen, state2, text) {
1668
+ for (const ch of text) {
1669
+ if (ch === "\n") screen.newline();
1670
+ else if (ch === "\r") screen.carriageReturn();
1671
+ else if (ch === "\b") screen.backspace();
1672
+ else if (ch === " " || ch >= " " && ch !== "\x7F") screen.write(ch, state2.style, state2.href);
1673
+ }
1674
+ }
1675
+ function applyCsi(screen, state2, params, inter, final) {
1676
+ if (inter !== "") return;
1677
+ if (final === "m") {
1678
+ if (/^[0-9;:]*$/.test(params)) state2.style = applySgr(state2.style, params);
1679
+ return;
1680
+ }
1681
+ if (!/^[0-9;]*$/.test(params)) return;
1682
+ const n = params === "" ? NaN : Number(params.split(";")[0]);
1683
+ const count2 = Number.isNaN(n) || n === 0 ? 1 : n;
1684
+ const mode = Number.isNaN(n) ? 0 : n;
1685
+ if (final === "A") screen.moveRowBy(-count2);
1686
+ else if (final === "B") screen.moveRowBy(count2);
1687
+ else if (final === "G") screen.setColumn(count2 - 1);
1688
+ else if (final === "K") screen.eraseLine(mode);
1689
+ else if (final === "J" && mode === 0) screen.eraseBelow();
1690
+ }
1691
+ function applyOsc(state2, payload, schemes) {
1692
+ if (!payload.startsWith("8;")) return;
1693
+ const split = payload.indexOf(";", 2);
1694
+ state2.href = split === -1 ? void 0 : allowedHref(payload.slice(split + 1), schemes);
1695
+ }
1696
+ function feedAnsi(screen, state2, text, options) {
1697
+ const schemes = options.linkSchemes ?? DEFAULT_SCHEMES;
1698
+ const re = new RegExp(ESCAPE_SOURCE, "g");
1699
+ let last = 0;
1700
+ for (let m = re.exec(text); m; m = re.exec(text)) {
1701
+ writeText(screen, state2, text.slice(last, m.index));
1702
+ last = m.index + m[0].length;
1703
+ if (m[3] !== void 0) applyCsi(screen, state2, m[1] ?? "", m[2] ?? "", m[3]);
1704
+ else if (m[4] !== void 0) applyOsc(state2, m[4], schemes);
1705
+ }
1706
+ writeText(screen, state2, text.slice(last));
1707
+ }
1708
+ function run(text, state2, options) {
1709
+ const screen = new Screen();
1710
+ feedAnsi(screen, state2, text, options);
1711
+ return screen.lines();
1712
+ }
1713
+ function parseAnsi(text, options = {}) {
1714
+ return run(text, { style: {} }, options);
1715
+ }
1716
+ function createAnsiParser(options = {}) {
1717
+ const state2 = { style: {} };
1718
+ return {
1719
+ parse: (chunk) => run(chunk, state2, options),
1720
+ reset: () => {
1721
+ state2.style = {};
1722
+ state2.href = void 0;
1723
+ }
1724
+ };
1725
+ }
1726
+
1727
+ // src/terminal-output/line-split.ts
1728
+ var INCOMPLETE_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*|\][^\x07\x1b]*\x1b?|[ -/]*)$/;
1729
+ function splitIncompleteEscape(text) {
1730
+ const match = INCOMPLETE_ESCAPE.exec(text);
1731
+ if (!match) return { complete: text, held: "" };
1732
+ return { complete: text.slice(0, match.index), held: match[0] };
1733
+ }
1734
+ function splitLines(text) {
1735
+ const pieces = text.split("\n");
1736
+ const rest = pieces.pop() ?? "";
1737
+ const lines = pieces.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
1738
+ return { lines, rest };
1739
+ }
1740
+
1741
+ // src/terminal-output/line-buffer.ts
1742
+ function createLineBuffer({ maxLines }) {
1743
+ let decoder = new TextDecoder("utf-8");
1744
+ let lines = [];
1745
+ let pending = "";
1746
+ let cursor;
1747
+ let dropped = 0;
1748
+ function keep(added) {
1749
+ lines.push(...added);
1750
+ const over = Math.max(0, lines.length - maxLines);
1751
+ if (over > 0) lines.splice(0, over);
1752
+ dropped += over;
1753
+ return over;
1754
+ }
1755
+ function update(added, over) {
1756
+ return { lines: added, partial: splitIncompleteEscape(pending).complete, dropped: over };
1757
+ }
1758
+ return {
1759
+ push(chunk, options) {
1760
+ const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
1761
+ if (options?.cursor !== void 0) cursor = options.cursor;
1762
+ const { lines: added, rest } = splitLines(pending + text);
1763
+ pending = rest;
1764
+ return update(added, keep(added));
1765
+ },
1766
+ flush() {
1767
+ const tail = pending + decoder.decode();
1768
+ pending = "";
1769
+ const added = tail === "" ? [] : [tail];
1770
+ return update(added, keep(added));
1771
+ },
1772
+ reset() {
1773
+ decoder = new TextDecoder("utf-8");
1774
+ lines = [];
1775
+ pending = "";
1776
+ cursor = void 0;
1777
+ dropped = 0;
1778
+ },
1779
+ get lines() {
1780
+ return lines;
1781
+ },
1782
+ get partial() {
1783
+ return splitIncompleteEscape(pending).complete;
1784
+ },
1785
+ get cursor() {
1786
+ return cursor;
1787
+ },
1788
+ get dropped() {
1789
+ return dropped;
1790
+ }
1791
+ };
1792
+ }
1793
+
1794
+ // src/terminal-output/ansi-stream.ts
1795
+ var DEFAULT_TAIL = 50;
1796
+ var CURSOR_UP = /\x1b\[(\d*)A/g;
1797
+ function largestCursorUp(text) {
1798
+ let largest = 0;
1799
+ for (const m of text.matchAll(CURSOR_UP)) largest = Math.max(largest, Number(m[1] || 1));
1800
+ return largest;
1801
+ }
1802
+ function sameLine(a, b) {
1803
+ return a !== void 0 && (a === b || JSON.stringify(a) === JSON.stringify(b));
1804
+ }
1805
+ function createAnsiStream(options = {}) {
1806
+ let tail = options.tailLines ?? DEFAULT_TAIL;
1807
+ let screen = new Screen();
1808
+ let state2 = { style: {} };
1809
+ let frozen = 0;
1810
+ let shown = [];
1811
+ let lastPartial = "";
1812
+ let finished = false;
1813
+ function render(partial) {
1814
+ tail = Math.max(tail, largestCursorUp(partial));
1815
+ screen.lines();
1816
+ const draft = screen.clone();
1817
+ feedAnsi(draft, { ...state2 }, partial, options);
1818
+ const rows = draft.lines();
1819
+ const first = rows.findIndex((row, i) => !sameLine(shown[i], row));
1820
+ const from = first === -1 ? rows.length < shown.length ? rows.length : -1 : first;
1821
+ shown = rows;
1822
+ const update = from === -1 ? {} : { replaced: { from: frozen + from, lines: rows.slice(from) } };
1823
+ const freeze = screen.cursorRow - tail;
1824
+ if (freeze > 0) {
1825
+ const dropped = screen.dropTop(freeze);
1826
+ shown = shown.slice(dropped);
1827
+ frozen += dropped;
1828
+ }
1829
+ return update;
1830
+ }
1831
+ return {
1832
+ push(lines, partial = "") {
1833
+ if (finished) return {};
1834
+ for (const line of lines) {
1835
+ tail = Math.max(tail, largestCursorUp(line));
1836
+ feedAnsi(screen, state2, `${line}
1837
+ `, options);
1838
+ }
1839
+ lastPartial = partial;
1840
+ return render(partial);
1841
+ },
1842
+ finish(partial = lastPartial) {
1843
+ if (finished) return {};
1844
+ const update = render(partial);
1845
+ finished = true;
1846
+ return update;
1847
+ },
1848
+ reset() {
1849
+ finished = false;
1850
+ lastPartial = "";
1851
+ screen = new Screen();
1852
+ state2 = { style: {} };
1853
+ frozen = 0;
1854
+ shown = [];
1855
+ tail = options.tailLines ?? DEFAULT_TAIL;
1856
+ }
1857
+ };
1858
+ }
1859
+
1860
+ // src/terminal-output/log-helpers.ts
1861
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1862
+ function queryMatcher(query) {
1863
+ const q = query.trim();
1864
+ return q ? new RegExp(escapeRegExp(q), "giu") : null;
1865
+ }
1866
+ function filterLines(lines, query) {
1867
+ const matcher = queryMatcher(query);
1868
+ const indexes = [];
1869
+ lines.forEach((line, i) => {
1870
+ if (!matcher) indexes.push(i);
1871
+ else {
1872
+ matcher.lastIndex = 0;
1873
+ if (matcher.test(line)) indexes.push(i);
1874
+ }
1875
+ });
1876
+ return indexes;
1877
+ }
1878
+ function splitByMatch(line, query) {
1879
+ const matcher = queryMatcher(query);
1880
+ if (!matcher) return [{ text: line, match: false }];
1881
+ const segments = [];
1882
+ let last = 0;
1883
+ for (const m of line.matchAll(matcher)) {
1884
+ const start = m.index ?? 0;
1885
+ if (start > last) segments.push({ text: line.slice(last, start), match: false });
1886
+ segments.push({ text: m[0], match: true });
1887
+ last = start + m[0].length;
1888
+ }
1889
+ if (last < line.length || segments.length === 0) {
1890
+ segments.push({ text: line.slice(last), match: false });
1891
+ }
1892
+ return segments;
1893
+ }
1894
+ var FAILURE_PATTERNS = [
1895
+ { tool: "tsc", kind: "error", pattern: /\berror TS\d+/ },
1896
+ { tool: "pnpm", kind: "error", pattern: /\bERR_PNPM_[A-Z_]+/ },
1897
+ { tool: "npm", kind: "error", pattern: /\bnpm (?:ERR!|error\b)/ },
1898
+ { tool: "nx", kind: "failed tasks", pattern: /targets failed, including|Failed tasks/ },
1899
+ { tool: "nx", kind: "task failed", pattern: /^\s*✖\s+nx run / },
1900
+ { tool: "nx", kind: "failed", pattern: /^\s*✖\s+\d+\/\d+ failed/ },
1901
+ { tool: "vitest", kind: "fail", pattern: /^\s*FAIL\s/ },
1902
+ { tool: "vitest", kind: "test", pattern: /^\s*[×✗]\s/ },
1903
+ { tool: "docker", kind: "error", pattern: /^\s*(?:Error response from daemon|ERROR: failed to solve|failed to solve|pull access denied)/ },
1904
+ { tool: "docker", kind: "exit", pattern: /^\s*Container \S+\s+(?:Error|exited with code [1-9]\d*)/ },
1905
+ { tool: "generic", kind: "error", pattern: /\b\w*Error\b(?: \[\w+\])?:/ }
1906
+ ];
1907
+ var STACK_FRAME = /^\s*at\s/;
1908
+ function findFailures(lines, patterns = FAILURE_PATTERNS) {
1909
+ const failures = [];
1910
+ lines.forEach((line, index) => {
1911
+ if (STACK_FRAME.test(line)) return;
1912
+ const hit = patterns.find((p) => p.pattern.test(line));
1913
+ if (hit) failures.push({ index, tool: hit.tool, kind: hit.kind });
1914
+ });
1915
+ return failures;
1916
+ }
1917
+ function firstFailureIndex(lines, patterns = FAILURE_PATTERNS) {
1918
+ return findFailures(lines, patterns)[0]?.index ?? -1;
1919
+ }
1920
+ var NOISE_PATTERNS = [
1921
+ /^\s*[0-9a-f]{12}: (?:Pulling fs layer|Waiting|Downloading|Download complete|Verifying Checksum|Extracting|Pull complete|Already exists)/,
1922
+ /^\s*[0-9a-f]{12} (?:Pulling fs layer|Waiting|Downloading|Download complete|Verifying|Extracting|Pull complete|Already exists)/,
1923
+ /^\s*Image \S+\s+(?:Pulling|Pulled)\s*$/,
1924
+ /^\s*Container \S+\s+(?:Creating|Created|Starting|Started|Running|Waiting|Healthy|Stopping|Stopped|Removing|Removed)\s*$/,
1925
+ /^\s*Nx read the output from the cache/
1926
+ ];
1927
+ function isNoise(line, patterns = NOISE_PATTERNS) {
1928
+ return patterns.some((p) => p.test(line));
1929
+ }
1930
+ function tailMeaningful(lines, n, patterns = NOISE_PATTERNS) {
1931
+ const out = [];
1932
+ for (let i = lines.length - 1; i >= 0 && out.length < n; i--) {
1933
+ const line = (lines[i] ?? "").trimEnd();
1934
+ if (line.trim() && !isNoise(line, patterns)) out.unshift(line);
1935
+ }
1936
+ return out;
1937
+ }
1938
+
1939
+ // src/terminal-output/follow-tail.ts
1940
+ var DEFAULT_TAIL_THRESHOLD = 24;
1941
+ function distanceFromEnd({ scrollTop, scrollHeight, clientHeight }) {
1942
+ return scrollHeight - scrollTop - clientHeight;
1943
+ }
1944
+ function isNearEnd(metrics, threshold = DEFAULT_TAIL_THRESHOLD) {
1945
+ return distanceFromEnd(metrics) <= threshold;
1946
+ }
1947
+ function shouldStick(prevAtBottom, metrics, userInitiated, threshold = DEFAULT_TAIL_THRESHOLD) {
1948
+ if (!userInitiated) return prevAtBottom;
1949
+ return isNearEnd(metrics, threshold);
1950
+ }
1951
+ function isUserScrollUp({ kind, delta = 0, lastTarget, scrollTop }, threshold = DEFAULT_TAIL_THRESHOLD) {
1952
+ if (kind === "wheel") return delta < 0;
1953
+ if (kind === "touch") return delta > 0;
1954
+ if (kind === "gutter") return true;
1955
+ return lastTarget !== null && scrollTop < lastTarget - threshold;
1956
+ }
1957
+ function unreadDelta(prevCount, nextCount, attached2) {
1958
+ if (attached2) return 0;
1959
+ return Math.max(0, nextCount - prevCount);
1960
+ }
1961
+
1962
+ // src/terminal-output/use-follow-tail.ts
1963
+ var import_react22 = require("react");
1964
+ var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? import_react22.useEffect : import_react22.useLayoutEffect;
1965
+ function useFollowTail({
1966
+ scrollRef,
1967
+ itemCount,
1968
+ scrollToEnd,
1969
+ resetKey,
1970
+ threshold = DEFAULT_TAIL_THRESHOLD
1971
+ }) {
1972
+ const [atBottom, setAtBottom] = (0, import_react22.useState)(true);
1973
+ const [unread, setUnread] = (0, import_react22.useState)(0);
1974
+ const attached2 = (0, import_react22.useRef)(true);
1975
+ const programmatic = (0, import_react22.useRef)(false);
1976
+ const lastTarget = (0, import_react22.useRef)(null);
1977
+ const lastTop = (0, import_react22.useRef)(0);
1978
+ const frame = (0, import_react22.useRef)(0);
1979
+ const prevCount = (0, import_react22.useRef)(itemCount);
1980
+ const prevReset = (0, import_react22.useRef)(resetKey);
1981
+ const latest = (0, import_react22.useRef)({ scrollToEnd, threshold });
1982
+ latest.current = { scrollToEnd, threshold };
1983
+ const attach = (0, import_react22.useCallback)((value) => {
1984
+ attached2.current = value;
1985
+ setAtBottom(value);
1986
+ if (value) setUnread(0);
1987
+ }, []);
1988
+ const followEnd = (0, import_react22.useCallback)(() => {
1989
+ const el = scrollRef.current;
1990
+ if (!el) return;
1991
+ programmatic.current = true;
1992
+ if (latest.current.scrollToEnd) latest.current.scrollToEnd();
1993
+ else el.scrollTop = el.scrollHeight;
1994
+ lastTarget.current = el.scrollHeight - el.clientHeight;
1995
+ if (typeof requestAnimationFrame === "function") {
1996
+ cancelAnimationFrame(frame.current);
1997
+ frame.current = requestAnimationFrame(() => {
1998
+ programmatic.current = false;
1999
+ });
2000
+ } else {
2001
+ programmatic.current = false;
2002
+ }
2003
+ }, [scrollRef]);
2004
+ useIsomorphicLayoutEffect2(() => {
2005
+ const count2 = prevCount.current;
2006
+ prevCount.current = itemCount;
2007
+ if (prevReset.current !== resetKey) {
2008
+ prevReset.current = resetKey;
2009
+ attach(true);
2010
+ followEnd();
2011
+ return;
2012
+ }
2013
+ if (attached2.current) {
2014
+ if (itemCount > 0) followEnd();
2015
+ return;
2016
+ }
2017
+ const added = unreadDelta(count2, itemCount, false);
2018
+ if (added > 0) setUnread((n) => n + added);
2019
+ }, [itemCount, resetKey, attach, followEnd]);
2020
+ (0, import_react22.useEffect)(() => {
2021
+ const el = scrollRef.current;
2022
+ if (!el) return;
2023
+ const takeOver = () => {
2024
+ programmatic.current = false;
2025
+ lastTarget.current = null;
2026
+ if (attached2.current) attach(false);
2027
+ };
2028
+ const onScroll = () => {
2029
+ const { threshold: limit } = latest.current;
2030
+ const movedUp = el.scrollTop < lastTop.current;
2031
+ lastTop.current = el.scrollTop;
2032
+ const info = { kind: "scroll", lastTarget: movedUp ? lastTarget.current : null, scrollTop: el.scrollTop };
2033
+ const user = isUserScrollUp(info, limit);
2034
+ if (user) takeOver();
2035
+ const next = shouldStick(attached2.current, el, user || !programmatic.current, limit);
2036
+ if (next !== attached2.current) attach(next);
2037
+ };
2038
+ const canScrollUp = () => el.scrollHeight > el.clientHeight && el.scrollTop > 0;
2039
+ const onWheel = (e) => {
2040
+ if (canScrollUp() && isUserScrollUp({ kind: "wheel", delta: e.deltaY, lastTarget: null, scrollTop: el.scrollTop })) takeOver();
2041
+ };
2042
+ let touchY = 0;
2043
+ const onTouchStart = (e) => {
2044
+ touchY = e.touches[0]?.clientY ?? 0;
2045
+ };
2046
+ const onTouchMove = (e) => {
2047
+ const delta = (e.touches[0]?.clientY ?? touchY) - touchY;
2048
+ if (canScrollUp() && isUserScrollUp({ kind: "touch", delta, lastTarget: null, scrollTop: el.scrollTop })) takeOver();
2049
+ };
2050
+ const onPointerDown = (e) => {
2051
+ const gutter = el.offsetWidth - el.clientWidth;
2052
+ if (e.target !== el || gutter <= 0) return;
2053
+ if (e.clientX >= el.getBoundingClientRect().left + el.clientWidth) {
2054
+ if (isUserScrollUp({ kind: "gutter", lastTarget: null, scrollTop: el.scrollTop })) takeOver();
2055
+ }
2056
+ };
2057
+ el.addEventListener("scroll", onScroll, { passive: true });
2058
+ el.addEventListener("wheel", onWheel, { passive: true });
2059
+ el.addEventListener("touchstart", onTouchStart, { passive: true });
2060
+ el.addEventListener("touchmove", onTouchMove, { passive: true });
2061
+ el.addEventListener("pointerdown", onPointerDown, { passive: true });
2062
+ return () => {
2063
+ el.removeEventListener("scroll", onScroll);
2064
+ el.removeEventListener("wheel", onWheel);
2065
+ el.removeEventListener("touchstart", onTouchStart);
2066
+ el.removeEventListener("touchmove", onTouchMove);
2067
+ el.removeEventListener("pointerdown", onPointerDown);
2068
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame.current);
2069
+ };
2070
+ }, [scrollRef, attach]);
2071
+ const jumpToBottom = (0, import_react22.useCallback)(() => {
2072
+ attach(true);
2073
+ followEnd();
2074
+ }, [attach, followEnd]);
2075
+ return { atBottom, unread, jumpToBottom };
2076
+ }
1465
2077
  // Annotate the CommonJS export names for ESM import in node:
1466
2078
  0 && (module.exports = {
1467
2079
  BACK_ZONE_PX,
1468
2080
  CLOSE_DISTANCE_PX,
1469
2081
  COARSE_POINTER_QUERY,
1470
2082
  DEFAULT_IGNORE_SELECTOR,
2083
+ DEFAULT_TAIL_THRESHOLD,
2084
+ FAILURE_PATTERNS,
1471
2085
  FOCUSABLE_SELECTOR,
1472
2086
  FORWARD_ZONE_PX,
1473
2087
  MAX_SWIPE_DURATION_MS,
2088
+ NOISE_PATTERNS,
1474
2089
  OPEN_DISTANCE_PX,
1475
2090
  REDUCED_MOTION_QUERY,
1476
2091
  VERTICAL_CANCEL_PX,
1477
2092
  canShareFiles,
1478
2093
  computeKeyboardInset,
1479
2094
  copyText,
2095
+ createAnsiParser,
2096
+ createAnsiStream,
1480
2097
  createDebouncer,
2098
+ createLineBuffer,
1481
2099
  deriveUpdateState,
1482
2100
  detectApple,
1483
2101
  detectPlatform,
2102
+ distanceFromEnd,
2103
+ filterLines,
2104
+ findFailures,
2105
+ firstFailureIndex,
1484
2106
  focusableWithin,
1485
2107
  getPushSupport,
1486
2108
  handleBeforeUnload,
@@ -1491,23 +2113,32 @@ function usePushSubscription(options) {
1491
2113
  isInsideHorizontalScroller,
1492
2114
  isIos,
1493
2115
  isIosSafari,
2116
+ isNearEnd,
2117
+ isNoise,
1494
2118
  isOutside,
1495
2119
  isPrimaryPress,
1496
2120
  isShareCancel,
1497
2121
  isStandalone,
2122
+ isUserScrollUp,
1498
2123
  lockScroll,
1499
2124
  matchHotkey,
1500
2125
  nextDelay,
1501
2126
  nextFocusTarget,
1502
2127
  nextHeight,
2128
+ parseAnsi,
1503
2129
  parseHotkey,
1504
2130
  readText,
1505
2131
  resolveSwipeIntent,
1506
2132
  shareOrCopy,
1507
2133
  shareTextFor,
1508
2134
  shouldResubscribe,
2135
+ shouldStick,
2136
+ splitByMatch,
1509
2137
  startInstallPromptCapture,
2138
+ stripAnsi,
2139
+ tailMeaningful,
1510
2140
  unlockScroll,
2141
+ unreadDelta,
1511
2142
  unregisterAll,
1512
2143
  urlBase64ToUint8Array,
1513
2144
  useAutoGrowTextarea,
@@ -1518,6 +2149,7 @@ function usePushSubscription(options) {
1518
2149
  useDrawerSwipe,
1519
2150
  useEscape,
1520
2151
  useFocusTrap,
2152
+ useFollowTail,
1521
2153
  useHotkey,
1522
2154
  useInstallPrompt,
1523
2155
  useIsStandalone,