@develemit/web-hooks 0.4.0 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-11-06
4
+
5
+ - **New optional peer dependency:** `@tanstack/react-virtual` (`^3.13.0`), needed only by apps that import `@develemit/web-hooks/virtual-list`. Every other entry, including the root, works without it.
6
+ - `virtual-list`: `useVirtualLog({ lines, scrollRef, query, estimateSize, overscan, resetKey, filename })` combines `useVirtualList` with `useFollowTail`, a deferred `filterLines` search, incremental `findFailures` with `jumpToFailure` / `nextFailure` / `previousFailure` by index, and `copyText` / `download` over the whole log. Also exports `logToText` and `downloadText`. Budgets measured in WebKit on a 20,765-line, 1.4 MB log: first render about 20 ms with 40 rows in the page, filter keystrokes under 50 ms, about 3 ms of render per batch of appended lines.
7
+ - `virtual-list`: new subpath with `useVirtualList({ count, estimateSize, scrollRef | windowScroll, scrollMargin, overscan = 10, measure = true, getItemKey, initialCount = 30 })` returning `{ items, totalSize, measureRef, scrollToIndex, scrollToOffset, virtualizer }`, plus the pure `measuredOrEstimate`, `shouldRealign`, `targetOffset` and `initialVisibleCount`. Rows are measured by default (a 0 reading falls back to the estimate), it can scroll an element or the window, `scrollToIndex` re-aligns for up to 3 frames after measuring so it lands on unrendered rows, and the server render includes the first rows. `@tanstack/react-virtual` (`^3.13.0`) is an **optional** peer dependency used only by this subpath; the root index does not re-export it.
8
+
3
9
  ## 0.4.0 - 2026-11-06
4
10
 
5
11
  - `terminal-output`: `useFollowTail` detaches on a user scroll up even when output streams every frame. It now detects intent (wheel up, a finger dragging down, a press on the scrollbar gutter, or a scroll event above where the hook last scrolled to) instead of ignoring scroll events in the frame after its own scroll. New pure `isUserScrollUp`.
package/README.md CHANGED
@@ -25,6 +25,7 @@ that every app ends up hand-copying from the last one.
25
25
  | `@develemit/web-hooks/keyboard-inset` | On-screen keyboard height, without the phantom gap in standalone iPhone PWAs |
26
26
  | `@develemit/web-hooks/push` | Web push subscription, with iOS install detection and silent re-subscribe |
27
27
  | `@develemit/web-hooks/terminal-output` | Strip and parse ANSI, buffer streamed lines, search logs and find failures, and follow the tail of a scrolling log |
28
+ | `@develemit/web-hooks/virtual-list` | Virtualize long lists on `@tanstack/react-virtual` with phone-safe defaults (optional peer) |
28
29
 
29
30
  Behavior only. No styling and no components: each app keeps its own look.
30
31
 
@@ -457,3 +458,65 @@ const COLORS = ['text-black', 'text-red', 'text-green', 'text-yellow', 'text-blu
457
458
  </div>
458
459
  ))}
