/** Frame lines, incremental patch helpers, smoke compose for tests (preamble; no import). */ function bareTopSafeWrite(ctx, stdout, s) { if (!stdout || typeof stdout.write !== 'function') return try { stdout.write(s) } catch { try { ctx.console?.error?.('baretop: stdout write failed') } catch { /* ignore */ } } } /** * FNV-1a 32-bit for line dirty detection (item 4). * @param {string} s */ function bareTopLineHash32(s) { const str = String(s) let h = 0x811c9dc5 for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i) h = Math.imul(h, 0x01000193) } return h >>> 0 } /** * @param {string} out * @returns {string[]} */ function bareTopFrameToLines(out) { return out.split(/\r\n/) } /** * Join lines to frame string (no trailing requirement). * @param {string[]} lines */ function bareTopLinesToFrame(lines) { return lines.join('\r\n') } /** * Build incremental ANSI patch (CUP + EL + line per change). * @param {string[] | null} prevLines * @param {string[]} nextLines * @param {(row: number, col: number) => string} cup * @param {{ useLineHash?: boolean, lineHashMinLen?: number }} opts * @returns {{ patch: string, nch: number }} */ function bareTopBuildLinePatch(prevLines, nextLines, cup, opts) { const o = opts && typeof opts === 'object' ? opts : {} const useHash = o.useLineHash === true const minLen = Number(o.lineHashMinLen) > 0 ? Number(o.lineHashMinLen) : 200 let patch = '\x1b[?25l' let nch = 0 for (let i = 0; i < nextLines.length; i++) { const prev = prevLines && i < prevLines.length ? prevLines[i] : undefined const next = nextLines[i] let changed = false if (prev === undefined) changed = true else if (useHash && next.length >= minLen) { changed = bareTopLineHash32(prev) !== bareTopLineHash32(next) } else changed = prev !== next if (changed) { nch++ const r = Math.max(1, Math.min(i + 1, 9999)) patch += cup(r, 1) + '\x1b[K' + next + '\r\n' } } patch += '\x1b[?25h' return { patch, nch } } /** * Emit full frame or patch (item 1/10): single write. * @param {Record} ctx * @param {import('stream').Writable} stdout * @param {string} fullOut * @param {{ * prevLines: string[] | null, * rows: number, * cols: number, * incrementalFrameDiff: boolean, * useLineHash: boolean, * fullscreenPanel: boolean, * cup: (r: number, c: number) => string * }} o * @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number }} */ function bareTopEmitFrame(ctx, stdout, fullOut, o) { const nextLines = bareTopFrameToLines(fullOut) let wrotePatch = false let patchLen = 0 if ( o.incrementalFrameDiff && o.prevLines && o.prevLines.length === nextLines.length && nextLines.length > 2 && !o.fullscreenPanel ) { const { patch, nch } = bareTopBuildLinePatch( o.prevLines, nextLines, o.cup, { useLineHash: o.useLineHash, lineHashMinLen: 200 } ) if (nch > 0 && nch < nextLines.length * 0.92) { patchLen = patch.length bareTopSafeWrite(/** @type {Record} */ (ctx), stdout, patch) wrotePatch = true return { nextLines, wrotePatch, patchLen } } } bareTopSafeWrite(/** @type {Record} */ (ctx), stdout, fullOut) return { nextLines, wrotePatch, patchLen } } /** * Minimal deterministic compose for unit tests (item 96) — no TTY, stable output. * Uses bareTopProcessRowsFromTable from preamble (baretop-ui-helpers.js before this file in bundle order). * @param {Record | null} snap * @param {number} cols */ function bareTopComposeSmokeTestFrame(snap, cols) { const c = Math.max(40, cols | 0) const lines = [] lines.push('baretop-compose-smoke cols=' + c) const pt = snap && snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : snap && snap.metricsLive && snap.metricsLive.processTable && typeof snap.metricsLive.processTable === 'object' ? snap.metricsLive.processTable : null const rows = typeof bareTopProcessRowsFromTable === 'function' ? bareTopProcessRowsFromTable(pt) : [] lines.push('process_rows=' + rows.length) if (rows[0] && typeof rows[0] === 'object') { const r = /** @type {Record} */ (rows[0]) lines.push('head_pid=' + String(r.pid ?? '')) } return { lines, meta: { layoutVersion: 'baretop-compose-1', footerRow: lines.length + 1, procBodyStart: null } } }