sidekick-docker 0.2.4 → 0.2.6

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.
@@ -55711,7 +55711,7 @@ var require_DockerClient = __commonJS({
55711
55711
  const validated = schemas_1.ContainerInspectEnvSchema.parse(info);
55712
55712
  return validated.Config.Env ?? [];
55713
55713
  }
55714
- async *streamLogs(id, opts = {}) {
55714
+ async *streamLogs(id, opts = {}, signal) {
55715
55715
  const container = this.docker.getContainer(id);
55716
55716
  const logOpts = {
55717
55717
  follow: true,
@@ -55732,32 +55732,42 @@ var require_DockerClient = __commonJS({
55732
55732
  return;
55733
55733
  }
55734
55734
  const readable = stream;
55735
+ const destroy = () => readable.destroy?.();
55736
+ if (signal) {
55737
+ signal.addEventListener("abort", destroy, { once: true });
55738
+ }
55735
55739
  const buffer = [];
55736
- for await (const chunk of readable) {
55737
- const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
55738
- buffer.push(data);
55739
- const combined = Buffer.concat(buffer);
55740
- buffer.length = 0;
55741
- let offset = 0;
55742
- while (offset + 8 <= combined.length) {
55743
- const streamType = combined[offset];
55744
- const size = combined.readUInt32BE(offset + 4);
55745
- if (offset + 8 + size > combined.length) {
55746
- buffer.push(combined.subarray(offset));
55740
+ try {
55741
+ for await (const chunk of readable) {
55742
+ if (signal?.aborted)
55747
55743
  break;
55748
- }
55749
- const payload = combined.subarray(offset + 8, offset + 8 + size).toString("utf8");
55750
- const streamName = streamType === 2 ? "stderr" : "stdout";
55751
- for (const line of payload.split("\n")) {
55752
- if (line.trim()) {
55753
- yield parseLogLine(line, streamName);
55744
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
55745
+ buffer.push(data);
55746
+ const combined = Buffer.concat(buffer);
55747
+ buffer.length = 0;
55748
+ let offset = 0;
55749
+ while (offset + 8 <= combined.length) {
55750
+ const streamType = combined[offset];
55751
+ const size = combined.readUInt32BE(offset + 4);
55752
+ if (offset + 8 + size > combined.length) {
55753
+ buffer.push(Buffer.from(combined.subarray(offset)));
55754
+ break;
55755
+ }
55756
+ const payload = combined.subarray(offset + 8, offset + 8 + size).toString("utf8");
55757
+ const streamName = streamType === 2 ? "stderr" : "stdout";
55758
+ for (const line of payload.split("\n")) {
55759
+ if (line.trim()) {
55760
+ yield parseLogLine(line, streamName);
55761
+ }
55754
55762
  }
55763
+ offset += 8 + size;
55764
+ }
55765
+ if (offset < combined.length && buffer.length === 0) {
55766
+ buffer.push(Buffer.from(combined.subarray(offset)));
55755
55767
  }
55756
- offset += 8 + size;
55757
- }
55758
- if (offset < combined.length && buffer.length === 0) {
55759
- buffer.push(combined.subarray(offset));
55760
55768
  }
55769
+ } finally {
55770
+ destroy();
55761
55771
  }
55762
55772
  }
55763
55773
  parseStats(validated, prevCpu, prevSystem) {
@@ -55813,7 +55823,7 @@ var require_DockerClient = __commonJS({
55813
55823
  systemTotal: systemUsage
55814
55824
  };
55815
55825
  }
55816
- async *streamStats(id) {
55826
+ async *streamStats(id, signal) {
55817
55827
  const container = this.docker.getContainer(id);
55818
55828
  let prevCpu = 0;
55819
55829
  let prevSystem = 0;
@@ -55826,21 +55836,33 @@ var require_DockerClient = __commonJS({
55826
55836
  yield first;
55827
55837
  } catch {
55828
55838
  }
55839
+ if (signal?.aborted)
55840
+ return;
55829
55841
  const stream = await container.stats({ stream: true });
55830
- for await (const chunk of stream) {
55831
- const lines = chunk.toString("utf8").split("\n").filter(Boolean);
55832
- for (const line of lines) {
55833
- try {
55834
- const raw = JSON.parse(line);
55835
- const validated = schemas_1.DockerStatsRawSchema.parse(raw);
55836
- const { stats, cpuTotal, systemTotal } = this.parseStats(validated, prevCpu, prevSystem);
55837
- prevCpu = cpuTotal;
55838
- prevSystem = systemTotal;
55839
- yield stats;
55840
- } catch {
55841
- continue;
55842
+ const destroy = () => stream.destroy?.();
55843
+ if (signal) {
55844
+ signal.addEventListener("abort", destroy, { once: true });
55845
+ }
55846
+ try {
55847
+ for await (const chunk of stream) {
55848
+ if (signal?.aborted)
55849
+ break;
55850
+ const lines = chunk.toString("utf8").split("\n").filter(Boolean);
55851
+ for (const line of lines) {
55852
+ try {
55853
+ const raw = JSON.parse(line);
55854
+ const validated = schemas_1.DockerStatsRawSchema.parse(raw);
55855
+ const { stats, cpuTotal, systemTotal } = this.parseStats(validated, prevCpu, prevSystem);
55856
+ prevCpu = cpuTotal;
55857
+ prevSystem = systemTotal;
55858
+ yield stats;
55859
+ } catch {
55860
+ continue;
55861
+ }
55842
55862
  }
55843
55863
  }
55864
+ } finally {
55865
+ destroy();
55844
55866
  }
55845
55867
  }
55846
55868
  async listImages(all = false) {
@@ -56140,7 +56162,7 @@ var require_ComposeClient = __commonJS({
56140
56162
  async ps(project) {
56141
56163
  return this.exec(["-p", project, "ps", "--format", "json"]);
56142
56164
  }
56143
- async *streamLogs(project, service, tail = 100) {
56165
+ async *streamLogs(project, service, tail = 100, signal) {
56144
56166
  const args = ["-p", project, "logs", "--follow", "--tail", String(tail), "--timestamps"];
56145
56167
  if (service)
56146
56168
  args.push(service);
@@ -56173,12 +56195,23 @@ var require_ComposeClient = __commonJS({
56173
56195
  let stdoutBuffer = "";
56174
56196
  let stderrBuffer = "";
56175
56197
  let done = false;
56198
+ const MAX_QUEUE = 1e3;
56176
56199
  const entries = [];
56177
56200
  let resolve = null;
56178
56201
  const push = (entry) => {
56179
56202
  entries.push(entry);
56203
+ if (entries.length > MAX_QUEUE)
56204
+ entries.shift();
56180
56205
  resolve?.();
56181
56206
  };
56207
+ const kill = () => {
56208
+ done = true;
56209
+ resolve?.();
56210
+ proc.kill();
56211
+ };
56212
+ if (signal) {
56213
+ signal.addEventListener("abort", kill, { once: true });
56214
+ }
56182
56215
  proc.stdout.on("data", (data) => {
56183
56216
  stdoutBuffer += data.toString();
56184
56217
  const lines = stdoutBuffer.split("\n");
@@ -56220,6 +56253,9 @@ var require_ComposeClient = __commonJS({
56220
56253
  }
56221
56254
  }
56222
56255
  } finally {
56256
+ proc.stdout.removeAllListeners();
56257
+ proc.stderr.removeAllListeners();
56258
+ proc.removeAllListeners();
56223
56259
  proc.kill();
56224
56260
  }
56225
56261
  }
@@ -56399,6 +56435,14 @@ var require_StatsCollector = __commonJS({
56399
56435
  remove(containerId) {
56400
56436
  this.histories.delete(containerId);
56401
56437
  }
56438
+ /** Remove history entries for containers not in the active set. */
56439
+ prune(activeContainerIds) {
56440
+ for (const id of this.histories.keys()) {
56441
+ if (!activeContainerIds.has(id)) {
56442
+ this.histories.delete(id);
56443
+ }
56444
+ }
56445
+ }
56402
56446
  clear() {
56403
56447
  this.histories.clear();
56404
56448
  }
@@ -56801,10 +56845,17 @@ var require_LogTemplateEngine = __commonJS({
56801
56845
  function isVariable(token) {
56802
56846
  return VARIABLE_PATTERNS.some((p) => p.test(token));
56803
56847
  }
56848
+ var DEFAULT_MAX_GROUPS = 500;
56804
56849
  var LogTemplateEngine2 = class {
56805
56850
  // Group templates by token count for efficient lookup
56806
56851
  groups = /* @__PURE__ */ new Map();
56807
56852
  totalLines = 0;
56853
+ groupCount = 0;
56854
+ droppedGroups = 0;
56855
+ maxGroups;
56856
+ constructor(maxGroups = DEFAULT_MAX_GROUPS) {
56857
+ this.maxGroups = maxGroups;
56858
+ }
56808
56859
  /**
56809
56860
  * Process a log line and update templates.
56810
56861
  */
@@ -56815,7 +56866,12 @@ var require_LogTemplateEngine = __commonJS({
56815
56866
  return;
56816
56867
  const groups = this.groups.get(tokens.length);
56817
56868
  if (!groups) {
56869
+ if (this.groupCount >= this.maxGroups) {
56870
+ this.droppedGroups++;
56871
+ return;
56872
+ }
56818
56873
  this.groups.set(tokens.length, [{ tokens: tokens.map((t) => isVariable(t) ? "<*>" : t), count: 1 }]);
56874
+ this.groupCount++;
56819
56875
  return;
56820
56876
  }
56821
56877
  let bestMatch = null;
@@ -56840,8 +56896,11 @@ var require_LogTemplateEngine = __commonJS({
56840
56896
  bestMatch.tokens[i] = "<*>";
56841
56897
  }
56842
56898
  }
56843
- } else {
56899
+ } else if (this.groupCount < this.maxGroups) {
56844
56900
  groups.push({ tokens: tokens.map((t) => isVariable(t) ? "<*>" : t), count: 1 });
56901
+ this.groupCount++;
56902
+ } else {
56903
+ this.droppedGroups++;
56845
56904
  }
56846
56905
  }
56847
56906
  /**
@@ -56864,9 +56923,18 @@ var require_LogTemplateEngine = __commonJS({
56864
56923
  getTotalLines() {
56865
56924
  return this.totalLines;
56866
56925
  }
56926
+ getDiagnostics() {
56927
+ return {
56928
+ groupCount: this.groupCount,
56929
+ droppedGroups: this.droppedGroups,
56930
+ totalLines: this.totalLines
56931
+ };
56932
+ }
56867
56933
  reset() {
56868
56934
  this.groups.clear();
56869
56935
  this.totalLines = 0;
56936
+ this.groupCount = 0;
56937
+ this.droppedGroups = 0;
56870
56938
  }
56871
56939
  };
56872
56940
  exports2.LogTemplateEngine = LogTemplateEngine2;
@@ -94298,10 +94366,25 @@ var DockerState = class {
94298
94366
  lastRefresh = null;
94299
94367
  daemonConnected = false;
94300
94368
  cachedFileConfig = null;
94369
+ refreshTimer = null;
94301
94370
  constructor(client, cwd2) {
94302
94371
  this.client = client;
94303
94372
  this.cwd = cwd2;
94304
94373
  }
94374
+ /** Debounced refresh — coalesces rapid event-driven refreshes into a single call. */
94375
+ scheduleRefresh() {
94376
+ if (this.refreshTimer) return;
94377
+ this.refreshTimer = setTimeout(() => {
94378
+ this.refreshTimer = null;
94379
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
94380
+ }, 500);
94381
+ }
94382
+ dispose() {
94383
+ if (this.refreshTimer) {
94384
+ clearTimeout(this.refreshTimer);
94385
+ this.refreshTimer = null;
94386
+ }
94387
+ }
94305
94388
  async refresh() {
94306
94389
  try {
94307
94390
  const promises = [
@@ -94320,6 +94403,19 @@ var DockerState = class {
94320
94403
  this.composeProjects = this.composeDetector.detect(containers, fileConfig);
94321
94404
  this.lastRefresh = /* @__PURE__ */ new Date();
94322
94405
  this.daemonConnected = true;
94406
+ const currentContainerIds = new Set(containers.map((c) => c.id));
94407
+ for (const id of this.inspectedEnv.keys()) {
94408
+ if (!currentContainerIds.has(id)) this.inspectedEnv.delete(id);
94409
+ }
94410
+ for (const id of this.containerChanges.keys()) {
94411
+ if (!currentContainerIds.has(id)) this.containerChanges.delete(id);
94412
+ }
94413
+ const currentImageIds = new Set(images.map((i) => i.id));
94414
+ for (const id of this.imageLayers.keys()) {
94415
+ if (!currentImageIds.has(id)) this.imageLayers.delete(id);
94416
+ }
94417
+ const runningIds = new Set(containers.filter((c) => c.state === "running").map((c) => c.id));
94418
+ this.statsCollector.prune(runningIds);
94323
94419
  } catch {
94324
94420
  this.daemonConnected = false;
94325
94421
  }
@@ -94332,7 +94428,7 @@ var DockerState = class {
94332
94428
  case "image":
94333
94429
  case "volume":
94334
94430
  case "network":
94335
- this.refresh().catch((e) => console.debug("refresh failed:", e));
94431
+ this.scheduleRefresh();
94336
94432
  break;
94337
94433
  }
94338
94434
  }
@@ -94347,7 +94443,7 @@ var DockerState = class {
94347
94443
  existing.state = "running";
94348
94444
  existing.status = "Up just now";
94349
94445
  }
94350
- this.refresh().catch((e) => console.debug("refresh failed:", e));
94446
+ this.scheduleRefresh();
94351
94447
  break;
94352
94448
  }
94353
94449
  case "stop":
@@ -94369,13 +94465,15 @@ var DockerState = class {
94369
94465
  case "destroy":
94370
94466
  this.containers = this.containers.filter((c) => c.id !== resourceId);
94371
94467
  this.statsCollector.remove(resourceId);
94468
+ this.inspectedEnv.delete(resourceId);
94469
+ this.containerChanges.delete(resourceId);
94372
94470
  break;
94373
94471
  case "create":
94374
- this.refresh().catch((e) => console.debug("refresh failed:", e));
94472
+ this.scheduleRefresh();
94375
94473
  break;
94376
94474
  default:
94377
94475
  if (name) {
94378
- this.refresh().catch((e) => console.debug("refresh failed:", e));
94476
+ this.scheduleRefresh();
94379
94477
  }
94380
94478
  break;
94381
94479
  }
@@ -94437,19 +94535,19 @@ var DockerState = class {
94437
94535
  }
94438
94536
  getMetrics() {
94439
94537
  return {
94440
- containers: [...this.containers],
94441
- images: [...this.images],
94442
- volumes: [...this.volumes],
94443
- networks: [...this.networks],
94444
- composeProjects: [...this.composeProjects],
94538
+ containers: this.containers,
94539
+ images: this.images,
94540
+ volumes: this.volumes,
94541
+ networks: this.networks,
94542
+ composeProjects: this.composeProjects,
94445
94543
  statsCollector: this.statsCollector,
94446
94544
  inspectedEnv: this.inspectedEnv,
94447
94545
  containerChanges: this.containerChanges,
94448
94546
  imageLayers: this.imageLayers,
94449
- selectedContainerLogs: [...this.selectedLogs],
94450
- selectedComposeLogs: [...this.selectedComposeLogs],
94451
- secondaryContainerLogs: [...this.secondaryLogs],
94452
- secondaryComposeLogs: [...this.secondaryComposeLogs],
94547
+ selectedContainerLogs: this.selectedLogs,
94548
+ selectedComposeLogs: this.selectedComposeLogs,
94549
+ secondaryContainerLogs: this.secondaryLogs,
94550
+ secondaryComposeLogs: this.secondaryComposeLogs,
94453
94551
  lastRefresh: this.lastRefresh,
94454
94552
  daemonConnected: this.daemonConnected,
94455
94553
  logFilterString: "",
@@ -94593,17 +94691,27 @@ function colorizeTokens(message) {
94593
94691
  return colorFn ? colorFn(t.text) : t.text;
94594
94692
  }).join("");
94595
94693
  }
94694
+ var colorizedCache = /* @__PURE__ */ new WeakMap();
94596
94695
  function colorizeLogEntry(entry, filterMatches) {
94696
+ if (!filterMatches) {
94697
+ const cached = colorizedCache.get(entry);
94698
+ if (cached !== void 0) return cached;
94699
+ }
94597
94700
  const ts = entry.timestamp ? (0, import_sidekick_docker_shared2.formatTimestampTime)(entry.timestamp) : "";
94598
94701
  const tsColored = ts ? ansi.dim(ansi.gray(ts)) + " " : "";
94702
+ let result;
94599
94703
  if (entry.stream === "stderr") {
94600
94704
  const msg = filterMatches ? highlightMatches(entry.message, filterMatches, ansi.red) : ansi.red(entry.message);
94601
- return tsColored + msg;
94705
+ result = tsColored + msg;
94706
+ } else if (filterMatches && filterMatches.length > 0) {
94707
+ result = tsColored + highlightMatches(entry.message, filterMatches);
94708
+ } else {
94709
+ result = tsColored + colorizeTokens(entry.message);
94602
94710
  }
94603
- if (filterMatches && filterMatches.length > 0) {
94604
- return tsColored + highlightMatches(entry.message, filterMatches);
94711
+ if (!filterMatches) {
94712
+ colorizedCache.set(entry, result);
94605
94713
  }
94606
- return tsColored + colorizeTokens(entry.message);
94714
+ return result;
94607
94715
  }
94608
94716
  function highlightMatches(text, matches, baseFn) {
94609
94717
  if (matches.length === 0) return baseFn ? baseFn(text) : colorizeTokens(text);
@@ -94765,8 +94873,8 @@ var ContainersPanel = class {
94765
94873
  label: "Logs",
94766
94874
  render: (_item, metrics) => {
94767
94875
  const logs = metrics.selectedContainerLogs;
94768
- if (logs.length === 0) return "No logs available. Select a container to view logs.";
94769
- return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode, metrics.logSeverityCounts).join("\n");
94876
+ if (logs.length === 0) return ["No logs available. Select a container to view logs."];
94877
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode, metrics.logSeverityCounts);
94770
94878
  },
94771
94879
  autoScrollBottom: true
94772
94880
  },
@@ -95065,8 +95173,8 @@ var ServicesPanel = class {
95065
95173
  label: "Logs",
95066
95174
  render: (_item, metrics) => {
95067
95175
  const logs = metrics.selectedComposeLogs;
95068
- if (logs.length === 0) return "No compose logs. Logs will appear when a service produces output.";
95069
- return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode).join("\n");
95176
+ if (logs.length === 0) return ["No compose logs. Logs will appear when a service produces output."];
95177
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode);
95070
95178
  },
95071
95179
  autoScrollBottom: true
95072
95180
  }
@@ -95479,6 +95587,8 @@ var BaseStreamManager = class {
95479
95587
  streamPromise = null;
95480
95588
  reconnect = new import_sidekick_docker_shared6.ReconnectScheduler();
95481
95589
  onChange;
95590
+ streamController = null;
95591
+ streamGeneration = 0;
95482
95592
  constructor(onChange) {
95483
95593
  this.onChange = onChange;
95484
95594
  this.currentId = this.emptyId();
@@ -95492,25 +95602,29 @@ var BaseStreamManager = class {
95492
95602
  if (!this.isValidId(id)) return;
95493
95603
  this.aborted = false;
95494
95604
  this.reconnect.reset();
95605
+ this.streamController = new AbortController();
95606
+ this.streamGeneration++;
95495
95607
  this.onBeforeStream();
95496
- this.streamPromise = this.runStream(id);
95608
+ this.streamPromise = this.runStream(id, this.streamController.signal, this.streamGeneration);
95497
95609
  }
95498
- async runStream(id) {
95610
+ async runStream(id, signal, generation) {
95499
95611
  try {
95500
- for await (const item of this.createStream(id)) {
95501
- if (this.aborted || !this.isSameId(id, this.currentId)) break;
95612
+ for await (const item of this.createStream(id, signal)) {
95613
+ if (signal.aborted || this.aborted || !this.isSameId(id, this.currentId)) break;
95502
95614
  this.processItem(id, item);
95503
95615
  this.onChange();
95504
95616
  }
95505
95617
  this.reconnect.reset();
95506
95618
  } catch (err) {
95619
+ if (signal.aborted) return;
95507
95620
  console.debug(`${this.streamLabel} stream error:`, (0, import_sidekick_docker_shared6.errorMessage)(err));
95508
95621
  }
95509
- if (!this.aborted && this.isSameId(id, this.currentId)) {
95622
+ if (!signal.aborted && !this.aborted && this.isSameId(id, this.currentId) && generation === this.streamGeneration) {
95510
95623
  const scheduled = this.reconnect.schedule(() => {
95511
- if (!this.aborted && this.isSameId(id, this.currentId)) {
95624
+ if (!this.aborted && this.isSameId(id, this.currentId) && generation === this.streamGeneration) {
95625
+ this.streamController = new AbortController();
95512
95626
  this.onBeforeStream();
95513
- this.streamPromise = this.runStream(id);
95627
+ this.streamPromise = this.runStream(id, this.streamController.signal, generation);
95514
95628
  }
95515
95629
  });
95516
95630
  if (!scheduled) {
@@ -95521,6 +95635,8 @@ var BaseStreamManager = class {
95521
95635
  stop() {
95522
95636
  this.aborted = true;
95523
95637
  this.currentId = this.emptyId();
95638
+ this.streamController?.abort();
95639
+ this.streamController = null;
95524
95640
  this.streamPromise = null;
95525
95641
  this.reconnect.clear();
95526
95642
  this.onStop();
@@ -95561,8 +95677,8 @@ var LogStreamManager = class extends BaseStreamManager {
95561
95677
  idLabel(id) {
95562
95678
  return id ?? "(none)";
95563
95679
  }
95564
- createStream(id) {
95565
- return this.client.streamLogs(id, { follow: true, tail: 100 });
95680
+ createStream(id, signal) {
95681
+ return this.client.streamLogs(id, { follow: true, tail: 100 }, signal);
95566
95682
  }
95567
95683
  processItem(_id, entry) {
95568
95684
  this.logs.push(entry);
@@ -95594,6 +95710,9 @@ var LogStreamManager = class extends BaseStreamManager {
95594
95710
  getCurrentContainerId() {
95595
95711
  return this.currentId;
95596
95712
  }
95713
+ getTemplateDiagnostics() {
95714
+ return this.templateEngine.getDiagnostics();
95715
+ }
95597
95716
  };
95598
95717
 
95599
95718
  // src/dashboard/StatsStreamManager.ts
@@ -95618,8 +95737,8 @@ var StatsStreamManager = class extends BaseStreamManager {
95618
95737
  idLabel(id) {
95619
95738
  return id ?? "(none)";
95620
95739
  }
95621
- createStream(id) {
95622
- return this.client.streamStats(id);
95740
+ createStream(id, signal) {
95741
+ return this.client.streamStats(id, signal);
95623
95742
  }
95624
95743
  processItem(id, stats) {
95625
95744
  this.collector.push(id, stats);
@@ -95659,8 +95778,8 @@ var ComposeLogStreamManager = class extends BaseStreamManager {
95659
95778
  idLabel(id) {
95660
95779
  return id.project ?? "(none)";
95661
95780
  }
95662
- createStream(id) {
95663
- return this.composeClient.streamLogs(id.project, id.service ?? void 0);
95781
+ createStream(id, signal) {
95782
+ return this.composeClient.streamLogs(id.project, id.service ?? void 0, 100, signal);
95664
95783
  }
95665
95784
  processItem(_id, entry) {
95666
95785
  this.logs.push(entry);
@@ -97222,13 +97341,13 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97222
97341
  logFilterString: state.logFilterString,
97223
97342
  logFilterMode: state.logFilterMode
97224
97343
  };
97225
- let detailContent = "";
97344
+ let detailLines = [];
97226
97345
  if (selectedItem && detailTabs.length > 0 && tabIdx >= 0) {
97227
- detailContent = detailTabs[tabIdx].render(selectedItem, enrichedMetrics);
97346
+ const result = detailTabs[tabIdx].render(selectedItem, enrichedMetrics);
97347
+ detailLines = Array.isArray(result) ? result : result.split("\n");
97228
97348
  } else if (!selectedItem) {
97229
- detailContent = "(no item selected)";
97349
+ detailLines = ["(no item selected)"];
97230
97350
  }
97231
- const detailLines = detailContent.split("\n");
97232
97351
  const detailViewportHeight = Math.max(1, rows - RESERVED_UI_ROWS);
97233
97352
  const activeTab = detailTabs[tabIdx];
97234
97353
  const shouldAutoScroll = activeTab?.autoScrollBottom ?? false;
@@ -97380,8 +97499,8 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97380
97499
  )
97381
97500
  ] })
97382
97501
  ] }),
97383
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.4" }),
97384
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VersionOverlay, { version: "0.2.4" }),
97502
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.6" }),
97503
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VersionOverlay, { version: "0.2.6" }),
97385
97504
  state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97386
97505
  ExecOverlay,
97387
97506
  {
@@ -97398,7 +97517,7 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97398
97517
  filterString: state.filterString,
97399
97518
  containerCount: metrics.containers.length,
97400
97519
  runningCount,
97401
- version: "0.2.4",
97520
+ version: "0.2.6",
97402
97521
  matchCount: state.filterString ? currentItems.length : void 0,
97403
97522
  totalCount: state.filterString ? totalItemCount : void 0,
97404
97523
  lastRefresh: metrics.lastRefresh,
@@ -97468,6 +97587,7 @@ async function dashboardAction(_opts, cmd) {
97468
97587
  if (renderTimer) return;
97469
97588
  renderTimer = setTimeout(() => {
97470
97589
  renderTimer = null;
97590
+ if (process.stdout.writableLength > process.stdout.writableHighWaterMark) return;
97471
97591
  instance.rerender(
97472
97592
  import_react37.default.createElement(Dashboard, {
97473
97593
  panels,
@@ -97477,7 +97597,7 @@ async function dashboardAction(_opts, cmd) {
97477
97597
  onExecFallback
97478
97598
  })
97479
97599
  );
97480
- }, 100);
97600
+ }, 200);
97481
97601
  }
97482
97602
  const logManager = new LogStreamManager(client, () => {
97483
97603
  if (logFlushTimer) return;
@@ -97660,6 +97780,21 @@ async function dashboardAction(_opts, cmd) {
97660
97780
  const refreshInterval = setInterval(() => {
97661
97781
  state.refresh().then(() => scheduleRender()).catch((e) => console.debug("periodic refresh failed:", e));
97662
97782
  }, 3e4);
97783
+ let debugInterval = null;
97784
+ if (process.env.SIDEKICK_DEBUG_STREAMS === "1") {
97785
+ debugInterval = setInterval(() => {
97786
+ const mem = process.memoryUsage();
97787
+ const heapMB = (mem.heapUsed / 1024 / 1024).toFixed(1);
97788
+ const rssMB = (mem.rss / 1024 / 1024).toFixed(1);
97789
+ const extMB = (mem.external / 1024 / 1024).toFixed(1);
97790
+ const bufMB = (mem.arrayBuffers / 1024 / 1024).toFixed(1);
97791
+ const stdoutBuf = process.stdout.writableLength;
97792
+ const stdoutHWM = process.stdout.writableHighWaterMark;
97793
+ const templateDiag = logManager.getTemplateDiagnostics();
97794
+ const secondaryDiag = secondaryLogManager.getTemplateDiagnostics();
97795
+ console.debug(`[sidekick-debug] heap=${heapMB}MB rss=${rssMB}MB ext=${extMB}MB bufs=${bufMB}MB stdout=${stdoutBuf}/${stdoutHWM} templates=${JSON.stringify(templateDiag)} secondary=${JSON.stringify(secondaryDiag)}`);
97796
+ }, 6e4);
97797
+ }
97663
97798
  const execTriggerRef = { current: null };
97664
97799
  const onExecFallback = (containerId) => {
97665
97800
  instance.clear();
@@ -97759,10 +97894,18 @@ async function dashboardAction(_opts, cmd) {
97759
97894
  clearInterval(refreshInterval);
97760
97895
  } catch {
97761
97896
  }
97897
+ try {
97898
+ if (debugInterval) clearInterval(debugInterval);
97899
+ } catch {
97900
+ }
97762
97901
  try {
97763
97902
  watcher.stop();
97764
97903
  } catch {
97765
97904
  }
97905
+ try {
97906
+ state.dispose();
97907
+ } catch {
97908
+ }
97766
97909
  try {
97767
97910
  client.dispose();
97768
97911
  } catch {
@@ -97839,7 +97982,7 @@ async function logsAction(container, opts) {
97839
97982
 
97840
97983
  // src/cli.ts
97841
97984
  var program2 = new Command();
97842
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.4").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97985
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.6").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97843
97986
  await dashboardAction(_opts, cmd);
97844
97987
  });
97845
97988
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-docker",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Docker management TUI dashboard",
5
5
  "author": "Cesar Andres Lopez <cesarandreslopez@gmail.com>",
6
6
  "repository": {