459
460
  ```
461
+
462
+ ### virtual-list
463
+
464
+ `@develemit/web-hooks/virtual-list` wraps `@tanstack/react-virtual` (v3.13+). Install it yourself (`pnpm add @tanstack/react-virtual`); it is an **optional** peer, needed only by apps that import this subpath. The root `@develemit/web-hooks` import does not re-export it, so root imports never need the peer.
465
+
466
+ `useVirtualList({ count, estimateSize, scrollRef? | windowScroll: true, scrollMargin = 0, overscan = 10, measure = true, getItemKey?, initialCount = 30 })` returns `{ items, totalSize, measureRef, scrollToIndex(i, { align }), scrollToOffset, virtualizer }`; `items` are `{ index, key, start, size }`, and `virtualizer` is the raw escape hatch.
467
+
468
+ - Rows are measured by default; a row that reads 0 before layout keeps its estimate, so the list never collapses. Pass `measure: false` if your own maths relies on flat estimates.
469
+ - `windowScroll: true` scrolls with the page (use `scrollMargin` for the list's distance from the page top; item `start` is relative to the list).
470
+ - `scrollToIndex` to a row that isn't rendered yet scrolls to the estimate, then re-aligns after measuring, for up to 3 frames.
471
+ - The server render includes the first `initialCount` rows.
472
+
473
+ ```tsx
474
+ const ref = useRef<HTMLDivElement>(null);
475
+ const { items, totalSize, measureRef } = useVirtualList({ count: lines.length, estimateSize: () => 20, scrollRef: ref });
476
+
477
+ <div ref={ref} className="h-96 overflow-auto">
478
+ <div style={{ height: totalSize, position: 'relative' }}>
479
+ {items.map((it) => (
480
+ <div key={it.key} data-index={it.index} ref={measureRef}
481
+ style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${it.start}px)` }}>
482
+ {lines[it.index]}
483
+ </div>
484
+ ))}
485
+ </div>
486
+ </div>
487
+ ```
488
+
489
+ Each row needs `data-index={it.index}` and `ref={measureRef}`.
490
+
491
+ #### useVirtualLog
492
+
493
+ `useVirtualLog({ lines, scrollRef, query = '', estimateSize = () => 18, overscan = 12, resetKey?, filename = 'log.txt' })` joins `useVirtualList` with the `terminal-output` helpers for a live, searchable log. Pass the lines as strings (strip or keep escape codes; search and failure scans run on what you pass, so give it plain text if you want to search plain text).
494
+
495
+ It returns `rows` (`{ index, lineIndex, key, start, size }`, where `lineIndex` is the row's place in `lines`), `totalSize`, `measureRef`, `isFiltering`, `shownCount`, `atBottom`, `unread`, `jumpToBottom`, `failures`, `failurePosition`, `jumpToFailure(n)`, `nextFailure()`, `previousFailure()`, `text()`, `copyText()` and `download(filename?)`.
496
+
497
+ - Search goes through `useDeferredValue`, so typing stays instant on 20k lines. Rows are the matches; map them back with `lines[row.lineIndex]` for highlighting with `splitByMatch`.
498
+ - It follows the tail and stops when the user scrolls up. While a filter is on, following pauses and `unread` counts matches that arrived since the filter began.
499
+ - `failures` is scanned once per content change, and only on appended rows. `jumpToFailure(n)` scrolls by index (so it works for rows that aren't rendered) and returns `false` if that failure is hidden by the filter.
500
+ - `copyText()` and `download()` use the whole log with escape codes stripped, however few rows are in the page. `logToText(lines, { stripAnsi })` and `downloadText(filename, text)` are exported on their own.
501
+ - `lines` is an array, not a stream: keep your own state (for example `createLineBuffer` + `createAnsiStream`) and pass its rows.
502
+
503
+ ```tsx
504
+ const ref = useRef<HTMLDivElement>(null);
505
+ const [query, setQuery] = useState('');
506
+ const log = useVirtualLog({ lines, scrollRef: ref, query });
507
+
508
+ <div ref={ref} className="h-96 overflow-auto">
509
+ <div style={{ height: log.totalSize, position: 'relative' }}>
510
+ {log.rows.map((row) => (
511
+ <div key={row.key} data-index={row.index} ref={log.measureRef}
512
+ style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${row.start}px)` }}>
513
+ {log.isFiltering
514
+ ? splitByMatch(lines[row.lineIndex] ?? '', query).map((s, j) => (s.match ? <mark key={j}>{s.text}</mark> : <span key={j}>{s.text}</span>))
515
+ : lines[row.lineIndex]}
516
+ </div>
517
+ ))}
518
+ </div>
519
+ </div>
520
+ {!log.atBottom && <button onClick={log.jumpToBottom}>{log.unread} new</button>}
521
+ <button onClick={log.nextFailure}>Next failure</button>
522
+ ```
@@ -241,139 +241,6 @@ function createAnsiParser(options = {}) {
241
241
  };
242
242
  }
243
243
 
244
- // src/terminal-output/line-split.ts
245
- var INCOMPLETE_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*|\][^\x07\x1b]*\x1b?|[ -/]*)$/;
246
- function splitIncompleteEscape(text) {
247
- const match = INCOMPLETE_ESCAPE.exec(text);
248
- if (!match) return { complete: text, held: "" };
249
- return { complete: text.slice(0, match.index), held: match[0] };
250
- }
251
- function splitLines(text) {
252
- const pieces = text.split("\n");
253
- const rest = pieces.pop() ?? "";
254
- const lines = pieces.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
255
- return { lines, rest };
256
- }
257
-
258
- // src/terminal-output/line-buffer.ts
259
- function createLineBuffer({ maxLines }) {
260
- let decoder = new TextDecoder("utf-8");
261
- let lines = [];
262
- let pending = "";
263
- let cursor;
264
- let dropped = 0;
265
- function keep(added) {
266
- lines.push(...added);
267
- const over = Math.max(0, lines.length - maxLines);
268
- if (over > 0) lines.splice(0, over);
269
- dropped += over;
270
- return over;
271
- }
272
- function update(added, over) {
273
- return { lines: added, partial: splitIncompleteEscape(pending).complete, dropped: over };
274
- }
275
- return {
276
- push(chunk, options) {
277
- const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
278
- if (options?.cursor !== void 0) cursor = options.cursor;
279
- const { lines: added, rest } = splitLines(pending + text);
280
- pending = rest;
281
- return update(added, keep(added));
282
- },
283
- flush() {
284
- const tail = pending + decoder.decode();
285
- pending = "";
286
- const added = tail === "" ? [] : [tail];
287
- return update(added, keep(added));
288
- },
289
- reset() {
290
- decoder = new TextDecoder("utf-8");
291
- lines = [];
292
- pending = "";
293
- cursor = void 0;
294
- dropped = 0;
295
- },
296
- get lines() {
297
- return lines;
298
- },
299
- get partial() {
300
- return splitIncompleteEscape(pending).complete;
301
- },
302
- get cursor() {
303
- return cursor;
304
- },
305
- get dropped() {
306
- return dropped;
307
- }
308
- };
309
- }
310
-
311
- // src/terminal-output/ansi-stream.ts
312
- var DEFAULT_TAIL = 50;
313
- var CURSOR_UP = /\x1b\[(\d*)A/g;
314
- function largestCursorUp(text) {
315
- let largest = 0;
316
- for (const m of text.matchAll(CURSOR_UP)) largest = Math.max(largest, Number(m[1] || 1));
317
- return largest;
318
- }
319
- function sameLine(a, b) {
320
- return a !== void 0 && (a === b || JSON.stringify(a) === JSON.stringify(b));
321
- }
322
- function createAnsiStream(options = {}) {
323
- let tail = options.tailLines ?? DEFAULT_TAIL;
324
- let screen = new Screen();
325
- let state = { style: {} };
326
- let frozen = 0;
327
- let shown = [];
328
- let lastPartial = "";
329
- let finished = false;
330
- function render(partial) {
331
- tail = Math.max(tail, largestCursorUp(partial));
332
- screen.lines();
333
- const draft = screen.clone();
334
- feedAnsi(draft, { ...state }, partial, options);
335
- const rows = draft.lines();
336
- const first = rows.findIndex((row, i) => !sameLine(shown[i], row));
337
- const from = first === -1 ? rows.length < shown.length ? rows.length : -1 : first;
338
- shown = rows;
339
- const update = from === -1 ? {} : { replaced: { from: frozen + from, lines: rows.slice(from) } };
340
- const freeze = screen.cursorRow - tail;
341
- if (freeze > 0) {
342
- const dropped = screen.dropTop(freeze);
343
- shown = shown.slice(dropped);
344
- frozen += dropped;
345
- }
346
- return update;
347
- }
348
- return {
349
- push(lines, partial = "") {
350
- if (finished) return {};
351
- for (const line of lines) {
352
- tail = Math.max(tail, largestCursorUp(line));
353
- feedAnsi(screen, state, `${line}
354
- `, options);
355
- }
356
- lastPartial = partial;
357
- return render(partial);
358
- },
359
- finish(partial = lastPartial) {
360
- if (finished) return {};
361
- const update = render(partial);
362
- finished = true;
363
- return update;
364
- },
365
- reset() {
366
- finished = false;
367
- lastPartial = "";
368
- screen = new Screen();
369
- state = { style: {} };
370
- frozen = 0;
371
- shown = [];
372
- tail = options.tailLines ?? DEFAULT_TAIL;
373
- }
374
- };
375
- }
376
-
377
244
  // src/terminal-output/log-helpers.ts
378
245
  var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
379
246
  function queryMatcher(query) {
@@ -593,11 +460,11 @@ function useFollowTail({
593
460
  }
594
461
 
595
462
  export {
463
+ Screen,
596
464
  stripAnsi,
465
+ feedAnsi,
597
466
  parseAnsi,
598
467
  createAnsiParser,
599
- createLineBuffer,
600
- createAnsiStream,
601
468
  filterLines,
602
469
  splitByMatch,
603
470
  FAILURE_PATTERNS,
@@ -0,0 +1,143 @@
1
+ "use client";
2
+ import {
3
+ Screen,
4
+ feedAnsi
5
+ } from "./chunk-IVLRQ23V.js";
6
+
7
+ // src/terminal-output/line-split.ts
8
+ var INCOMPLETE_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*|\][^\x07\x1b]*\x1b?|[ -/]*)$/;
9
+ function splitIncompleteEscape(text) {
10
+ const match = INCOMPLETE_ESCAPE.exec(text);
11
+ if (!match) return { complete: text, held: "" };
12
+ return { complete: text.slice(0, match.index), held: match[0] };
13
+ }
14
+ function splitLines(text) {
15
+ const pieces = text.split("\n");
16
+ const rest = pieces.pop() ?? "";
17
+ const lines = pieces.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
18
+ return { lines, rest };
19
+ }
20
+
21
+ // src/terminal-output/line-buffer.ts
22
+ function createLineBuffer({ maxLines }) {
23
+ let decoder = new TextDecoder("utf-8");
24
+ let lines = [];
25
+ let pending = "";
26
+ let cursor;
27
+ let dropped = 0;
28
+ function keep(added) {
29
+ lines.push(...added);
30
+ const over = Math.max(0, lines.length - maxLines);
31
+ if (over > 0) lines.splice(0, over);
32
+ dropped += over;
33
+ return over;
34
+ }
35
+ function update(added, over) {
36
+ return { lines: added, partial: splitIncompleteEscape(pending).complete, dropped: over };
37
+ }
38
+ return {
39
+ push(chunk, options) {
40
+ const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
41
+ if (options?.cursor !== void 0) cursor = options.cursor;
42
+ const { lines: added, rest } = splitLines(pending + text);
43
+ pending = rest;
44
+ return update(added, keep(added));
45
+ },
46
+ flush() {
47
+ const tail = pending + decoder.decode();
48
+ pending = "";
49
+ const added = tail === "" ? [] : [tail];
50
+ return update(added, keep(added));
51
+ },
52
+ reset() {
53
+ decoder = new TextDecoder("utf-8");
54
+ lines = [];
55
+ pending = "";
56
+ cursor = void 0;
57
+ dropped = 0;
58
+ },
59
+ get lines() {
60
+ return lines;
61
+ },
62
+ get partial() {
63
+ return splitIncompleteEscape(pending).complete;
64
+ },
65
+ get cursor() {
66
+ return cursor;
67
+ },
68
+ get dropped() {
69
+ return dropped;
70
+ }
71
+ };
72
+ }
73
+
74
+ // src/terminal-output/ansi-stream.ts
75
+ var DEFAULT_TAIL = 50;
76
+ var CURSOR_UP = /\x1b\[(\d*)A/g;
77
+ function largestCursorUp(text) {
78
+ let largest = 0;
79
+ for (const m of text.matchAll(CURSOR_UP)) largest = Math.max(largest, Number(m[1] || 1));
80
+ return largest;
81
+ }
82
+ function sameLine(a, b) {
83
+ return a !== void 0 && (a === b || JSON.stringify(a) === JSON.stringify(b));
84
+ }
85
+ function createAnsiStream(options = {}) {
86
+ let tail = options.tailLines ?? DEFAULT_TAIL;
87
+ let screen = new Screen();
88
+ let state = { style: {} };
89
+ let frozen = 0;
90
+ let shown = [];
91
+ let lastPartial = "";
92
+ let finished = false;
93
+ function render(partial) {
94
+ tail = Math.max(tail, largestCursorUp(partial));
95
+ screen.lines();
96
+ const draft = screen.clone();
97
+ feedAnsi(draft, { ...state }, partial, options);
98
+ const rows = draft.lines();
99
+ const first = rows.findIndex((row, i) => !sameLine(shown[i], row));
100
+ const from = first === -1 ? rows.length < shown.length ? rows.length : -1 : first;
101
+ shown = rows;
102
+ const update = from === -1 ? {} : { replaced: { from: frozen + from, lines: rows.slice(from) } };
103
+ const freeze = screen.cursorRow - tail;
104
+ if (freeze > 0) {
105
+ const dropped = screen.dropTop(freeze);
106
+ shown = shown.slice(dropped);
107
+ frozen += dropped;
108
+ }
109
+ return update;
110
+ }
111
+ return {
112
+ push(lines, partial = "") {
113
+ if (finished) return {};
114
+ for (const line of lines) {
115
+ tail = Math.max(tail, largestCursorUp(line));
116
+ feedAnsi(screen, state, `${line}
117
+ `, options);
118
+ }
119
+ lastPartial = partial;
120
+ return render(partial);
121
+ },
122
+ finish(partial = lastPartial) {
123
+ if (finished) return {};
124
+ const update = render(partial);
125
+ finished = true;
126
+ return update;
127
+ },
128
+ reset() {
129
+ finished = false;
130
+ lastPartial = "";
131
+ screen = new Screen();
132
+ state = { style: {} };
133
+ frozen = 0;
134
+ shown = [];
135
+ tail = options.tailLines ?? DEFAULT_TAIL;
136
+ }
137
+ };
138
+ }
139
+
140
+ export {
141
+ createLineBuffer,
142
+ createAnsiStream
143
+ };
package/dist/index.d.cts CHANGED
@@ -17,5 +17,6 @@ export { ServiceWorkerState, UpdateStateInput, UseServiceWorkerOptions, deriveUp
17
17
  export { ShareInput, ShareResult, ShareStatus, canShareFiles, isShareCancel, shareOrCopy, shareTextFor, useShareFeedback } from './share.cjs';
18
18
  export { InsetReading, UseKeyboardInsetOptions, computeKeyboardInset, isEditableElement, useKeyboardInset } from './keyboard-inset.cjs';
19
19
  export { PushEnv, PushPermission, PushSubscriptionState, PushSupport, ResubscribeInput, UsePushSubscriptionOptions, getPushSupport, shouldResubscribe, urlBase64ToUint8Array, usePushSubscription } from './push.cjs';
20
- export { AnsiColor, AnsiLine, AnsiSegment, AnsiStream, AnsiStreamOptions, AnsiStreamUpdate, AnsiStyle, DEFAULT_TAIL_THRESHOLD, FAILURE_PATTERNS, Failure, FailurePattern, FailureTool, LineBuffer, LineBufferOptions, LineBufferUpdate, NOISE_PATTERNS, ParseAnsiOptions, ScrollEventInfo, ScrollEventKind, ScrollMetrics, StreamCursor, TextSegment, UseFollowTailOptions, UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, filterLines, findFailures, firstFailureIndex, isNearEnd, isNoise, isUserScrollUp, parseAnsi, shouldStick, splitByMatch, stripAnsi, tailMeaningful, unreadDelta, useFollowTail } from './terminal-output.cjs';
20
+ export { AnsiColor, AnsiLine, AnsiSegment, AnsiStream, AnsiStreamOptions, AnsiStreamUpdate, AnsiStyle, DEFAULT_TAIL_THRESHOLD, LineBuffer, LineBufferOptions, LineBufferUpdate, ParseAnsiOptions, ScrollEventInfo, ScrollEventKind, ScrollMetrics, StreamCursor, UseFollowTailOptions, UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, isNearEnd, isUserScrollUp, parseAnsi, shouldStick, stripAnsi, unreadDelta, useFollowTail } from './terminal-output.cjs';
21
+ export { F as FAILURE_PATTERNS, a as Failure, b as FailurePattern, c as FailureTool, N as NOISE_PATTERNS, T as TextSegment, f as filterLines, d as findFailures, e as firstFailureIndex, i as isNoise, s as splitByMatch, t as tailMeaningful } from './log-helpers-rkbgYqZW.cjs';
21
22
  import 'react';
package/dist/index.d.ts CHANGED
@@ -17,5 +17,6 @@ export { ServiceWorkerState, UpdateStateInput, UseServiceWorkerOptions, deriveUp
17
17
  export { ShareInput, ShareResult, ShareStatus, canShareFiles, isShareCancel, shareOrCopy, shareTextFor, useShareFeedback } from './share.js';
18
18
  export { InsetReading, UseKeyboardInsetOptions, computeKeyboardInset, isEditableElement, useKeyboardInset } from './keyboard-inset.js';
19
19
  export { PushEnv, PushPermission, PushSubscriptionState, PushSupport, ResubscribeInput, UsePushSubscriptionOptions, getPushSupport, shouldResubscribe, urlBase64ToUint8Array, usePushSubscription } from './push.js';
20
- export { AnsiColor, AnsiLine, AnsiSegment, AnsiStream, AnsiStreamOptions, AnsiStreamUpdate, AnsiStyle, DEFAULT_TAIL_THRESHOLD, FAILURE_PATTERNS, Failure, FailurePattern, FailureTool, LineBuffer, LineBufferOptions, LineBufferUpdate, NOISE_PATTERNS, ParseAnsiOptions, ScrollEventInfo, ScrollEventKind, ScrollMetrics, StreamCursor, TextSegment, UseFollowTailOptions, UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, filterLines, findFailures, firstFailureIndex, isNearEnd, isNoise, isUserScrollUp, parseAnsi, shouldStick, splitByMatch, stripAnsi, tailMeaningful, unreadDelta, useFollowTail } from './terminal-output.js';
20
+ export { AnsiColor, AnsiLine, AnsiSegment, AnsiStream, AnsiStreamOptions, AnsiStreamUpdate, AnsiStyle, DEFAULT_TAIL_THRESHOLD, LineBuffer, LineBufferOptions, LineBufferUpdate, ParseAnsiOptions, ScrollEventInfo, ScrollEventKind, ScrollMetrics, StreamCursor, UseFollowTailOptions, UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, isNearEnd, isUserScrollUp, parseAnsi, shouldStick, stripAnsi, unreadDelta, useFollowTail } from './terminal-output.js';
21
+ export { F as FAILURE_PATTERNS, a as Failure, b as FailurePattern, c as FailureTool, N as NOISE_PATTERNS, T as TextSegment, f as filterLines, d as findFailures, e as firstFailureIndex, i as isNoise, s as splitByMatch, t as tailMeaningful } from './log-helpers-rkbgYqZW.js';
21
22
  import 'react';
package/dist/index.js CHANGED
@@ -22,13 +22,15 @@ import {
22
22
  urlBase64ToUint8Array,
23
23
  usePushSubscription
24
24
  } from "./chunk-R2PFNE3U.js";
25
+ import {
26
+ createAnsiStream,
27
+ createLineBuffer
28
+ } from "./chunk-JTLQQSLO.js";
25
29
  import {
26
30
  DEFAULT_TAIL_THRESHOLD,
27
31
  FAILURE_PATTERNS,
28
32
  NOISE_PATTERNS,
29
33
  createAnsiParser,
30
- createAnsiStream,
31
- createLineBuffer,
32
34
  distanceFromEnd,
33
35
  filterLines,
34
36
  findFailures,
@@ -43,7 +45,7 @@ import {
43
45
  tailMeaningful,
44
46
  unreadDelta,
45
47
  useFollowTail
46
- } from "./chunk-TE2FRIPF.js";
48
+ } from "./chunk-IVLRQ23V.js";
47
49
  import {
48
50
  nextDelay,
49
51
  useVisibilityPolling
@@ -0,0 +1,25 @@
1
+ interface TextSegment {
2
+ text: string;
3
+ match: boolean;
4
+ }
5
+ type FailureTool = 'vitest' | 'nx' | 'tsc' | 'pnpm' | 'npm' | 'docker' | 'generic';
6
+ interface FailurePattern {
7
+ tool: FailureTool;
8
+ kind: string;
9
+ pattern: RegExp;
10
+ }
11
+ interface Failure {
12
+ index: number;
13
+ tool: FailureTool;
14
+ kind: string;
15
+ }
16
+ declare function filterLines(lines: readonly string[], query: string): number[];
17
+ declare function splitByMatch(line: string, query: string): TextSegment[];
18
+ declare const FAILURE_PATTERNS: readonly FailurePattern[];
19
+ declare function findFailures(lines: readonly string[], patterns?: readonly FailurePattern[]): Failure[];
20
+ declare function firstFailureIndex(lines: readonly string[], patterns?: readonly FailurePattern[]): number;
21
+ declare const NOISE_PATTERNS: readonly RegExp[];
22
+ declare function isNoise(line: string, patterns?: readonly RegExp[]): boolean;
23
+ declare function tailMeaningful(lines: readonly string[], n: number, patterns?: readonly RegExp[]): string[];
24
+
25
+ export { FAILURE_PATTERNS as F, NOISE_PATTERNS as N, type TextSegment as T, type Failure as a, type FailurePattern as b, type FailureTool as c, findFailures as d, firstFailureIndex as e, filterLines as f, isNoise as i, splitByMatch as s, tailMeaningful as t };
@@ -0,0 +1,25 @@
1
+ interface TextSegment {
2
+ text: string;
3
+ match: boolean;
4
+ }
5
+ type FailureTool = 'vitest' | 'nx' | 'tsc' | 'pnpm' | 'npm' | 'docker' | 'generic';
6
+ interface FailurePattern {
7
+ tool: FailureTool;
8
+ kind: string;
9
+ pattern: RegExp;
10
+ }
11
+ interface Failure {
12
+ index: number;
13
+ tool: FailureTool;
14
+ kind: string;
15
+ }
16
+ declare function filterLines(lines: readonly string[], query: string): number[];
17
+ declare function splitByMatch(line: string, query: string): TextSegment[];
18
+ declare const FAILURE_PATTERNS: readonly FailurePattern[];
19
+ declare function findFailures(lines: readonly string[], patterns?: readonly FailurePattern[]): Failure[];
20
+ declare function firstFailureIndex(lines: readonly string[], patterns?: readonly FailurePattern[]): number;
21
+ declare const NOISE_PATTERNS: readonly RegExp[];
22
+ declare function isNoise(line: string, patterns?: readonly RegExp[]): boolean;
23
+ declare function tailMeaningful(lines: readonly string[], n: number, patterns?: readonly RegExp[]): string[];
24
+
25
+ export { FAILURE_PATTERNS as F, NOISE_PATTERNS as N, type TextSegment as T, type Failure as a, type FailurePattern as b, type FailureTool as c, findFailures as d, firstFailureIndex as e, filterLines as f, isNoise as i, splitByMatch as s, tailMeaningful as t };
@@ -1,3 +1,4 @@
1
+ export { F as FAILURE_PATTERNS, a as Failure, b as FailurePattern, c as FailureTool, N as NOISE_PATTERNS, T as TextSegment, f as filterLines, d as findFailures, e as firstFailureIndex, i as isNoise, s as splitByMatch, t as tailMeaningful } from './log-helpers-rkbgYqZW.cjs';
1
2
  import { RefObject } from 'react';
2
3
 
3
4
  type AnsiColor = {
@@ -110,30 +111,6 @@ interface AnsiStream {
110
111
  */
111
112
  declare function createAnsiStream(options?: AnsiStreamOptions): AnsiStream;
112
113
 
113
- interface TextSegment {
114
- text: string;
115
- match: boolean;
116
- }
117
- type FailureTool = 'vitest' | 'nx' | 'tsc' | 'pnpm' | 'npm' | 'docker' | 'generic';
118
- interface FailurePattern {
119
- tool: FailureTool;
120
- kind: string;
121
- pattern: RegExp;
122
- }
123
- interface Failure {
124
- index: number;
125
- tool: FailureTool;
126
- kind: string;
127
- }
128
- declare function filterLines(lines: readonly string[], query: string): number[];
129
- declare function splitByMatch(line: string, query: string): TextSegment[];
130
- declare const FAILURE_PATTERNS: readonly FailurePattern[];
131
- declare function findFailures(lines: readonly string[], patterns?: readonly FailurePattern[]): Failure[];
132
- declare function firstFailureIndex(lines: readonly string[], patterns?: readonly FailurePattern[]): number;
133
- declare const NOISE_PATTERNS: readonly RegExp[];
134
- declare function isNoise(line: string, patterns?: readonly RegExp[]): boolean;
135
- declare function tailMeaningful(lines: readonly string[], n: number, patterns?: readonly RegExp[]): string[];
136
-
137
114
  interface ScrollMetrics {
138
115
  scrollTop: number;
139
116
  scrollHeight: number;
@@ -183,4 +160,4 @@ interface UseFollowTailResult {
183
160
  }
184
161
  declare function useFollowTail({ scrollRef, itemCount, scrollToEnd, resetKey, threshold, }: UseFollowTailOptions): UseFollowTailResult;
185
162
 
186
- export { type AnsiColor, type AnsiLine, type AnsiSegment, type AnsiStream, type AnsiStreamOptions, type AnsiStreamUpdate, type AnsiStyle, DEFAULT_TAIL_THRESHOLD, FAILURE_PATTERNS, type Failure, type FailurePattern, type FailureTool, type LineBuffer, type LineBufferOptions, type LineBufferUpdate, NOISE_PATTERNS, type ParseAnsiOptions, type ScrollEventInfo, type ScrollEventKind, type ScrollMetrics, type StreamCursor, type TextSegment, type UseFollowTailOptions, type UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, filterLines, findFailures, firstFailureIndex, isNearEnd, isNoise, isUserScrollUp, parseAnsi, shouldStick, splitByMatch, stripAnsi, tailMeaningful, unreadDelta, useFollowTail };
163
+ export { type AnsiColor, type AnsiLine, type AnsiSegment, type AnsiStream, type AnsiStreamOptions, type AnsiStreamUpdate, type AnsiStyle, DEFAULT_TAIL_THRESHOLD, type LineBuffer, type LineBufferOptions, type LineBufferUpdate, type ParseAnsiOptions, type ScrollEventInfo, type ScrollEventKind, type ScrollMetrics, type StreamCursor, type UseFollowTailOptions, type UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, isNearEnd, isUserScrollUp, parseAnsi, shouldStick, stripAnsi, unreadDelta, useFollowTail };
@@ -1,3 +1,4 @@
1
+ export { F as FAILURE_PATTERNS, a as Failure, b as FailurePattern, c as FailureTool, N as NOISE_PATTERNS, T as TextSegment, f as filterLines, d as findFailures, e as firstFailureIndex, i as isNoise, s as splitByMatch, t as tailMeaningful } from './log-helpers-rkbgYqZW.js';
1
2
  import { RefObject } from 'react';
2
3
 
3
4
  type AnsiColor = {
@@ -110,30 +111,6 @@ interface AnsiStream {
110
111
  */
111
112
  declare function createAnsiStream(options?: AnsiStreamOptions): AnsiStream;
112
113
 
113
- interface TextSegment {
114
- text: string;
115
- match: boolean;
116
- }
117
- type FailureTool = 'vitest' | 'nx' | 'tsc' | 'pnpm' | 'npm' | 'docker' | 'generic';
118
- interface FailurePattern {
119
- tool: FailureTool;
120
- kind: string;
121
- pattern: RegExp;
122
- }
123
- interface Failure {
124
- index: number;
125
- tool: FailureTool;
126
- kind: string;
127
- }
128
- declare function filterLines(lines: readonly string[], query: string): number[];
129
- declare function splitByMatch(line: string, query: string): TextSegment[];
130
- declare const FAILURE_PATTERNS: readonly FailurePattern[];
131
- declare function findFailures(lines: readonly string[], patterns?: readonly FailurePattern[]): Failure[];
132
- declare function firstFailureIndex(lines: readonly string[], patterns?: readonly FailurePattern[]): number;
133
- declare const NOISE_PATTERNS: readonly RegExp[];
134
- declare function isNoise(line: string, patterns?: readonly RegExp[]): boolean;
135
- declare function tailMeaningful(lines: readonly string[], n: number, patterns?: readonly RegExp[]): string[];
136
-
137
114
  interface ScrollMetrics {
138
115
  scrollTop: number;
139
116
  scrollHeight: number;
@@ -183,4 +160,4 @@ interface UseFollowTailResult {
183
160
  }
184
161
  declare function useFollowTail({ scrollRef, itemCount, scrollToEnd, resetKey, threshold, }: UseFollowTailOptions): UseFollowTailResult;
185
162
 
186
- export { type AnsiColor, type AnsiLine, type AnsiSegment, type AnsiStream, type AnsiStreamOptions, type AnsiStreamUpdate, type AnsiStyle, DEFAULT_TAIL_THRESHOLD, FAILURE_PATTERNS, type Failure, type FailurePattern, type FailureTool, type LineBuffer, type LineBufferOptions, type LineBufferUpdate, NOISE_PATTERNS, type ParseAnsiOptions, type ScrollEventInfo, type ScrollEventKind, type ScrollMetrics, type StreamCursor, type TextSegment, type UseFollowTailOptions, type UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, filterLines, findFailures, firstFailureIndex, isNearEnd, isNoise, isUserScrollUp, parseAnsi, shouldStick, splitByMatch, stripAnsi, tailMeaningful, unreadDelta, useFollowTail };
163
+ export { type AnsiColor, type AnsiLine, type AnsiSegment, type AnsiStream, type AnsiStreamOptions, type AnsiStreamUpdate, type AnsiStyle, DEFAULT_TAIL_THRESHOLD, type LineBuffer, type LineBufferOptions, type LineBufferUpdate, type ParseAnsiOptions, type ScrollEventInfo, type ScrollEventKind, type ScrollMetrics, type StreamCursor, type UseFollowTailOptions, type UseFollowTailResult, createAnsiParser, createAnsiStream, createLineBuffer, distanceFromEnd, isNearEnd, isUserScrollUp, parseAnsi, shouldStick, stripAnsi, unreadDelta, useFollowTail };
@@ -1,11 +1,13 @@
1
1
  "use client";
2
+ import {
3
+ createAnsiStream,
4
+ createLineBuffer
5
+ } from "./chunk-JTLQQSLO.js";
2
6
  import {
3
7
  DEFAULT_TAIL_THRESHOLD,
4
8
  FAILURE_PATTERNS,
5
9
  NOISE_PATTERNS,
6
10
  createAnsiParser,
7
- createAnsiStream,
8
- createLineBuffer,
9
11
  distanceFromEnd,
10
12
  filterLines,
11
13
  findFailures,
@@ -20,7 +22,7 @@ import {
20
22
  tailMeaningful,
21
23
  unreadDelta,
22
24
  useFollowTail
23
- } from "./chunk-TE2FRIPF.js";
25
+ } from "./chunk-IVLRQ23V.js";
24
26
  export {
25
27
  DEFAULT_TAIL_THRESHOLD,
26
28
  FAILURE_PATTERNS,