sidekick-docker 0.1.2 → 0.1.4

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.
@@ -15141,7 +15141,7 @@ var require_load_balancer_child_handler = __commonJS({
15141
15141
  createSubchannel(subchannelAddress, subchannelArgs) {
15142
15142
  return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
15143
15143
  }
15144
- updateState(connectivityState, picker, errorMessage) {
15144
+ updateState(connectivityState, picker, errorMessage5) {
15145
15145
  var _a;
15146
15146
  if (this.calledByPendingChild()) {
15147
15147
  if (connectivityState === connectivity_state_1.ConnectivityState.CONNECTING) {
@@ -15153,7 +15153,7 @@ var require_load_balancer_child_handler = __commonJS({
15153
15153
  } else if (!this.calledByCurrentChild()) {
15154
15154
  return;
15155
15155
  }
15156
- this.parent.channelControlHelper.updateState(connectivityState, picker, errorMessage);
15156
+ this.parent.channelControlHelper.updateState(connectivityState, picker, errorMessage5);
15157
15157
  }
15158
15158
  requestReresolution() {
15159
15159
  var _a;
@@ -15379,11 +15379,11 @@ var require_resolving_load_balancer = __commonJS({
15379
15379
  this.updateResolution();
15380
15380
  }
15381
15381
  },
15382
- updateState: (newState, picker, errorMessage) => {
15382
+ updateState: (newState, picker, errorMessage5) => {
15383
15383
  this.latestChildState = newState;
15384
15384
  this.latestChildPicker = picker;
15385
- this.latestChildErrorMessage = errorMessage;
15386
- this.updateState(newState, picker, errorMessage);
15385
+ this.latestChildErrorMessage = errorMessage5;
15386
+ this.updateState(newState, picker, errorMessage5);
15387
15387
  },
15388
15388
  addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper),
15389
15389
  removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper)
@@ -15447,13 +15447,13 @@ var require_resolving_load_balancer = __commonJS({
15447
15447
  }
15448
15448
  this.backoffTimeout.runOnce();
15449
15449
  }
15450
- updateState(connectivityState, picker, errorMessage) {
15450
+ updateState(connectivityState, picker, errorMessage5) {
15451
15451
  trace((0, uri_parser_1.uriToString)(this.target) + " " + connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[connectivityState]);
15452
15452
  if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) {
15453
15453
  picker = new picker_1.QueuePicker(this, picker);
15454
15454
  }
15455
15455
  this.currentState = connectivityState;
15456
- this.channelControlHelper.updateState(connectivityState, picker, errorMessage);
15456
+ this.channelControlHelper.updateState(connectivityState, picker, errorMessage5);
15457
15457
  }
15458
15458
  handleResolutionFailure(error) {
15459
15459
  if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) {
@@ -28353,13 +28353,13 @@ var require_subchannel = __commonJS({
28353
28353
  * @param newState The state to transition to
28354
28354
  * @returns True if the state changed, false otherwise
28355
28355
  */
28356
- transitionToState(oldStates, newState, errorMessage) {
28356
+ transitionToState(oldStates, newState, errorMessage5) {
28357
28357
  var _a, _b;
28358
28358
  if (oldStates.indexOf(this.connectivityState) === -1) {
28359
28359
  return false;
28360
28360
  }
28361
- if (errorMessage) {
28362
- this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState] + ' with error "' + errorMessage + '"');
28361
+ if (errorMessage5) {
28362
+ this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState] + ' with error "' + errorMessage5 + '"');
28363
28363
  } else {
28364
28364
  this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
28365
28365
  }
@@ -28400,7 +28400,7 @@ var require_subchannel = __commonJS({
28400
28400
  throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
28401
28401
  }
28402
28402
  for (const listener of this.stateListeners) {
28403
- listener(this, previousState, newState, this.keepaliveTime, errorMessage);
28403
+ listener(this, previousState, newState, this.keepaliveTime, errorMessage5);
28404
28404
  }
28405
28405
  return true;
28406
28406
  }
@@ -29969,18 +29969,18 @@ var require_transport = __commonJS({
29969
29969
  setImmediate(() => {
29970
29970
  if (!reportedError) {
29971
29971
  reportedError = true;
29972
- reject(`${errorMessage.trim()} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29972
+ reject(`${errorMessage5.trim()} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29973
29973
  }
29974
29974
  });
29975
29975
  };
29976
29976
  const errorHandler = (error) => {
29977
29977
  var _a2;
29978
29978
  (_a2 = this.session) === null || _a2 === void 0 ? void 0 : _a2.destroy();
29979
- errorMessage = error.message;
29980
- this.trace("connection failed with error " + errorMessage);
29979
+ errorMessage5 = error.message;
29980
+ this.trace("connection failed with error " + errorMessage5);
29981
29981
  if (!reportedError) {
29982
29982
  reportedError = true;
29983
- reject(`${errorMessage} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29983
+ reject(`${errorMessage5} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29984
29984
  }
29985
29985
  };
29986
29986
  const sessionOptions = {
@@ -30001,7 +30001,7 @@ var require_transport = __commonJS({
30001
30001
  const defaultWin = (_h = (_g = (_f = http2.getDefaultSettings) === null || _f === void 0 ? void 0 : _f.call(http2)) === null || _g === void 0 ? void 0 : _g.initialWindowSize) !== null && _h !== void 0 ? _h : 65535;
30002
30002
  const connWin = options["grpc-node.flow_control_window"];
30003
30003
  this.session = session;
30004
- let errorMessage = "Failed to connect";
30004
+ let errorMessage5 = "Failed to connect";
30005
30005
  let reportedError = false;
30006
30006
  session.unref();
30007
30007
  session.once("remoteSettings", () => {
@@ -35224,8 +35224,8 @@ var require_load_balancer_pick_first = __commonJS({
35224
35224
  this.currentState = connectivity_state_1.ConnectivityState.IDLE;
35225
35225
  this.currentSubchannelIndex = 0;
35226
35226
  this.currentPick = null;
35227
- this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime, errorMessage) => {
35228
- this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage);
35227
+ this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime, errorMessage5) => {
35228
+ this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage5);
35229
35229
  };
35230
35230
  this.pickedSubchannelHealthListener = () => this.calculateAndReportNewState();
35231
35231
  this.stickyTransientFailureMode = false;
@@ -35248,26 +35248,26 @@ var require_load_balancer_pick_first = __commonJS({
35248
35248
  var _a;
35249
35249
  if (this.currentPick) {
35250
35250
  if (this.reportHealthStatus && !this.currentPick.isHealthy()) {
35251
- const errorMessage = `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`;
35251
+ const errorMessage5 = `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`;
35252
35252
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35253
- details: errorMessage
35254
- }), errorMessage);
35253
+ details: errorMessage5
35254
+ }), errorMessage5);
35255
35255
  } else {
35256
35256
  this.updateState(connectivity_state_1.ConnectivityState.READY, new PickFirstPicker(this.currentPick), null);
35257
35257
  }
35258
35258
  } else if (((_a = this.latestAddressList) === null || _a === void 0 ? void 0 : _a.length) === 0) {
35259
- const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35259
+ const errorMessage5 = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35260
35260
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35261
- details: errorMessage
35262
- }), errorMessage);
35261
+ details: errorMessage5
35262
+ }), errorMessage5);
35263
35263
  } else if (this.children.length === 0) {
35264
35264
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
35265
35265
  } else {
35266
35266
  if (this.stickyTransientFailureMode) {
35267
- const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35267
+ const errorMessage5 = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35268
35268
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35269
- details: errorMessage
35270
- }), errorMessage);
35269
+ details: errorMessage5
35270
+ }), errorMessage5);
35271
35271
  } else {
35272
35272
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
35273
35273
  }
@@ -35301,7 +35301,7 @@ var require_load_balancer_pick_first = __commonJS({
35301
35301
  this.currentPick = null;
35302
35302
  }
35303
35303
  }
35304
- onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage) {
35304
+ onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage5) {
35305
35305
  var _a;
35306
35306
  if ((_a = this.currentPick) === null || _a === void 0 ? void 0 : _a.realSubchannelEquals(subchannel)) {
35307
35307
  if (newState !== connectivity_state_1.ConnectivityState.READY) {
@@ -35317,8 +35317,8 @@ var require_load_balancer_pick_first = __commonJS({
35317
35317
  }
35318
35318
  if (newState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
35319
35319
  child.hasReportedTransientFailure = true;
35320
- if (errorMessage) {
35321
- this.lastError = errorMessage;
35320
+ if (errorMessage5) {
35321
+ this.lastError = errorMessage5;
35322
35322
  }
35323
35323
  this.maybeEnterStickyTransientFailureMode();
35324
35324
  if (index === this.currentSubchannelIndex) {
@@ -35383,10 +35383,10 @@ var require_load_balancer_pick_first = __commonJS({
35383
35383
  clearTimeout(this.connectionDelayTimeout);
35384
35384
  this.calculateAndReportNewState();
35385
35385
  }
35386
- updateState(newState, picker, errorMessage) {
35386
+ updateState(newState, picker, errorMessage5) {
35387
35387
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
35388
35388
  this.currentState = newState;
35389
- this.channelControlHelper.updateState(newState, picker, errorMessage);
35389
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
35390
35390
  }
35391
35391
  resetSubchannelList() {
35392
35392
  for (const child of this.children) {
@@ -35479,10 +35479,10 @@ var require_load_balancer_pick_first = __commonJS({
35479
35479
  this.resolutionNote = resolutionNote;
35480
35480
  this.latestState = connectivity_state_1.ConnectivityState.IDLE;
35481
35481
  const childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
35482
- updateState: (connectivityState, picker, errorMessage) => {
35482
+ updateState: (connectivityState, picker, errorMessage5) => {
35483
35483
  this.latestState = connectivityState;
35484
35484
  this.latestPicker = picker;
35485
- channelControlHelper.updateState(connectivityState, picker, errorMessage);
35485
+ channelControlHelper.updateState(connectivityState, picker, errorMessage5);
35486
35486
  }
35487
35487
  });
35488
35488
  this.pickFirstBalancer = new PickFirstLoadBalancer(childChannelControlHelper);
@@ -35981,12 +35981,12 @@ var require_load_balancer_round_robin = __commonJS({
35981
35981
  this.updatesPaused = false;
35982
35982
  this.lastError = null;
35983
35983
  this.childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
35984
- updateState: (connectivityState, picker, errorMessage) => {
35984
+ updateState: (connectivityState, picker, errorMessage5) => {
35985
35985
  if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
35986
35986
  this.channelControlHelper.requestReresolution();
35987
35987
  }
35988
- if (errorMessage) {
35989
- this.lastError = errorMessage;
35988
+ if (errorMessage5) {
35989
+ this.lastError = errorMessage5;
35990
35990
  }
35991
35991
  this.calculateAndUpdateState();
35992
35992
  }
@@ -36016,10 +36016,10 @@ var require_load_balancer_round_robin = __commonJS({
36016
36016
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
36017
36017
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
36018
36018
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
36019
- const errorMessage = `round_robin: No connection established. Last error: ${this.lastError}`;
36019
+ const errorMessage5 = `round_robin: No connection established. Last error: ${this.lastError}`;
36020
36020
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
36021
- details: errorMessage
36022
- }), errorMessage);
36021
+ details: errorMessage5
36022
+ }), errorMessage5);
36023
36023
  } else {
36024
36024
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
36025
36025
  }
@@ -36029,7 +36029,7 @@ var require_load_balancer_round_robin = __commonJS({
36029
36029
  }
36030
36030
  }
36031
36031
  }
36032
- updateState(newState, picker, errorMessage) {
36032
+ updateState(newState, picker, errorMessage5) {
36033
36033
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
36034
36034
  if (newState === connectivity_state_1.ConnectivityState.READY) {
36035
36035
  this.currentReadyPicker = picker;
@@ -36037,7 +36037,7 @@ var require_load_balancer_round_robin = __commonJS({
36037
36037
  this.currentReadyPicker = null;
36038
36038
  }
36039
36039
  this.currentState = newState;
36040
- this.channelControlHelper.updateState(newState, picker, errorMessage);
36040
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
36041
36041
  }
36042
36042
  resetSubchannelList() {
36043
36043
  for (const child of this.children) {
@@ -36059,8 +36059,8 @@ var require_load_balancer_round_robin = __commonJS({
36059
36059
  const endpointList = rotateArray(maybeEndpointList.value, startIndex);
36060
36060
  this.resetSubchannelList();
36061
36061
  if (endpointList.length === 0) {
36062
- const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
36063
- this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
36062
+ const errorMessage5 = `No addresses resolved. Resolution note: ${resolutionNote}`;
36063
+ this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage5 }), errorMessage5);
36064
36064
  }
36065
36065
  trace("Connect to endpoint list " + endpointList.map(subchannel_address_1.endpointToString));
36066
36066
  this.updatesPaused = true;
@@ -36347,11 +36347,11 @@ var require_load_balancer_outlier_detection = __commonJS({
36347
36347
  mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.subchannelWrappers.push(subchannelWrapper);
36348
36348
  return subchannelWrapper;
36349
36349
  },
36350
- updateState: (connectivityState, picker, errorMessage) => {
36350
+ updateState: (connectivityState, picker, errorMessage5) => {
36351
36351
  if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
36352
- channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage);
36352
+ channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage5);
36353
36353
  } else {
36354
- channelControlHelper.updateState(connectivityState, picker, errorMessage);
36354
+ channelControlHelper.updateState(connectivityState, picker, errorMessage5);
36355
36355
  }
36356
36356
  }
36357
36357
  }));
@@ -36918,10 +36918,10 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36918
36918
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
36919
36919
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
36920
36920
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
36921
- const errorMessage = `weighted_round_robin: No connection established. Last error: ${this.lastError}`;
36921
+ const errorMessage5 = `weighted_round_robin: No connection established. Last error: ${this.lastError}`;
36922
36922
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
36923
- details: errorMessage
36924
- }), errorMessage);
36923
+ details: errorMessage5
36924
+ }), errorMessage5);
36925
36925
  } else {
36926
36926
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
36927
36927
  }
@@ -36931,10 +36931,10 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36931
36931
  }
36932
36932
  }
36933
36933
  }
36934
- updateState(newState, picker, errorMessage) {
36934
+ updateState(newState, picker, errorMessage5) {
36935
36935
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
36936
36936
  this.currentState = newState;
36937
- this.channelControlHelper.updateState(newState, picker, errorMessage);
36937
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
36938
36938
  }
36939
36939
  updateAddressList(maybeEndpointList, lbConfig, options, resolutionNote) {
36940
36940
  var _a, _b;
@@ -36948,8 +36948,8 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36948
36948
  return true;
36949
36949
  }
36950
36950
  if (maybeEndpointList.value.length === 0) {
36951
- const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
36952
- this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
36951
+ const errorMessage5 = `No addresses resolved. Resolution note: ${resolutionNote}`;
36952
+ this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage5 }), errorMessage5);
36953
36953
  return false;
36954
36954
  }
36955
36955
  trace("Connect to endpoint list " + maybeEndpointList.value.map(subchannel_address_1.endpointToString));
@@ -36964,15 +36964,15 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36964
36964
  if (!entry) {
36965
36965
  entry = {
36966
36966
  child: new load_balancer_pick_first_1.LeafLoadBalancer(endpoint, (0, load_balancer_1.createChildChannelControlHelper)(this.channelControlHelper, {
36967
- updateState: (connectivityState, picker, errorMessage) => {
36967
+ updateState: (connectivityState, picker, errorMessage5) => {
36968
36968
  if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
36969
36969
  this.channelControlHelper.requestReresolution();
36970
36970
  }
36971
36971
  if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
36972
36972
  entry.nonEmptySince = null;
36973
36973
  }
36974
- if (errorMessage) {
36975
- this.lastError = errorMessage;
36974
+ if (errorMessage5) {
36975
+ this.lastError = errorMessage5;
36976
36976
  }
36977
36977
  this.calculateAndUpdateState();
36978
36978
  },
@@ -39740,9 +39740,15 @@ var require_DockerClient = __commonJS({
39740
39740
  const result = await this.docker.pruneNetworks();
39741
39741
  return { networksDeleted: result.NetworksDeleted || [] };
39742
39742
  }
39743
- async *streamEvents(filters) {
39743
+ async *streamEvents(filters, signal) {
39744
39744
  const stream = await this.docker.getEvents({ filters });
39745
+ if (signal) {
39746
+ const onAbort = () => stream.destroy?.();
39747
+ signal.addEventListener("abort", onAbort, { once: true });
39748
+ }
39745
39749
  for await (const chunk of stream) {
39750
+ if (signal?.aborted)
39751
+ break;
39746
39752
  const lines = chunk.toString("utf8").split("\n").filter(Boolean);
39747
39753
  for (const line of lines) {
39748
39754
  let raw;
@@ -40087,7 +40093,7 @@ var require_StatsCollector = __commonJS({
40087
40093
  Object.defineProperty(exports2, "__esModule", { value: true });
40088
40094
  exports2.StatsCollector = void 0;
40089
40095
  var DEFAULT_MAX_SAMPLES = 60;
40090
- var StatsCollector2 = class {
40096
+ var StatsCollector3 = class {
40091
40097
  histories = /* @__PURE__ */ new Map();
40092
40098
  maxSamples;
40093
40099
  constructor(maxSamples = DEFAULT_MAX_SAMPLES) {
@@ -40132,7 +40138,7 @@ var require_StatsCollector = __commonJS({
40132
40138
  this.histories.clear();
40133
40139
  }
40134
40140
  };
40135
- exports2.StatsCollector = StatsCollector2;
40141
+ exports2.StatsCollector = StatsCollector3;
40136
40142
  }
40137
40143
  });
40138
40144
 
@@ -40605,19 +40611,63 @@ var require_LogTemplateEngine = __commonJS({
40605
40611
  }
40606
40612
  });
40607
40613
 
40614
+ // ../sidekick-docker-shared/dist/reconnect.js
40615
+ var require_reconnect = __commonJS({
40616
+ "../sidekick-docker-shared/dist/reconnect.js"(exports2) {
40617
+ "use strict";
40618
+ Object.defineProperty(exports2, "__esModule", { value: true });
40619
+ exports2.ReconnectScheduler = exports2.MAX_RECONNECT_ATTEMPTS = exports2.MAX_RECONNECT_DELAY = exports2.INITIAL_RECONNECT_DELAY = void 0;
40620
+ exports2.INITIAL_RECONNECT_DELAY = 2e3;
40621
+ exports2.MAX_RECONNECT_DELAY = 3e4;
40622
+ exports2.MAX_RECONNECT_ATTEMPTS = 10;
40623
+ var ReconnectScheduler4 = class {
40624
+ timer = null;
40625
+ attempts = 0;
40626
+ delay = exports2.INITIAL_RECONNECT_DELAY;
40627
+ /** Schedule a reconnect callback with exponential backoff. Returns false if max attempts reached. */
40628
+ schedule(callback) {
40629
+ if (this.attempts >= exports2.MAX_RECONNECT_ATTEMPTS) {
40630
+ return false;
40631
+ }
40632
+ this.clear();
40633
+ this.timer = setTimeout(() => {
40634
+ this.timer = null;
40635
+ this.attempts++;
40636
+ this.delay = Math.min(this.delay * 2, exports2.MAX_RECONNECT_DELAY);
40637
+ callback();
40638
+ }, this.delay);
40639
+ return true;
40640
+ }
40641
+ /** Clear any pending reconnect timer. */
40642
+ clear() {
40643
+ if (this.timer) {
40644
+ clearTimeout(this.timer);
40645
+ this.timer = null;
40646
+ }
40647
+ }
40648
+ /** Reset backoff state (call after a successful stream). */
40649
+ reset() {
40650
+ this.attempts = 0;
40651
+ this.delay = exports2.INITIAL_RECONNECT_DELAY;
40652
+ }
40653
+ };
40654
+ exports2.ReconnectScheduler = ReconnectScheduler4;
40655
+ }
40656
+ });
40657
+
40608
40658
  // ../sidekick-docker-shared/dist/events/EventWatcher.js
40609
40659
  var require_EventWatcher = __commonJS({
40610
40660
  "../sidekick-docker-shared/dist/events/EventWatcher.js"(exports2) {
40611
40661
  "use strict";
40612
40662
  Object.defineProperty(exports2, "__esModule", { value: true });
40613
40663
  exports2.EventWatcher = void 0;
40664
+ var reconnect_1 = require_reconnect();
40614
40665
  var EventWatcher2 = class {
40615
40666
  client;
40616
40667
  callbacks;
40617
40668
  running = false;
40618
40669
  abortController = null;
40619
- reconnectDelay = 1e3;
40620
- maxReconnectDelay = 3e4;
40670
+ reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40621
40671
  constructor(client, callbacks) {
40622
40672
  this.client = client;
40623
40673
  this.callbacks = callbacks;
@@ -40626,7 +40676,7 @@ var require_EventWatcher = __commonJS({
40626
40676
  if (this.running)
40627
40677
  return;
40628
40678
  this.running = true;
40629
- this.reconnectDelay = 1e3;
40679
+ this.reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40630
40680
  this.watch();
40631
40681
  }
40632
40682
  stop() {
@@ -40641,12 +40691,12 @@ var require_EventWatcher = __commonJS({
40641
40691
  while (this.running) {
40642
40692
  try {
40643
40693
  this.abortController = new AbortController();
40644
- for await (const event of this.client.streamEvents()) {
40694
+ for await (const event of this.client.streamEvents(void 0, this.abortController.signal)) {
40645
40695
  if (!this.running)
40646
40696
  break;
40647
40697
  this.callbacks.onEvent(event);
40648
40698
  }
40649
- this.reconnectDelay = 1e3;
40699
+ this.reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40650
40700
  } catch (err) {
40651
40701
  if (!this.running)
40652
40702
  break;
@@ -40655,15 +40705,22 @@ var require_EventWatcher = __commonJS({
40655
40705
  if (!this.running)
40656
40706
  break;
40657
40707
  this.callbacks.onReconnect?.();
40658
- await sleep(this.reconnectDelay);
40659
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
40660
- }
40708
+ await this.cancellableSleep(this.reconnectDelay);
40709
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, reconnect_1.MAX_RECONNECT_DELAY);
40710
+ }
40711
+ }
40712
+ /** Sleep that resolves early when stop() is called. */
40713
+ cancellableSleep(ms) {
40714
+ return new Promise((resolve) => {
40715
+ const timer = setTimeout(resolve, ms);
40716
+ this.abortController?.signal.addEventListener("abort", () => {
40717
+ clearTimeout(timer);
40718
+ resolve();
40719
+ }, { once: true });
40720
+ });
40661
40721
  }
40662
40722
  };
40663
40723
  exports2.EventWatcher = EventWatcher2;
40664
- function sleep(ms) {
40665
- return new Promise((resolve) => setTimeout(resolve, ms));
40666
- }
40667
40724
  }
40668
40725
  });
40669
40726
 
@@ -40748,6 +40805,18 @@ var require_formatters = __commonJS({
40748
40805
  }
40749
40806
  });
40750
40807
 
40808
+ // ../sidekick-docker-shared/dist/errors.js
40809
+ var require_errors2 = __commonJS({
40810
+ "../sidekick-docker-shared/dist/errors.js"(exports2) {
40811
+ "use strict";
40812
+ Object.defineProperty(exports2, "__esModule", { value: true });
40813
+ exports2.errorMessage = errorMessage5;
40814
+ function errorMessage5(err) {
40815
+ return err instanceof Error ? err.message : String(err);
40816
+ }
40817
+ }
40818
+ });
40819
+
40751
40820
  // ../sidekick-docker-shared/dist/branding.js
40752
40821
  var require_branding = __commonJS({
40753
40822
  "../sidekick-docker-shared/dist/branding.js"(exports2) {
@@ -41472,7 +41541,7 @@ var require_dist = __commonJS({
41472
41541
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
41473
41542
  };
41474
41543
  Object.defineProperty(exports2, "__esModule", { value: true });
41475
- exports2.getRandomPhrase = exports2.BRAND_COLOR_ANSI_RESET = exports2.BRAND_COLOR_ANSI = exports2.BRAND_COLOR_HEX = exports2.BRAND_TAGLINE = exports2.BRAND_INLINE = exports2.stateColor = exports2.truncate = exports2.stateIcon = exports2.formatPorts = exports2.formatMemory = exports2.formatCpu = exports2.formatBytes = exports2.EventWatcher = exports2.LogTemplateEngine = exports2.LogSeverityTimeSeries = exports2.parseLine = exports2.detectFormat = exports2.detectSeverity = exports2.LogAnalytics = exports2.filterLine = exports2.fuzzyMatch = exports2.exactMatch = exports2.tokenizeLogLine = exports2.StatsCollector = exports2.ComposeFileReader = exports2.ComposeClient = exports2.ComposeDetector = exports2.DockerClient = void 0;
41544
+ exports2.getRandomPhrase = exports2.BRAND_COLOR_ANSI_RESET = exports2.BRAND_COLOR_ANSI = exports2.BRAND_COLOR_HEX = exports2.BRAND_TAGLINE = exports2.BRAND_INLINE = exports2.MAX_LOG_LINES = exports2.errorMessage = exports2.MAX_RECONNECT_ATTEMPTS = exports2.MAX_RECONNECT_DELAY = exports2.INITIAL_RECONNECT_DELAY = exports2.ReconnectScheduler = exports2.stateColor = exports2.truncate = exports2.stateIcon = exports2.formatPorts = exports2.formatMemory = exports2.formatCpu = exports2.formatBytes = exports2.EventWatcher = exports2.LogTemplateEngine = exports2.LogSeverityTimeSeries = exports2.parseLine = exports2.detectFormat = exports2.detectSeverity = exports2.LogAnalytics = exports2.filterLine = exports2.fuzzyMatch = exports2.exactMatch = exports2.tokenizeLogLine = exports2.StatsCollector = exports2.ComposeFileReader = exports2.ComposeClient = exports2.ComposeDetector = exports2.DockerClient = void 0;
41476
41545
  __exportStar(require_types(), exports2);
41477
41546
  var DockerClient_1 = require_DockerClient();
41478
41547
  Object.defineProperty(exports2, "DockerClient", { enumerable: true, get: function() {
@@ -41556,6 +41625,24 @@ var require_dist = __commonJS({
41556
41625
  Object.defineProperty(exports2, "stateColor", { enumerable: true, get: function() {
41557
41626
  return formatters_1.stateColor;
41558
41627
  } });
41628
+ var reconnect_1 = require_reconnect();
41629
+ Object.defineProperty(exports2, "ReconnectScheduler", { enumerable: true, get: function() {
41630
+ return reconnect_1.ReconnectScheduler;
41631
+ } });
41632
+ Object.defineProperty(exports2, "INITIAL_RECONNECT_DELAY", { enumerable: true, get: function() {
41633
+ return reconnect_1.INITIAL_RECONNECT_DELAY;
41634
+ } });
41635
+ Object.defineProperty(exports2, "MAX_RECONNECT_DELAY", { enumerable: true, get: function() {
41636
+ return reconnect_1.MAX_RECONNECT_DELAY;
41637
+ } });
41638
+ Object.defineProperty(exports2, "MAX_RECONNECT_ATTEMPTS", { enumerable: true, get: function() {
41639
+ return reconnect_1.MAX_RECONNECT_ATTEMPTS;
41640
+ } });
41641
+ var errors_1 = require_errors2();
41642
+ Object.defineProperty(exports2, "errorMessage", { enumerable: true, get: function() {
41643
+ return errors_1.errorMessage;
41644
+ } });
41645
+ exports2.MAX_LOG_LINES = 1e3;
41559
41646
  var branding_1 = require_branding();
41560
41647
  Object.defineProperty(exports2, "BRAND_INLINE", { enumerable: true, get: function() {
41561
41648
  return branding_1.BRAND_INLINE;
@@ -77874,10 +77961,33 @@ var {
77874
77961
  } = import_index.default;
77875
77962
 
77876
77963
  // src/commands/dashboard.ts
77877
- var import_react36 = __toESM(require_react(), 1);
77878
- var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
77964
+ var import_react37 = __toESM(require_react(), 1);
77965
+ var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
77879
77966
  import { spawnSync } from "child_process";
77880
77967
 
77968
+ // src/utils/clipboard.ts
77969
+ import { execSync } from "child_process";
77970
+ function copyToClipboard(text) {
77971
+ try {
77972
+ if (process.platform === "darwin") {
77973
+ execSync("pbcopy", { input: text });
77974
+ } else {
77975
+ try {
77976
+ execSync("xclip -selection clipboard", { input: text });
77977
+ } catch {
77978
+ try {
77979
+ execSync("xsel --clipboard --input", { input: text });
77980
+ } catch {
77981
+ execSync("wl-copy", { input: text });
77982
+ }
77983
+ }
77984
+ }
77985
+ return true;
77986
+ } catch {
77987
+ return false;
77988
+ }
77989
+ }
77990
+
77881
77991
  // src/dashboard/DockerState.ts
77882
77992
  var import_sidekick_docker_shared = __toESM(require_dist(), 1);
77883
77993
  var DockerState = class {
@@ -77931,8 +78041,7 @@ var DockerState = class {
77931
78041
  case "image":
77932
78042
  case "volume":
77933
78043
  case "network":
77934
- this.refresh().catch(() => {
77935
- });
78044
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77936
78045
  break;
77937
78046
  }
77938
78047
  }
@@ -77944,11 +78053,10 @@ var DockerState = class {
77944
78053
  case "unpause": {
77945
78054
  const existing = this.containers.find((c) => c.id === resourceId);
77946
78055
  if (existing) {
77947
- existing.state = type === "unpause" ? "running" : "running";
78056
+ existing.state = "running";
77948
78057
  existing.status = "Up just now";
77949
78058
  }
77950
- this.refresh().catch(() => {
77951
- });
78059
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77952
78060
  break;
77953
78061
  }
77954
78062
  case "stop":
@@ -77972,13 +78080,11 @@ var DockerState = class {
77972
78080
  this.statsCollector.remove(resourceId);
77973
78081
  break;
77974
78082
  case "create":
77975
- this.refresh().catch(() => {
77976
- });
78083
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77977
78084
  break;
77978
78085
  default:
77979
78086
  if (name) {
77980
- this.refresh().catch(() => {
77981
- });
78087
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77982
78088
  }
77983
78089
  break;
77984
78090
  }
@@ -77989,7 +78095,7 @@ var DockerState = class {
77989
78095
  }
77990
78096
  appendLog(entry) {
77991
78097
  this.selectedLogs.push(entry);
77992
- if (this.selectedLogs.length > 1e3) {
78098
+ if (this.selectedLogs.length > import_sidekick_docker_shared.MAX_LOG_LINES) {
77993
78099
  this.selectedLogs.shift();
77994
78100
  }
77995
78101
  }
@@ -77998,7 +78104,7 @@ var DockerState = class {
77998
78104
  }
77999
78105
  appendComposeLog(entry) {
78000
78106
  this.selectedComposeLogs.push(entry);
78001
- if (this.selectedComposeLogs.length > 1e3) {
78107
+ if (this.selectedComposeLogs.length > import_sidekick_docker_shared.MAX_LOG_LINES) {
78002
78108
  this.selectedComposeLogs.shift();
78003
78109
  }
78004
78110
  }
@@ -78039,6 +78145,11 @@ var DockerState = class {
78039
78145
  // src/dashboard/panels/ContainersPanel.ts
78040
78146
  var import_sidekick_docker_shared4 = __toESM(require_dist(), 1);
78041
78147
 
78148
+ // src/dashboard/panels/types.ts
78149
+ var defaultOnError = (msg) => {
78150
+ console.debug(msg);
78151
+ };
78152
+
78042
78153
  // src/formatters.ts
78043
78154
  var import_sidekick_docker_shared2 = __toESM(require_dist(), 1);
78044
78155
  var import_sidekick_docker_shared3 = __toESM(require_dist(), 1);
@@ -78226,14 +78337,21 @@ var ContainersPanel = class {
78226
78337
  shortcutKey = 1;
78227
78338
  client;
78228
78339
  onAction;
78340
+ onError;
78229
78341
  onExec;
78230
- constructor(client, onAction) {
78342
+ onCopyLogs;
78343
+ lastMetrics = null;
78344
+ constructor(client, onAction, onError) {
78231
78345
  this.client = client;
78232
78346
  this.onAction = onAction;
78347
+ this.onError = onError ?? defaultOnError;
78233
78348
  }
78234
78349
  setOnExec(handler) {
78235
78350
  this.onExec = handler;
78236
78351
  }
78352
+ setOnCopyLogs(handler) {
78353
+ this.onCopyLogs = handler;
78354
+ }
78237
78355
  detailTabs = [
78238
78356
  {
78239
78357
  label: "Logs",
@@ -78363,10 +78481,11 @@ var ContainersPanel = class {
78363
78481
  }
78364
78482
  ];
78365
78483
  getItems(metrics) {
78484
+ this.lastMetrics = metrics;
78366
78485
  return metrics.containers.map((c) => {
78367
78486
  const uptime = compactUptime(c.status);
78368
78487
  const portHint = c.state === "running" && c.ports.length > 0 ? `:${c.ports[0].hostPort || c.ports[0].containerPort}` : "";
78369
- const namePart = portHint ? `${(0, import_sidekick_docker_shared3.truncate)(c.name, 16)} ${portHint}` : (0, import_sidekick_docker_shared3.truncate)(c.name, 20);
78488
+ const namePart = portHint ? `${(0, import_sidekick_docker_shared3.truncate)(c.name, 34)} ${portHint}` : (0, import_sidekick_docker_shared3.truncate)(c.name, 38);
78370
78489
  return {
78371
78490
  id: c.id,
78372
78491
  label: `${(0, import_sidekick_docker_shared3.stateIcon)(c.state)} ${namePart}`,
@@ -78385,8 +78504,7 @@ var ContainersPanel = class {
78385
78504
  label: "Start",
78386
78505
  handler: (item) => {
78387
78506
  const c = item.data;
78388
- this.client.startContainer(c.id).then(() => this.onAction()).catch(() => {
78389
- });
78507
+ this.client.startContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78390
78508
  },
78391
78509
  condition: (item) => item.data.state !== "running"
78392
78510
  },
@@ -78395,8 +78513,7 @@ var ContainersPanel = class {
78395
78513
  label: "Stop",
78396
78514
  handler: (item) => {
78397
78515
  const c = item.data;
78398
- this.client.stopContainer(c.id).then(() => this.onAction()).catch(() => {
78399
- });
78516
+ this.client.stopContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78400
78517
  },
78401
78518
  condition: (item) => item.data.state === "running"
78402
78519
  },
@@ -78405,8 +78522,7 @@ var ContainersPanel = class {
78405
78522
  label: "Restart",
78406
78523
  handler: (item) => {
78407
78524
  const c = item.data;
78408
- this.client.restartContainer(c.id).then(() => this.onAction()).catch(() => {
78409
- });
78525
+ this.client.restartContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78410
78526
  },
78411
78527
  condition: (item) => item.data.state === "running"
78412
78528
  },
@@ -78417,8 +78533,7 @@ var ContainersPanel = class {
78417
78533
  confirmMessage: "Remove this container?",
78418
78534
  handler: (item) => {
78419
78535
  const c = item.data;
78420
- this.client.removeContainer(c.id, true).then(() => this.onAction()).catch(() => {
78421
- });
78536
+ this.client.removeContainer(c.id, true).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78422
78537
  }
78423
78538
  },
78424
78539
  {
@@ -78429,6 +78544,23 @@ var ContainersPanel = class {
78429
78544
  this.onExec?.(c.id);
78430
78545
  },
78431
78546
  condition: (item) => item.data.state === "running"
78547
+ },
78548
+ {
78549
+ key: "c",
78550
+ label: "Copy Logs",
78551
+ handler: () => {
78552
+ if (!this.lastMetrics || !this.onCopyLogs) return;
78553
+ const logs = this.lastMetrics.selectedContainerLogs;
78554
+ const query = this.lastMetrics.logFilterString;
78555
+ const mode = this.lastMetrics.logFilterMode;
78556
+ let lines;
78557
+ if (query) {
78558
+ lines = logs.filter((l) => (0, import_sidekick_docker_shared4.filterLine)(l.message, query, mode).matched).map((l) => l.message);
78559
+ } else {
78560
+ lines = logs.map((l) => l.message);
78561
+ }
78562
+ this.onCopyLogs(lines.join("\n"));
78563
+ }
78432
78564
  }
78433
78565
  ];
78434
78566
  }
@@ -78439,16 +78571,21 @@ var ContainersPanel = class {
78439
78571
  };
78440
78572
 
78441
78573
  // src/dashboard/panels/ServicesPanel.ts
78574
+ function getProjectName(d) {
78575
+ return d.type === "project" ? d.project.name : d.service.projectName;
78576
+ }
78442
78577
  var ServicesPanel = class {
78443
78578
  id = "services";
78444
78579
  title = "Services";
78445
78580
  shortcutKey = 2;
78446
78581
  composeClient;
78447
78582
  onAction;
78583
+ onError;
78448
78584
  cwd;
78449
- constructor(composeClient, onAction, cwd2) {
78585
+ constructor(composeClient, onAction, cwd2, onError) {
78450
78586
  this.composeClient = composeClient;
78451
78587
  this.onAction = onAction;
78588
+ this.onError = onError ?? defaultOnError;
78452
78589
  this.cwd = cwd2;
78453
78590
  }
78454
78591
  detailTabs = [
@@ -78509,7 +78646,7 @@ var ServicesPanel = class {
78509
78646
  const icon = (0, import_sidekick_docker_shared3.stateIcon)(service.state);
78510
78647
  items.push({
78511
78648
  id: `service:${project.name}:${service.name}`,
78512
- label: ` ${icon} ${(0, import_sidekick_docker_shared3.truncate)(service.name, 18)}`,
78649
+ label: ` ${icon} ${(0, import_sidekick_docker_shared3.truncate)(service.name, 36)}`,
78513
78650
  sortKey: sortKey++,
78514
78651
  data: { type: "service", service },
78515
78652
  iconColor: (0, import_sidekick_docker_shared3.stateColor)(service.state)
@@ -78533,9 +78670,7 @@ var ServicesPanel = class {
78533
78670
  label: "Up",
78534
78671
  handler: (item) => {
78535
78672
  const d = item.data;
78536
- const projectName = d.type === "project" ? d.project.name : d.service.projectName;
78537
- this.composeClient.up(projectName, this.cwd).then(() => this.onAction()).catch(() => {
78538
- });
78673
+ this.composeClient.up(getProjectName(d), this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78539
78674
  },
78540
78675
  condition: (item) => item.data !== null
78541
78676
  },
@@ -78546,9 +78681,7 @@ var ServicesPanel = class {
78546
78681
  confirmMessage: "Bring down this compose project?",
78547
78682
  handler: (item) => {
78548
78683
  const d = item.data;
78549
- const projectName = d.type === "project" ? d.project.name : d.service.projectName;
78550
- this.composeClient.down(projectName, this.cwd).then(() => this.onAction()).catch(() => {
78551
- });
78684
+ this.composeClient.down(getProjectName(d), this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78552
78685
  },
78553
78686
  condition: (item) => item.data !== null
78554
78687
  },
@@ -78558,11 +78691,9 @@ var ServicesPanel = class {
78558
78691
  handler: (item) => {
78559
78692
  const d = item.data;
78560
78693
  if (d.type === "service") {
78561
- this.composeClient.restart(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch(() => {
78562
- });
78694
+ this.composeClient.restart(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78563
78695
  } else {
78564
- this.composeClient.restart(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch(() => {
78565
- });
78696
+ this.composeClient.restart(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78566
78697
  }
78567
78698
  },
78568
78699
  condition: (item) => item.data !== null
@@ -78573,11 +78704,9 @@ var ServicesPanel = class {
78573
78704
  handler: (item) => {
78574
78705
  const d = item.data;
78575
78706
  if (d.type === "service") {
78576
- this.composeClient.stop(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch(() => {
78577
- });
78707
+ this.composeClient.stop(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78578
78708
  } else {
78579
- this.composeClient.stop(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch(() => {
78580
- });
78709
+ this.composeClient.stop(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78581
78710
  }
78582
78711
  },
78583
78712
  condition: (item) => item.data !== null
@@ -78599,9 +78728,11 @@ var ImagesPanel = class {
78599
78728
  shortcutKey = 3;
78600
78729
  client;
78601
78730
  onAction;
78602
- constructor(client, onAction) {
78731
+ onError;
78732
+ constructor(client, onAction, onError) {
78603
78733
  this.client = client;
78604
78734
  this.onAction = onAction;
78735
+ this.onError = onError ?? defaultOnError;
78605
78736
  }
78606
78737
  detailTabs = [
78607
78738
  {
@@ -78624,7 +78755,7 @@ var ImagesPanel = class {
78624
78755
  const icon = img.isDangling ? "\u25CB" : "\u25CF";
78625
78756
  return {
78626
78757
  id: img.id,
78627
- label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(tag, 20)}`,
78758
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(tag, 38)}`,
78628
78759
  sortKey: img.isDangling ? 1 : 0,
78629
78760
  data: img,
78630
78761
  iconColor: img.isDangling ? "gray" : "#2B4C7E",
@@ -78642,8 +78773,7 @@ var ImagesPanel = class {
78642
78773
  confirmMessage: "Remove this image?",
78643
78774
  handler: (item) => {
78644
78775
  const img = item.data;
78645
- this.client.removeImage(img.id).then(() => this.onAction()).catch(() => {
78646
- });
78776
+ this.client.removeImage(img.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78647
78777
  }
78648
78778
  },
78649
78779
  {
@@ -78652,8 +78782,7 @@ var ImagesPanel = class {
78652
78782
  confirm: true,
78653
78783
  confirmMessage: "Prune all dangling images?",
78654
78784
  handler: () => {
78655
- this.client.pruneImages().then(() => this.onAction()).catch(() => {
78656
- });
78785
+ this.client.pruneImages().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78657
78786
  }
78658
78787
  }
78659
78788
  ];
@@ -78671,9 +78800,11 @@ var VolumesPanel = class {
78671
78800
  shortcutKey = 4;
78672
78801
  client;
78673
78802
  onAction;
78674
- constructor(client, onAction) {
78803
+ onError;
78804
+ constructor(client, onAction, onError) {
78675
78805
  this.client = client;
78676
78806
  this.onAction = onAction;
78807
+ this.onError = onError ?? defaultOnError;
78677
78808
  }
78678
78809
  detailTabs = [
78679
78810
  {
@@ -78695,7 +78826,7 @@ var VolumesPanel = class {
78695
78826
  const icon = vol.isInUse ? "\u25CF" : "\u25CB";
78696
78827
  return {
78697
78828
  id: vol.name,
78698
- label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(vol.name, 20)}`,
78829
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(vol.name, 38)}`,
78699
78830
  sortKey: vol.isInUse ? 0 : 1,
78700
78831
  data: vol,
78701
78832
  iconColor: vol.isInUse ? "green" : "gray",
@@ -78713,8 +78844,7 @@ var VolumesPanel = class {
78713
78844
  confirmMessage: "Remove this volume?",
78714
78845
  handler: (item) => {
78715
78846
  const vol = item.data;
78716
- this.client.removeVolume(vol.name).then(() => this.onAction()).catch(() => {
78717
- });
78847
+ this.client.removeVolume(vol.name).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78718
78848
  },
78719
78849
  condition: (item) => !item.data.isInUse
78720
78850
  },
@@ -78724,8 +78854,7 @@ var VolumesPanel = class {
78724
78854
  confirm: true,
78725
78855
  confirmMessage: "Prune all unused volumes?",
78726
78856
  handler: () => {
78727
- this.client.pruneVolumes().then(() => this.onAction()).catch(() => {
78728
- });
78857
+ this.client.pruneVolumes().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78729
78858
  }
78730
78859
  }
78731
78860
  ];
@@ -78743,9 +78872,11 @@ var NetworksPanel = class {
78743
78872
  shortcutKey = 5;
78744
78873
  client;
78745
78874
  onAction;
78746
- constructor(client, onAction) {
78875
+ onError;
78876
+ constructor(client, onAction, onError) {
78747
78877
  this.client = client;
78748
78878
  this.onAction = onAction;
78879
+ this.onError = onError ?? defaultOnError;
78749
78880
  }
78750
78881
  detailTabs = [
78751
78882
  {
@@ -78777,7 +78908,7 @@ var NetworksPanel = class {
78777
78908
  const countLabel = net.containers.length > 0 ? `${net.containers.length}` : "";
78778
78909
  return {
78779
78910
  id: net.id,
78780
- label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(net.name, 20)}`,
78911
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(net.name, 38)}`,
78781
78912
  sortKey: net.isDefault ? 0 : 1,
78782
78913
  data: net,
78783
78914
  iconColor: net.isDefault ? "#2B4C7E" : "gray",
@@ -78795,8 +78926,7 @@ var NetworksPanel = class {
78795
78926
  confirmMessage: "Remove this network?",
78796
78927
  handler: (item) => {
78797
78928
  const net = item.data;
78798
- this.client.removeNetwork(net.id).then(() => this.onAction()).catch(() => {
78799
- });
78929
+ this.client.removeNetwork(net.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78800
78930
  },
78801
78931
  condition: (item) => {
78802
78932
  const net = item.data;
@@ -78809,8 +78939,7 @@ var NetworksPanel = class {
78809
78939
  confirm: true,
78810
78940
  confirmMessage: "Prune all unused networks?",
78811
78941
  handler: () => {
78812
- this.client.pruneNetworks().then(() => this.onAction()).catch(() => {
78813
- });
78942
+ this.client.pruneNetworks().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78814
78943
  }
78815
78944
  }
78816
78945
  ];
@@ -78823,13 +78952,13 @@ var NetworksPanel = class {
78823
78952
 
78824
78953
  // src/dashboard/LogStreamManager.ts
78825
78954
  var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
78826
- var MAX_LOG_LINES = 1e3;
78827
78955
  var LogStreamManager = class {
78828
78956
  client;
78829
78957
  currentContainerId = null;
78830
78958
  logs = [];
78831
78959
  aborted = false;
78832
78960
  streamPromise = null;
78961
+ reconnect = new import_sidekick_docker_shared5.ReconnectScheduler();
78833
78962
  onChange;
78834
78963
  analytics = new import_sidekick_docker_shared5.LogAnalytics();
78835
78964
  timeSeries = new import_sidekick_docker_shared5.LogSeverityTimeSeries();
@@ -78849,6 +78978,7 @@ var LogStreamManager = class {
78849
78978
  this.templateEngine.reset();
78850
78979
  if (!containerId) return;
78851
78980
  this.aborted = false;
78981
+ this.reconnect.reset();
78852
78982
  this.streamPromise = this.streamLogs(containerId);
78853
78983
  }
78854
78984
  async streamLogs(containerId) {
@@ -78862,18 +78992,31 @@ var LogStreamManager = class {
78862
78992
  const severity = this.analytics.push(entry.message);
78863
78993
  this.timeSeries.push(severity);
78864
78994
  this.templateEngine.push(entry.message);
78865
- if (this.logs.length > MAX_LOG_LINES) {
78995
+ if (this.logs.length > import_sidekick_docker_shared5.MAX_LOG_LINES) {
78866
78996
  this.logs.shift();
78867
78997
  }
78868
78998
  this.onChange();
78869
78999
  }
78870
- } catch {
79000
+ this.reconnect.reset();
79001
+ } catch (err) {
79002
+ console.debug("log stream error:", (0, import_sidekick_docker_shared5.errorMessage)(err));
79003
+ }
79004
+ if (!this.aborted && this.currentContainerId === containerId) {
79005
+ const scheduled = this.reconnect.schedule(() => {
79006
+ if (!this.aborted && this.currentContainerId === containerId) {
79007
+ this.streamPromise = this.streamLogs(containerId);
79008
+ }
79009
+ });
79010
+ if (!scheduled) {
79011
+ console.debug(`log stream: gave up reconnecting for ${containerId}`);
79012
+ }
78871
79013
  }
78872
79014
  }
78873
79015
  stop() {
78874
79016
  this.aborted = true;
78875
79017
  this.currentContainerId = null;
78876
79018
  this.streamPromise = null;
79019
+ this.reconnect.clear();
78877
79020
  }
78878
79021
  getLogs() {
78879
79022
  return this.logs;
@@ -78896,12 +79039,14 @@ var LogStreamManager = class {
78896
79039
  };
78897
79040
 
78898
79041
  // src/dashboard/StatsStreamManager.ts
79042
+ var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
78899
79043
  var StatsStreamManager = class {
78900
79044
  client;
78901
79045
  collector;
78902
79046
  currentContainerId = null;
78903
79047
  aborted = false;
78904
79048
  streamPromise = null;
79049
+ reconnect = new import_sidekick_docker_shared6.ReconnectScheduler();
78905
79050
  onChange;
78906
79051
  loadingInterval = null;
78907
79052
  constructor(client, collector, onChange) {
@@ -78916,6 +79061,7 @@ var StatsStreamManager = class {
78916
79061
  this.currentContainerId = containerId;
78917
79062
  if (!containerId) return;
78918
79063
  this.aborted = false;
79064
+ this.reconnect.reset();
78919
79065
  this.loadingInterval = setInterval(() => this.onChange(), 200);
78920
79066
  this.streamPromise = this.streamStats(containerId);
78921
79067
  }
@@ -78933,12 +79079,26 @@ var StatsStreamManager = class {
78933
79079
  this.clearLoadingInterval();
78934
79080
  this.onChange();
78935
79081
  }
78936
- } catch {
79082
+ this.reconnect.reset();
79083
+ } catch (err) {
79084
+ console.debug("stats stream error:", (0, import_sidekick_docker_shared6.errorMessage)(err));
79085
+ }
79086
+ if (!this.aborted && this.currentContainerId === containerId) {
79087
+ const scheduled = this.reconnect.schedule(() => {
79088
+ if (!this.aborted && this.currentContainerId === containerId) {
79089
+ this.loadingInterval = setInterval(() => this.onChange(), 200);
79090
+ this.streamPromise = this.streamStats(containerId);
79091
+ }
79092
+ });
79093
+ if (!scheduled) {
79094
+ console.debug(`stats stream: gave up reconnecting for ${containerId}`);
79095
+ }
78937
79096
  }
78938
79097
  }
78939
79098
  stop() {
78940
79099
  this.aborted = true;
78941
79100
  this.clearLoadingInterval();
79101
+ this.reconnect.clear();
78942
79102
  this.currentContainerId = null;
78943
79103
  this.streamPromise = null;
78944
79104
  }
@@ -78950,18 +79110,18 @@ var StatsStreamManager = class {
78950
79110
  }
78951
79111
  dispose() {
78952
79112
  this.stop();
78953
- this.clearLoadingInterval();
78954
79113
  }
78955
79114
  };
78956
79115
 
78957
79116
  // src/dashboard/ComposeLogStreamManager.ts
78958
- var MAX_LOG_LINES2 = 1e3;
79117
+ var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
78959
79118
  var ComposeLogStreamManager = class {
78960
79119
  composeClient;
78961
79120
  currentProject = null;
78962
79121
  currentService = null;
78963
79122
  logs = [];
78964
79123
  aborted = false;
79124
+ reconnect = new import_sidekick_docker_shared7.ReconnectScheduler();
78965
79125
  onChange;
78966
79126
  constructor(composeClient, onChange) {
78967
79127
  this.composeClient = composeClient;
@@ -78975,6 +79135,7 @@ var ComposeLogStreamManager = class {
78975
79135
  this.logs = [];
78976
79136
  if (!project) return;
78977
79137
  this.aborted = false;
79138
+ this.reconnect.reset();
78978
79139
  this.streamLogs(project, service);
78979
79140
  }
78980
79141
  async streamLogs(project, service) {
@@ -78982,18 +79143,31 @@ var ComposeLogStreamManager = class {
78982
79143
  for await (const entry of this.composeClient.streamLogs(project, service ?? void 0)) {
78983
79144
  if (this.aborted || this.currentProject !== project) break;
78984
79145
  this.logs.push(entry);
78985
- if (this.logs.length > MAX_LOG_LINES2) {
79146
+ if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
78986
79147
  this.logs.shift();
78987
79148
  }
78988
79149
  this.onChange();
78989
79150
  }
78990
- } catch {
79151
+ this.reconnect.reset();
79152
+ } catch (err) {
79153
+ console.debug("compose log stream error:", (0, import_sidekick_docker_shared7.errorMessage)(err));
79154
+ }
79155
+ if (!this.aborted && this.currentProject === project) {
79156
+ const scheduled = this.reconnect.schedule(() => {
79157
+ if (!this.aborted && this.currentProject === project) {
79158
+ this.streamLogs(project, service);
79159
+ }
79160
+ });
79161
+ if (!scheduled) {
79162
+ console.debug(`compose log stream: gave up reconnecting for ${project}`);
79163
+ }
78991
79164
  }
78992
79165
  }
78993
79166
  stop() {
78994
79167
  this.aborted = true;
78995
79168
  this.currentProject = null;
78996
79169
  this.currentService = null;
79170
+ this.reconnect.clear();
78997
79171
  }
78998
79172
  getLogs() {
78999
79173
  return this.logs;
@@ -79004,7 +79178,7 @@ var ComposeLogStreamManager = class {
79004
79178
  };
79005
79179
 
79006
79180
  // src/dashboard/ink/Dashboard.tsx
79007
- var import_react35 = __toESM(require_react(), 1);
79181
+ var import_react36 = __toESM(require_react(), 1);
79008
79182
  await init_build2();
79009
79183
 
79010
79184
  // src/dashboard/ink/useTerminalSize.ts
@@ -79077,6 +79251,325 @@ function useWindowedScroll({ totalItems, viewportHeight }) {
79077
79251
  };
79078
79252
  }
79079
79253
 
79254
+ // src/dashboard/ink/useKeyboardHandler.ts
79255
+ await init_build2();
79256
+ function executeAction(action, item, dispatch, addToast) {
79257
+ if (action.confirm) {
79258
+ dispatch({ type: "SET_CONFIRM", action: () => {
79259
+ action.handler(item);
79260
+ addToast(action.label, "info");
79261
+ }, message: action.confirmMessage || "Are you sure?" });
79262
+ } else {
79263
+ action.handler(item);
79264
+ addToast(action.label, "info");
79265
+ }
79266
+ }
79267
+ function handleFilterInput(input, key, opts) {
79268
+ const { currentValue, setAction, clearToast, dispatch, addToast, onTab } = opts;
79269
+ if (key.escape) {
79270
+ if (currentValue && clearToast) addToast(clearToast, "info");
79271
+ dispatch({ type: setAction, value: "" });
79272
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79273
+ return;
79274
+ }
79275
+ if (key.return) {
79276
+ if (setAction === "SET_FILTER" && currentValue) addToast(`Filter: "${currentValue}"`, "info");
79277
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79278
+ return;
79279
+ }
79280
+ if (key.tab && onTab) {
79281
+ onTab();
79282
+ return;
79283
+ }
79284
+ if (key.backspace || key.delete) {
79285
+ dispatch({ type: setAction, value: currentValue.slice(0, -1) });
79286
+ return;
79287
+ }
79288
+ if (input && !key.ctrl && !key.meta) {
79289
+ dispatch({ type: setAction, value: currentValue + input });
79290
+ }
79291
+ }
79292
+ function useKeyboardHandler(ctx) {
79293
+ const { exit } = use_app_default();
79294
+ const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, rotatePhrase } = ctx;
79295
+ use_input_default((input, key) => {
79296
+ if (state.overlay === "exec") return;
79297
+ rotatePhrase();
79298
+ if (input === "q" || key.ctrl && input === "c") {
79299
+ if (state.overlay) {
79300
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79301
+ return;
79302
+ }
79303
+ exit();
79304
+ return;
79305
+ }
79306
+ if (state.overlay === "confirm") {
79307
+ if (input === "y" || input === "Y") {
79308
+ state.confirmAction?.();
79309
+ dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79310
+ return;
79311
+ }
79312
+ if (input === "n" || input === "N" || key.escape) {
79313
+ dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79314
+ return;
79315
+ }
79316
+ return;
79317
+ }
79318
+ if (state.overlay === "filter") {
79319
+ handleFilterInput(input, key, {
79320
+ currentValue: state.filterString,
79321
+ setAction: "SET_FILTER",
79322
+ clearToast: "Filter cleared",
79323
+ dispatch,
79324
+ addToast
79325
+ });
79326
+ return;
79327
+ }
79328
+ if (state.overlay === "log-filter") {
79329
+ handleFilterInput(input, key, {
79330
+ currentValue: state.logFilterString,
79331
+ setAction: "SET_LOG_FILTER",
79332
+ clearToast: "Log filter cleared",
79333
+ dispatch,
79334
+ addToast,
79335
+ onTab: () => dispatch({ type: "TOGGLE_LOG_FILTER_MODE" })
79336
+ });
79337
+ return;
79338
+ }
79339
+ if (state.overlay === "context-menu") {
79340
+ if (key.escape) {
79341
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79342
+ return;
79343
+ }
79344
+ if (input === "j" || key.downArrow) {
79345
+ dispatch({ type: "CONTEXT_MENU_NAV", delta: 1, itemCount: contextActions.length });
79346
+ return;
79347
+ }
79348
+ if (input === "k" || key.upArrow) {
79349
+ dispatch({ type: "CONTEXT_MENU_NAV", delta: -1, itemCount: contextActions.length });
79350
+ return;
79351
+ }
79352
+ if (key.return) {
79353
+ const action = contextActions[state.contextMenuIndex];
79354
+ if (action && selectedItem) {
79355
+ executeAction(action, selectedItem, dispatch, addToast);
79356
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79357
+ }
79358
+ return;
79359
+ }
79360
+ const match = contextActions.find((a) => a.key === input);
79361
+ if (match && selectedItem) {
79362
+ executeAction(match, selectedItem, dispatch, addToast);
79363
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79364
+ }
79365
+ return;
79366
+ }
79367
+ if (state.overlay === "help") {
79368
+ if (key.escape || input === "?") {
79369
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79370
+ }
79371
+ return;
79372
+ }
79373
+ if (state.overlay === "version") {
79374
+ if (key.escape || input === "V") {
79375
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79376
+ }
79377
+ return;
79378
+ }
79379
+ if (key.escape) {
79380
+ if (state.filterString) {
79381
+ dispatch({ type: "SET_FILTER", value: "" });
79382
+ return;
79383
+ }
79384
+ if (state.focusTarget === "detail") {
79385
+ dispatch({ type: "SET_FOCUS", target: "side" });
79386
+ return;
79387
+ }
79388
+ return;
79389
+ }
79390
+ if (input === "?") {
79391
+ dispatch({ type: "SET_OVERLAY", overlay: "help" });
79392
+ return;
79393
+ }
79394
+ if (input === "V") {
79395
+ dispatch({ type: "SET_OVERLAY", overlay: "version" });
79396
+ return;
79397
+ }
79398
+ const num = parseInt(input, 10);
79399
+ if (num >= 1 && num <= panels.length) {
79400
+ panels[state.activePanelIndex]?.onDeactivate?.();
79401
+ dispatch({ type: "SWITCH_PANEL", index: num - 1 });
79402
+ panels[num - 1]?.onActivate?.();
79403
+ return;
79404
+ }
79405
+ if (key.tab) {
79406
+ dispatch({ type: "TOGGLE_FOCUS" });
79407
+ return;
79408
+ }
79409
+ if (input === "z") {
79410
+ dispatch({ type: "CYCLE_LAYOUT" });
79411
+ const nextMode = state.layoutMode === "normal" ? "Wide" : state.layoutMode === "wide" ? "Expanded" : "Normal";
79412
+ addToast(`Layout: ${nextMode}`, "info");
79413
+ return;
79414
+ }
79415
+ if (input === "/") {
79416
+ dispatch({ type: "SET_OVERLAY", overlay: "filter" });
79417
+ return;
79418
+ }
79419
+ if (input === "f") {
79420
+ if (panel.id === "containers" && tabIdx === 0) {
79421
+ dispatch({ type: "SET_OVERLAY", overlay: "log-filter" });
79422
+ return;
79423
+ }
79424
+ }
79425
+ if (input === "x") {
79426
+ if (selectedItem && panelActions.length > 0) {
79427
+ dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
79428
+ }
79429
+ return;
79430
+ }
79431
+ if (input === "[") {
79432
+ dispatch({ type: "CYCLE_DETAIL_TAB", direction: -1, tabCount: detailTabs.length });
79433
+ return;
79434
+ }
79435
+ if (input === "]") {
79436
+ dispatch({ type: "CYCLE_DETAIL_TAB", direction: 1, tabCount: detailTabs.length });
79437
+ return;
79438
+ }
79439
+ if (state.focusTarget === "side") {
79440
+ if (input === "j" || key.downArrow) {
79441
+ if (clampedSelection < currentItems.length - 1) {
79442
+ dispatch({ type: "SELECT_ITEM", index: clampedSelection + 1 });
79443
+ sideScroll.selectNext();
79444
+ }
79445
+ return;
79446
+ }
79447
+ if (input === "k" || key.upArrow) {
79448
+ if (clampedSelection > 0) {
79449
+ dispatch({ type: "SELECT_ITEM", index: clampedSelection - 1 });
79450
+ sideScroll.selectPrev();
79451
+ }
79452
+ return;
79453
+ }
79454
+ if (input === "g") {
79455
+ dispatch({ type: "SELECT_ITEM", index: 0 });
79456
+ sideScroll.selectFirst();
79457
+ return;
79458
+ }
79459
+ if (input === "G") {
79460
+ dispatch({ type: "SELECT_ITEM", index: Math.max(0, currentItems.length - 1) });
79461
+ sideScroll.selectLast();
79462
+ return;
79463
+ }
79464
+ if (key.return) {
79465
+ dispatch({ type: "SET_FOCUS", target: "detail" });
79466
+ return;
79467
+ }
79468
+ }
79469
+ if (state.focusTarget === "detail") {
79470
+ if (input === "j" || key.downArrow) {
79471
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79472
+ return;
79473
+ }
79474
+ if (input === "k" || key.upArrow) {
79475
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta: -1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79476
+ return;
79477
+ }
79478
+ if (input === "h" || key.leftArrow) {
79479
+ dispatch({ type: "SET_FOCUS", target: "side" });
79480
+ return;
79481
+ }
79482
+ if (input === "g") {
79483
+ dispatch({ type: "SCROLL_DETAIL", offset: 0 });
79484
+ return;
79485
+ }
79486
+ if (input === "G") {
79487
+ dispatch({ type: "SCROLL_DETAIL", offset: Math.max(0, detailLines.length - detailViewportHeight) });
79488
+ return;
79489
+ }
79490
+ }
79491
+ if (selectedItem) {
79492
+ const actionMatch = panelActions.find((a) => a.key === input && (!a.condition || a.condition(selectedItem)));
79493
+ if (actionMatch) {
79494
+ executeAction(actionMatch, selectedItem, dispatch, addToast);
79495
+ }
79496
+ }
79497
+ });
79498
+ }
79499
+
79500
+ // src/dashboard/ink/useMouseHandler.ts
79501
+ var import_react31 = __toESM(require_react(), 1);
79502
+ function useMouseHandler(ctx) {
79503
+ const { state, dispatch, panels, panelCounts, currentItems, clampedSelection, sideWidth, sideScroll, detailLines, detailViewportHeight, detailTabs, rows, rotatePhrase } = ctx;
79504
+ return (0, import_react31.useCallback)((event) => {
79505
+ rotatePhrase();
79506
+ if (state.overlay === "filter") return;
79507
+ if (state.overlay) {
79508
+ if (event.type === "click") {
79509
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79510
+ }
79511
+ return;
79512
+ }
79513
+ const { x, y } = event;
79514
+ if (event.type === "scroll") {
79515
+ if (x < sideWidth && sideWidth > 0) {
79516
+ const delta = event.scrollDirection === "down" ? 3 : -3;
79517
+ dispatch({ type: "SCROLL_SIDE", delta, itemCount: currentItems.length });
79518
+ const newIdx = Math.max(0, Math.min(clampedSelection + delta, currentItems.length - 1));
79519
+ sideScroll.setSelected(newIdx);
79520
+ } else {
79521
+ const delta = event.scrollDirection === "down" ? 3 : -3;
79522
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79523
+ }
79524
+ return;
79525
+ }
79526
+ if (event.type !== "click" || event.button !== "left") return;
79527
+ if (y === 0) {
79528
+ let col = 0;
79529
+ for (let i = 0; i < panels.length; i++) {
79530
+ const count = panelCounts[i];
79531
+ let countLen = 0;
79532
+ if (count) {
79533
+ countLen = count.running !== void 0 ? ` ${count.running}/${count.total}`.length : ` ${count.total}`.length;
79534
+ }
79535
+ const tabWidth = String(panels[i].shortcutKey).length + panels[i].title.length + 3 + countLen + 1;
79536
+ if (x >= col && x < col + tabWidth) {
79537
+ panels[state.activePanelIndex]?.onDeactivate?.();
79538
+ dispatch({ type: "SWITCH_PANEL", index: i });
79539
+ panels[i]?.onActivate?.();
79540
+ return;
79541
+ }
79542
+ col += tabWidth;
79543
+ }
79544
+ return;
79545
+ }
79546
+ if (y >= rows - 1) return;
79547
+ if (x < sideWidth && sideWidth > 0) {
79548
+ dispatch({ type: "SET_FOCUS", target: "side" });
79549
+ const hasScrollUp = sideScroll.scrollOffset > 0;
79550
+ const itemRow = y - 2 - (hasScrollUp ? 1 : 0);
79551
+ const itemIndex = sideScroll.scrollOffset + itemRow;
79552
+ if (itemIndex >= 0 && itemIndex < currentItems.length) {
79553
+ dispatch({ type: "SELECT_ITEM", index: itemIndex });
79554
+ sideScroll.setSelected(itemIndex);
79555
+ }
79556
+ } else {
79557
+ dispatch({ type: "SET_FOCUS", target: "detail" });
79558
+ if (y === 1 && detailTabs.length > 1) {
79559
+ let col = sideWidth;
79560
+ for (let i = 0; i < detailTabs.length; i++) {
79561
+ const tabWidth = detailTabs[i].label.length + 3;
79562
+ if (x >= col && x < col + tabWidth) {
79563
+ dispatch({ type: "SET_DETAIL_TAB", index: i });
79564
+ return;
79565
+ }
79566
+ col += tabWidth;
79567
+ }
79568
+ }
79569
+ }
79570
+ }, [state.overlay, state.activePanelIndex, sideWidth, currentItems.length, clampedSelection, sideScroll, detailLines.length, detailViewportHeight, panels, panelCounts, detailTabs, rows, rotatePhrase, dispatch]);
79571
+ }
79572
+
79080
79573
  // src/dashboard/ink/TabBar.tsx
79081
79574
  await init_build2();
79082
79575
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
@@ -79086,8 +79579,14 @@ function TabBar({ panels, activeIndex, layoutMode, phrase, panelCounts }) {
79086
79579
  const isActive = i === activeIndex;
79087
79580
  const count = panelCounts?.[i];
79088
79581
  let countStr = "";
79582
+ let countColor;
79089
79583
  if (count) {
79090
79584
  countStr = count.running !== void 0 ? ` ${count.running}/${count.total}` : ` ${count.total}`;
79585
+ if (count.running !== void 0) {
79586
+ if (count.running === count.total && count.total > 0) countColor = "green";
79587
+ else if (count.running > 0) countColor = "yellow";
79588
+ else countColor = void 0;
79589
+ }
79091
79590
  }
79092
79591
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginRight: 1, children: [
79093
79592
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -79102,10 +79601,10 @@ function TabBar({ panels, activeIndex, layoutMode, phrase, panelCounts }) {
79102
79601
  countStr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
79103
79602
  Text,
79104
79603
  {
79105
- color: isActive ? "#2B4C7E" : "gray",
79604
+ color: isActive ? countColor || "#2B4C7E" : countColor || "gray",
79106
79605
  bold: isActive,
79107
79606
  inverse: isActive,
79108
- dimColor: !isActive,
79607
+ dimColor: !isActive && !countColor,
79109
79608
  children: `${countStr} `
79110
79609
  }
79111
79610
  ),
@@ -79121,7 +79620,7 @@ function TabBar({ panels, activeIndex, layoutMode, phrase, panelCounts }) {
79121
79620
  ] }, panel.id);
79122
79621
  }),
79123
79622
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, marginLeft: 1, children: phrase && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", wrap: "truncate", children: phrase }) }),
79124
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: layoutMode === "expanded" ? "#2B4C7E" : "gray", children: `z: ${layoutMode === "expanded" ? "Expanded" : "Normal"} \u25B8` })
79623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: layoutMode !== "normal" ? "#2B4C7E" : "gray", children: `z: ${layoutMode === "expanded" ? "Expanded" : layoutMode === "wide" ? "Wide" : "Normal"} \u25B8` })
79125
79624
  ] });
79126
79625
  }
79127
79626
 
@@ -79165,11 +79664,15 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
79165
79664
  const rightLabel = item.rightLabel || "";
79166
79665
  const rightLen = rightLabel.length;
79167
79666
  const leftWidth = innerWidth - rightLen - (rightLen ? 1 : 0);
79168
- const leftText = `${prefix}${icon}${rest}`;
79169
- const paddedLeft = leftText.length < leftWidth ? leftText + " ".repeat(leftWidth - leftText.length) : leftText.substring(0, leftWidth);
79170
79667
  if (isSelected && focused) {
79171
- const fullLine = rightLen ? ` ${paddedLeft} ${rightLabel}` : ` ${paddedLeft}`;
79172
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", bold: true, inverse: true, wrap: "truncate", children: fullLine.padEnd(innerWidth) }) }, item.id);
79668
+ const restText = rest.length < leftWidth - prefix.length - 1 ? rest + " ".repeat(leftWidth - prefix.length - 1 - rest.length) : rest.substring(0, leftWidth - prefix.length - 1);
79669
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
79670
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, wrap: "truncate", children: ` ${prefix}` }),
79671
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: item.iconColor || "#2B4C7E", bold: true, inverse: true, children: icon }),
79672
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, wrap: "truncate", children: restText }),
79673
+ rightLen > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, children: ` ${rightLabel}` }),
79674
+ !rightLen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, children: " " })
79675
+ ] }, item.id);
79173
79676
  }
79174
79677
  const iconColor = item.iconColor || (isSelected ? "#2B4C7E" : "white");
79175
79678
  const textColor = isSelected ? "#2B4C7E" : "white";
@@ -79181,13 +79684,17 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
79181
79684
  ] }, item.id);
79182
79685
  }),
79183
79686
  hasScrollDown && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` \u25BC (${belowCount} more)` }),
79184
- items.length === 0 && filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
79185
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` No matches for "${filterString}"` }),
79186
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: " Press Esc to clear" })
79687
+ items.length === 0 && filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, children: [
79688
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: ` \u2717 No matches for "${filterString}"` }),
79689
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", dimColor: true, children: " Press Esc to clear filter" })
79187
79690
  ] }),
79188
- items.length === 0 && !filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
79189
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` No ${panelTitle.toLowerCase()} found` }),
79190
- panelId && EMPTY_HINTS[panelId]?.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` ${hint}` }, i))
79691
+ items.length === 0 && !filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, children: [
79692
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` \u2500 No ${panelTitle.toLowerCase()} found` }),
79693
+ panelId && EMPTY_HINTS[panelId] && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
79694
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "" }),
79695
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", dimColor: true, children: ` ${EMPTY_HINTS[panelId][0]}` }),
79696
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, children: ` ${EMPTY_HINTS[panelId][1]}` })
79697
+ ] })
79191
79698
  ] })
79192
79699
  ] });
79193
79700
  }
@@ -79197,7 +79704,7 @@ await init_build2();
79197
79704
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
79198
79705
  function DetailTabBar({ tabs, activeIndex }) {
79199
79706
  if (tabs.length <= 1) {
79200
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, {});
79707
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: tabs.length === 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", dimColor: true, children: ` ${tabs[0].label}` }) });
79201
79708
  }
79202
79709
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
79203
79710
  tabs.map((tab2, i) => {
@@ -79213,7 +79720,7 @@ function DetailTabBar({ tabs, activeIndex }) {
79213
79720
  ) }, tab2.label);
79214
79721
  }),
79215
79722
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1 }),
79216
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", children: "[ ] switch" })
79723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", dimColor: true, children: "[/] cycle tabs" })
79217
79724
  ] });
79218
79725
  }
79219
79726
 
@@ -79235,9 +79742,9 @@ function DetailPane({ content, scrollOffset, viewportHeight, focused }) {
79235
79742
  }
79236
79743
 
79237
79744
  // src/dashboard/ink/StatusBar.tsx
79238
- var import_react31 = __toESM(require_react(), 1);
79745
+ var import_react32 = __toESM(require_react(), 1);
79239
79746
  await init_build2();
79240
- var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
79747
+ var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
79241
79748
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
79242
79749
  function formatAgo(date) {
79243
79750
  const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
@@ -79246,72 +79753,89 @@ function formatAgo(date) {
79246
79753
  const mins = Math.floor(secs / 60);
79247
79754
  return { text: `${mins}m ago`, stale: mins >= 1 };
79248
79755
  }
79249
- function StatusBar({ daemonConnected, focusTarget, panelHints, panelActionHints, filterString, containerCount, runningCount, version: version2, matchCount, totalCount, lastRefresh }) {
79250
- const [, setTick] = (0, import_react31.useState)(0);
79251
- (0, import_react31.useEffect)(() => {
79756
+ var SEP2 = "\u2502";
79757
+ function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterString, containerCount, runningCount, version: version2, matchCount, totalCount, lastRefresh }) {
79758
+ const [, setTick] = (0, import_react32.useState)(0);
79759
+ (0, import_react32.useEffect)(() => {
79252
79760
  const timer = setInterval(() => setTick((t) => t + 1), 5e3);
79253
79761
  return () => clearInterval(timer);
79254
79762
  }, []);
79255
79763
  const ago = lastRefresh ? formatAgo(lastRefresh) : null;
79256
79764
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
79257
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` ${import_sidekick_docker_shared6.BRAND_INLINE}` }),
79258
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: ` ${import_sidekick_docker_shared6.BRAND_TAGLINE} v${version2}` }),
79259
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: " " }),
79765
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared8.BRAND_INLINE}` }),
79766
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared8.BRAND_TAGLINE} v${version2}` }),
79767
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
79260
79768
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
79261
- ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", children: ` \u21BB ${ago.text}` }),
79262
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: " " }),
79263
- panelActionHints ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: `${panelActionHints} ` }) : null,
79264
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "gray", children: [
79265
- panelHints,
79266
- focusTarget === "side" ? "j/k nav Tab focus " : "j/k scroll Tab focus ",
79267
- "/ filter ? help q quit"
79769
+ ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
79770
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
79771
+ panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
79772
+ panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react32.default.Fragment, { children: [
79773
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }),
79774
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
79775
+ ] }, hint.key)),
79776
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
79268
79777
  ] }),
79269
- filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: ` Filter: "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` (${matchCount} of ${totalCount})` : ""}` }) : null
79778
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
79779
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
79780
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
79781
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ? q" }),
79782
+ filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
79270
79783
  ] });
79271
79784
  }
79272
79785
 
79273
79786
  // src/dashboard/ink/HelpOverlay.tsx
79274
79787
  await init_build2();
79275
- var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
79788
+ var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
79276
79789
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
79277
79790
  var GLOBAL_BINDINGS = [
79278
79791
  { key: "1-5", label: "Switch panel" },
79279
79792
  { key: "j/k", label: "Navigate / scroll" },
79280
- { key: "g/G", label: "First / last item" },
79281
- { key: "Tab", label: "Toggle focus (side/detail)" },
79282
- { key: "[/]", label: "Prev / next detail tab" },
79283
- { key: "z", label: "Cycle layout mode" },
79793
+ { key: "g/G", label: "Jump to first / last" },
79794
+ { key: "Tab", label: "Toggle focus" },
79795
+ { key: "[/]", label: "Cycle detail tabs" },
79796
+ { key: "z", label: "Cycle layout (Normal/Wide/Expanded)" },
79284
79797
  { key: "/", label: "Filter items" },
79285
- { key: "x", label: "Context menu (actions)" },
79798
+ { key: "x", label: "Actions menu" },
79286
79799
  { key: "V", label: "Version info" },
79287
- { key: "?", label: "Toggle help" },
79800
+ { key: "?", label: "This help" },
79288
79801
  { key: "q", label: "Quit" }
79289
79802
  ];
79803
+ function KeyBadge({ k }) {
79804
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
79805
+ }
79290
79806
  function HelpOverlay({ panels, activePanelIndex, version: version2 }) {
79291
79807
  const panel = panels[activePanelIndex];
79292
79808
  const actions = panel.getActions();
79293
79809
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
79294
79810
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79295
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `${import_sidekick_docker_shared7.BRAND_INLINE} ${import_sidekick_docker_shared7.BRAND_TAGLINE}` }),
79296
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: ` v${version2}` })
79811
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE} ${import_sidekick_docker_shared9.BRAND_TAGLINE}` }),
79812
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
79813
+ ] }),
79814
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79815
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79816
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
79817
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
79297
79818
  ] }),
79298
79819
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79299
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "Navigation" }),
79300
- GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
79301
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "#2B4C7E", children: ` ${b.key.padEnd(8)}` }),
79302
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: b.label })
79820
+ GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79821
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: b.key }) }),
79822
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: b.label })
79303
79823
  ] }, b.key)),
79304
79824
  actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
79305
79825
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79306
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `${panel.title} Actions` }),
79307
- actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
79308
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "#2B4C7E", children: ` ${a.key.padEnd(8)}` }),
79309
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: a.label }),
79310
- a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", children: " (requires confirmation)" })
79826
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79827
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
79828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
79829
+ ] }),
79830
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79831
+ actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79832
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: a.key }) }),
79833
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
79834
+ a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
79311
79835
  ] }, a.key))
79312
79836
  ] }),
79313
79837
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79314
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: "Press ? or Esc to close" })
79838
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
79315
79839
  ] });
79316
79840
  }
79317
79841
 
@@ -79321,8 +79845,9 @@ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
79321
79845
  function FilterOverlay({ filterString, matchCount, totalCount, panelTitle }) {
79322
79846
  const countInfo = matchCount !== void 0 && totalCount !== void 0 && panelTitle ? ` ${matchCount} of ${totalCount} ${panelTitle.toLowerCase()}` : "";
79323
79847
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
79324
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "gray", color: "white", children: ` / ${filterString}\u2588 ` }),
79325
- countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", children: countInfo })
79848
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
79849
+ countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
79850
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
79326
79851
  ] });
79327
79852
  }
79328
79853
 
@@ -79338,23 +79863,37 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
79338
79863
  marginLeft: 2,
79339
79864
  flexDirection: "column",
79340
79865
  borderStyle: "single",
79341
- borderColor: "cyan",
79866
+ borderColor: "#2B4C7E",
79342
79867
  paddingX: 1,
79343
79868
  children: [
79344
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "cyan", children: "Actions" }),
79869
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
79345
79870
  actions.map((action, i) => {
79346
79871
  const isSelected = i === selectedIndex;
79347
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
79348
- Text,
79349
- {
79350
- color: isSelected ? "#2B4C7E" : "white",
79351
- bold: isSelected,
79352
- inverse: isSelected,
79353
- children: ` ${action.key} ${action.label} `
79354
- }
79355
- ) }, action.key);
79872
+ const isDanger = !!action.confirm;
79873
+ const color = isSelected ? isDanger ? "red" : "#2B4C7E" : isDanger ? "red" : "white";
79874
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { children: [
79875
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
79876
+ Text,
79877
+ {
79878
+ color,
79879
+ bold: isSelected,
79880
+ inverse: isSelected,
79881
+ children: ` ${action.key} `
79882
+ }
79883
+ ),
79884
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
79885
+ Text,
79886
+ {
79887
+ color,
79888
+ bold: isSelected,
79889
+ inverse: isSelected,
79890
+ children: `${action.label}${isDanger ? " \u26A0" : ""} `
79891
+ }
79892
+ )
79893
+ ] }, action.key);
79356
79894
  }),
79357
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", children: "Esc to close" })
79895
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: "" }),
79896
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
79358
79897
  ]
79359
79898
  }
79360
79899
  );
@@ -79373,16 +79912,22 @@ function ConfirmOverlay({ message }) {
79373
79912
  flexDirection: "column",
79374
79913
  borderStyle: "double",
79375
79914
  borderColor: "red",
79376
- paddingX: 1,
79915
+ paddingX: 2,
79916
+ paddingY: 1,
79377
79917
  children: [
79378
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "red", children: " Confirm " }),
79918
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
79919
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "red", bold: true, children: "\u26A0 " }),
79920
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "red", children: "Confirm Action" })
79921
+ ] }),
79922
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
79379
79923
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: ` ${message}` }),
79924
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " This cannot be undone." }),
79380
79925
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
79381
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
79382
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "green", children: " y " }),
79383
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "Yes " }),
79384
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "red", children: " n " }),
79385
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "No" })
79926
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
79927
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "green", color: "white", bold: true, children: " y Yes " }),
79928
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
79929
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
79930
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
79386
79931
  ] })
79387
79932
  ]
79388
79933
  }
@@ -79390,7 +79935,7 @@ function ConfirmOverlay({ message }) {
79390
79935
  }
79391
79936
 
79392
79937
  // src/dashboard/ink/ToastNotification.tsx
79393
- var import_react32 = __toESM(require_react(), 1);
79938
+ var import_react33 = __toESM(require_react(), 1);
79394
79939
  await init_build2();
79395
79940
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
79396
79941
  var SEVERITY_COLORS2 = {
@@ -79400,27 +79945,43 @@ var SEVERITY_COLORS2 = {
79400
79945
  };
79401
79946
  var SPINNER_FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827";
79402
79947
  function ToastNotification({ toast }) {
79403
- const [frame, setFrame] = (0, import_react32.useState)(0);
79404
- (0, import_react32.useEffect)(() => {
79948
+ const [frame, setFrame] = (0, import_react33.useState)(0);
79949
+ (0, import_react33.useEffect)(() => {
79405
79950
  if (toast.severity !== "info") return;
79406
79951
  const timer = setInterval(() => {
79407
79952
  setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
79408
79953
  }, 100);
79409
79954
  return () => clearInterval(timer);
79410
79955
  }, [toast.id, toast.severity]);
79411
- const spinner = toast.severity === "info" ? `${SPINNER_FRAMES[frame]} ` : "";
79412
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: SEVERITY_COLORS2[toast.severity] || "white", children: ` ${spinner}${toast.message} ` }) });
79956
+ const SEVERITY_ICONS = {
79957
+ error: "\u2717",
79958
+ // ✗
79959
+ warning: "\u26A0",
79960
+ // ⚠
79961
+ info: SPINNER_FRAMES[frame]
79962
+ };
79963
+ const icon = SEVERITY_ICONS[toast.severity] || "";
79964
+ const textColor = toast.severity === "warning" ? "black" : "white";
79965
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: SEVERITY_COLORS2[toast.severity] || "white", color: textColor, bold: true, children: ` ${icon} ${toast.message} ` }) });
79413
79966
  }
79414
79967
 
79415
79968
  // src/dashboard/ink/TooSmallOverlay.tsx
79416
79969
  await init_build2();
79970
+ var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
79417
79971
  var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
79418
79972
  function TooSmallOverlay({ columns, rows }) {
79973
+ const needWidth = Math.max(0, 60 - columns);
79974
+ const needHeight = Math.max(0, 15 - rows);
79975
+ const hints = [];
79976
+ if (needWidth > 0) hints.push(`${needWidth} col${needWidth > 1 ? "s" : ""} wider`);
79977
+ if (needHeight > 0) hints.push(`${needHeight} row${needHeight > 1 ? "s" : ""} taller`);
79419
79978
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
79979
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE}` }),
79980
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
79420
79981
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
79421
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `Current: ${columns}x${rows}` }),
79422
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: "Minimum: 60x15" }),
79423
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: "Please resize your terminal." })
79982
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
79983
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
79984
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
79424
79985
  ] });
79425
79986
  }
79426
79987
 
@@ -79509,7 +80070,7 @@ function parseMouseEvent(data) {
79509
80070
  }
79510
80071
 
79511
80072
  // src/dashboard/ink/mouse/MouseProvider.tsx
79512
- var import_react33 = __toESM(require_react(), 1);
80073
+ var import_react34 = __toESM(require_react(), 1);
79513
80074
  await init_build2();
79514
80075
  var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
79515
80076
  function InputSink() {
@@ -79518,7 +80079,7 @@ function InputSink() {
79518
80079
  return null;
79519
80080
  }
79520
80081
  function MouseProvider({ onMouse, children }) {
79521
- (0, import_react33.useEffect)(() => {
80082
+ (0, import_react34.useEffect)(() => {
79522
80083
  enableMouse();
79523
80084
  const handler = (data) => {
79524
80085
  const event = parseMouseEvent(data);
@@ -79547,34 +80108,34 @@ var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
79547
80108
  function LogFilterOverlay({ filterString, filterMode }) {
79548
80109
  const modeLabel = filterMode === "exact" ? "exact" : "fuzzy";
79549
80110
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
79550
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { backgroundColor: "blue", color: "white", children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
80111
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
79551
80112
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
79552
80113
  ] });
79553
80114
  }
79554
80115
 
79555
80116
  // src/dashboard/ink/VersionOverlay.tsx
79556
- var import_react34 = __toESM(require_react(), 1);
80117
+ var import_react35 = __toESM(require_react(), 1);
79557
80118
  await init_build2();
79558
- var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
80119
+ var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
79559
80120
  var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
79560
80121
  function VersionOverlay({ version: version2 }) {
79561
- const phrase = import_react34.default.useMemo(() => (0, import_sidekick_docker_shared8.getRandomPhrase)(), []);
80122
+ const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared11.getRandomPhrase)(), []);
79562
80123
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
79563
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: import_sidekick_docker_shared8.BRAND_INLINE }),
79564
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { color: "gray", children: [
79565
- import_sidekick_docker_shared8.BRAND_TAGLINE,
79566
- " v",
79567
- version2
79568
- ] }),
80124
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
80125
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared11.BRAND_TAGLINE} v${version2}` }),
80126
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80127
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
80128
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80129
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
79569
80130
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
79570
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", italic: true, children: `"${phrase}"` }),
80131
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
79571
80132
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
79572
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", children: "Press V or Esc to close" })
80133
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
79573
80134
  ] });
79574
80135
  }
79575
80136
 
79576
80137
  // src/dashboard/ink/Dashboard.tsx
79577
- var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
80138
+ var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
79578
80139
 
79579
80140
  // src/dashboard/ExecManager.ts
79580
80141
  var ExecManager = class {
@@ -79633,8 +80194,11 @@ var ExecManager = class {
79633
80194
  // src/dashboard/ink/Dashboard.tsx
79634
80195
  var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
79635
80196
  var SIDE_PANEL_WIDTH = 28;
80197
+ var SIDE_PANEL_WIDTH_WIDE = 42;
79636
80198
  var MIN_SCREEN_WIDTH = 60;
79637
80199
  var MIN_SCREEN_HEIGHT = 15;
80200
+ var RESERVED_UI_ROWS = 5;
80201
+ var TOAST_DURATIONS = { error: 4e3, warning: 3e3, info: 2e3 };
79638
80202
  function reducer(state, action) {
79639
80203
  switch (action.type) {
79640
80204
  case "SWITCH_PANEL":
@@ -79658,7 +80222,7 @@ function reducer(state, action) {
79658
80222
  return { ...state, detailTabIndex: next, detailScrollOffset: 0 };
79659
80223
  }
79660
80224
  case "CYCLE_LAYOUT": {
79661
- const next = state.layoutMode === "normal" ? "expanded" : "normal";
80225
+ const next = state.layoutMode === "normal" ? "wide" : state.layoutMode === "wide" ? "expanded" : "normal";
79662
80226
  return { ...state, layoutMode: next, focusTarget: next === "expanded" ? "detail" : state.focusTarget };
79663
80227
  }
79664
80228
  case "TOGGLE_FOCUS":
@@ -79739,25 +80303,26 @@ var initialState = {
79739
80303
  logFilterMode: "exact"
79740
80304
  };
79741
80305
  function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecFallback }) {
79742
- const [state, dispatch] = (0, import_react35.useReducer)(reducer, initialState);
79743
- const { exit } = use_app_default();
80306
+ const [state, dispatch] = (0, import_react36.useReducer)(reducer, initialState);
79744
80307
  const { columns, rows } = useTerminalSize();
79745
- const toastIdRef = (0, import_react35.useRef)(0);
79746
- const execManagerRef = (0, import_react35.useRef)(null);
79747
- const [phrase, setPhrase] = import_react35.default.useState(() => (0, import_sidekick_docker_shared9.getRandomPhrase)());
79748
- const phraseTimerRef = (0, import_react35.useRef)(null);
79749
- const rotatePhrase = (0, import_react35.useCallback)(() => {
79750
- setPhrase((0, import_sidekick_docker_shared9.getRandomPhrase)());
80308
+ const toastIdRef = (0, import_react36.useRef)(0);
80309
+ const execManagerRef = (0, import_react36.useRef)(null);
80310
+ const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared12.getRandomPhrase)());
80311
+ const phraseTimerRef = (0, import_react36.useRef)(null);
80312
+ const rotatePhraseRef = (0, import_react36.useRef)(void 0);
80313
+ rotatePhraseRef.current = () => {
80314
+ setPhrase((0, import_sidekick_docker_shared12.getRandomPhrase)());
79751
80315
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
79752
- phraseTimerRef.current = setTimeout(rotatePhrase, 7e3);
79753
- }, []);
79754
- (0, import_react35.useEffect)(() => {
79755
- phraseTimerRef.current = setTimeout(rotatePhrase, 7e3);
80316
+ phraseTimerRef.current = setTimeout(() => rotatePhraseRef.current?.(), 7e3);
80317
+ };
80318
+ const rotatePhrase = (0, import_react36.useCallback)(() => rotatePhraseRef.current?.(), []);
80319
+ (0, import_react36.useEffect)(() => {
80320
+ phraseTimerRef.current = setTimeout(() => rotatePhraseRef.current?.(), 7e3);
79756
80321
  return () => {
79757
80322
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
79758
80323
  };
79759
- }, [rotatePhrase]);
79760
- (0, import_react35.useEffect)(() => {
80324
+ }, []);
80325
+ (0, import_react36.useEffect)(() => {
79761
80326
  if (!execTriggerRef) return;
79762
80327
  execTriggerRef.current = (containerId, containerName) => {
79763
80328
  const manager = new ExecManager();
@@ -79792,7 +80357,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79792
80357
  execTriggerRef.current = null;
79793
80358
  };
79794
80359
  }, [execTriggerRef, onExecFallback, columns, rows]);
79795
- (0, import_react35.useEffect)(() => {
80360
+ (0, import_react36.useEffect)(() => {
79796
80361
  if (state.overlay !== "exec") return;
79797
80362
  const handler = (data) => {
79798
80363
  if (data.length === 1 && data[0] === 29) {
@@ -79809,22 +80374,23 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79809
80374
  process.stdin.removeListener("data", handler);
79810
80375
  };
79811
80376
  }, [state.overlay]);
79812
- (0, import_react35.useEffect)(() => {
80377
+ (0, import_react36.useEffect)(() => {
79813
80378
  if (state.overlay === "exec" && execManagerRef.current) {
79814
80379
  execManagerRef.current.resize(columns, rows);
79815
80380
  }
79816
80381
  }, [columns, rows, state.overlay]);
79817
- const addToast = (0, import_react35.useCallback)((message, severity) => {
79818
- const durations = { error: 4e3, warning: 3e3, info: 2e3 };
80382
+ const addToast = (0, import_react36.useCallback)((message, severity) => {
79819
80383
  const id = ++toastIdRef.current;
79820
- dispatch({ type: "ADD_TOAST", toast: { id, message, severity, expiresAt: Date.now() + durations[severity] } });
79821
- setTimeout(() => dispatch({ type: "REMOVE_TOAST", id }), durations[severity]);
80384
+ dispatch({ type: "ADD_TOAST", toast: { id, message, severity, expiresAt: Date.now() + TOAST_DURATIONS[severity] } });
80385
+ setTimeout(() => dispatch({ type: "REMOVE_TOAST", id }), TOAST_DURATIONS[severity]);
79822
80386
  }, []);
79823
80387
  const panel = panels[state.activePanelIndex];
79824
80388
  const tooSmall = columns < MIN_SCREEN_WIDTH || rows < MIN_SCREEN_HEIGHT;
79825
- const sideWidth = state.layoutMode === "expanded" ? 0 : SIDE_PANEL_WIDTH;
79826
- const getFilteredItems = (0, import_react35.useCallback)(() => {
79827
- let items = panel.getItems(metrics);
80389
+ const sideWidth = state.layoutMode === "expanded" ? 0 : state.layoutMode === "wide" ? SIDE_PANEL_WIDTH_WIDE : SIDE_PANEL_WIDTH;
80390
+ const allItems = panel.getItems(metrics);
80391
+ const totalItemCount = allItems.length;
80392
+ const currentItems = (() => {
80393
+ let items = allItems;
79828
80394
  if (state.filterString) {
79829
80395
  const f = state.filterString.toLowerCase();
79830
80396
  items = items.filter((it) => {
@@ -79834,21 +80400,20 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79834
80400
  }
79835
80401
  items.sort((a, b) => a.sortKey - b.sortKey);
79836
80402
  return items;
79837
- }, [panel, metrics, state.filterString]);
79838
- const currentItems = getFilteredItems();
80403
+ })();
79839
80404
  const clampedSelection = Math.min(state.selectedItemIndex, Math.max(0, currentItems.length - 1));
79840
80405
  if (clampedSelection !== state.selectedItemIndex && currentItems.length > 0) {
79841
80406
  dispatch({ type: "SELECT_ITEM", index: clampedSelection });
79842
80407
  }
79843
- const sideViewportHeight = Math.max(1, rows - 5);
80408
+ const sideViewportHeight = Math.max(1, rows - RESERVED_UI_ROWS);
79844
80409
  const sideScroll = useWindowedScroll({ totalItems: currentItems.length, viewportHeight: sideViewportHeight });
79845
- (0, import_react35.useEffect)(() => {
80410
+ (0, import_react36.useEffect)(() => {
79846
80411
  if (sideScroll.selectedIndex !== state.selectedItemIndex) {
79847
80412
  sideScroll.setSelected(state.selectedItemIndex);
79848
80413
  }
79849
80414
  }, [state.selectedItemIndex]);
79850
80415
  const selectedItem = currentItems[clampedSelection];
79851
- (0, import_react35.useEffect)(() => {
80416
+ (0, import_react36.useEffect)(() => {
79852
80417
  onSelectionChange?.(panel.id, selectedItem?.id ?? null);
79853
80418
  }, [panel.id, selectedItem?.id]);
79854
80419
  const detailTabs = panel.detailTabs;
@@ -79865,362 +80430,67 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79865
80430
  detailContent = "(no item selected)";
79866
80431
  }
79867
80432
  const detailLines = detailContent.split("\n");
79868
- const detailViewportHeight = Math.max(1, rows - 5);
80433
+ const detailViewportHeight = Math.max(1, rows - RESERVED_UI_ROWS);
79869
80434
  const activeTab = detailTabs[tabIdx];
79870
80435
  const shouldAutoScroll = activeTab?.autoScrollBottom ?? false;
79871
- (0, import_react35.useEffect)(() => {
80436
+ (0, import_react36.useEffect)(() => {
79872
80437
  if (shouldAutoScroll && detailLines.length > detailViewportHeight) {
79873
80438
  dispatch({ type: "SCROLL_DETAIL", offset: detailLines.length - detailViewportHeight });
79874
80439
  }
79875
80440
  }, [shouldAutoScroll, detailLines.length, detailViewportHeight]);
79876
- const getContextActions = (0, import_react35.useCallback)(() => {
79877
- if (!selectedItem) return [];
79878
- return panel.getActions().filter((a) => !a.condition || a.condition(selectedItem));
79879
- }, [panel, selectedItem]);
79880
- const contextActions = state.overlay === "context-menu" ? getContextActions() : [];
79881
- const handleMouse = (0, import_react35.useCallback)((event) => {
79882
- rotatePhrase();
79883
- if (state.overlay === "filter") return;
79884
- if (state.overlay) {
79885
- if (event.type === "click") {
79886
- dispatch({ type: "SET_OVERLAY", overlay: null });
79887
- }
79888
- return;
79889
- }
79890
- const { x, y } = event;
79891
- if (event.type === "scroll") {
79892
- if (x < sideWidth && sideWidth > 0) {
79893
- const delta = event.scrollDirection === "down" ? 3 : -3;
79894
- dispatch({ type: "SCROLL_SIDE", delta, itemCount: currentItems.length });
79895
- const newIdx = Math.max(0, Math.min(clampedSelection + delta, currentItems.length - 1));
79896
- sideScroll.setSelected(newIdx);
79897
- } else {
79898
- const delta = event.scrollDirection === "down" ? 3 : -3;
79899
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79900
- }
79901
- return;
79902
- }
79903
- if (event.type !== "click" || event.button !== "left") return;
79904
- if (y === 0) {
79905
- let col = 0;
79906
- for (let i = 0; i < panels.length; i++) {
79907
- const count = panelCounts[i];
79908
- let countLen = 0;
79909
- if (count) {
79910
- countLen = count.running !== void 0 ? ` ${count.running}/${count.total}`.length : ` ${count.total}`.length;
79911
- }
79912
- const tabWidth = String(panels[i].shortcutKey).length + panels[i].title.length + 3 + countLen + 1;
79913
- if (x >= col && x < col + tabWidth) {
79914
- panels[state.activePanelIndex]?.onDeactivate?.();
79915
- dispatch({ type: "SWITCH_PANEL", index: i });
79916
- panels[i]?.onActivate?.();
79917
- return;
79918
- }
79919
- col += tabWidth;
79920
- }
79921
- return;
79922
- }
79923
- if (y >= rows - 1) return;
79924
- if (x < sideWidth && sideWidth > 0) {
79925
- dispatch({ type: "SET_FOCUS", target: "side" });
79926
- const hasScrollUp = sideScroll.scrollOffset > 0;
79927
- const itemRow = y - 2 - (hasScrollUp ? 1 : 0);
79928
- const itemIndex = sideScroll.scrollOffset + itemRow;
79929
- if (itemIndex >= 0 && itemIndex < currentItems.length) {
79930
- dispatch({ type: "SELECT_ITEM", index: itemIndex });
79931
- sideScroll.setSelected(itemIndex);
79932
- }
79933
- } else {
79934
- dispatch({ type: "SET_FOCUS", target: "detail" });
79935
- if (y === 1 && detailTabs.length > 1) {
79936
- let col = sideWidth;
79937
- for (let i = 0; i < detailTabs.length; i++) {
79938
- const tabWidth = detailTabs[i].label.length + 3;
79939
- if (x >= col && x < col + tabWidth) {
79940
- dispatch({ type: "SET_DETAIL_TAB", index: i });
79941
- return;
79942
- }
79943
- col += tabWidth;
79944
- }
79945
- }
79946
- }
79947
- }, [state.overlay, state.activePanelIndex, sideWidth, currentItems.length, clampedSelection, sideScroll, detailLines.length, detailViewportHeight, panels, detailTabs, rows, rotatePhrase]);
79948
- use_input_default((input, key) => {
79949
- if (state.overlay === "exec") return;
79950
- rotatePhrase();
79951
- if (input === "q" || key.ctrl && input === "c") {
79952
- if (state.overlay) {
79953
- dispatch({ type: "SET_OVERLAY", overlay: null });
79954
- return;
79955
- }
79956
- exit();
79957
- return;
79958
- }
79959
- if (state.overlay === "confirm") {
79960
- if (input === "y" || input === "Y") {
79961
- state.confirmAction?.();
79962
- dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79963
- return;
79964
- }
79965
- if (input === "n" || input === "N" || key.escape) {
79966
- dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79967
- return;
79968
- }
79969
- return;
79970
- }
79971
- if (state.overlay === "filter") {
79972
- if (key.escape) {
79973
- if (state.filterString) addToast("Filter cleared", "info");
79974
- dispatch({ type: "SET_FILTER", value: "" });
79975
- dispatch({ type: "SET_OVERLAY", overlay: null });
79976
- return;
79977
- }
79978
- if (key.return) {
79979
- if (state.filterString) addToast(`Filter: "${state.filterString}"`, "info");
79980
- dispatch({ type: "SET_OVERLAY", overlay: null });
79981
- return;
79982
- }
79983
- if (key.backspace || key.delete) {
79984
- dispatch({ type: "SET_FILTER", value: state.filterString.slice(0, -1) });
79985
- return;
79986
- }
79987
- if (input && !key.ctrl && !key.meta) {
79988
- dispatch({ type: "SET_FILTER", value: state.filterString + input });
79989
- return;
79990
- }
79991
- return;
79992
- }
79993
- if (state.overlay === "log-filter") {
79994
- if (key.escape) {
79995
- if (state.logFilterString) addToast("Log filter cleared", "info");
79996
- dispatch({ type: "SET_LOG_FILTER", value: "" });
79997
- dispatch({ type: "SET_OVERLAY", overlay: null });
79998
- return;
79999
- }
80000
- if (key.return) {
80001
- dispatch({ type: "SET_OVERLAY", overlay: null });
80002
- return;
80003
- }
80004
- if (key.tab) {
80005
- dispatch({ type: "TOGGLE_LOG_FILTER_MODE" });
80006
- return;
80007
- }
80008
- if (key.backspace || key.delete) {
80009
- dispatch({ type: "SET_LOG_FILTER", value: state.logFilterString.slice(0, -1) });
80010
- return;
80011
- }
80012
- if (input && !key.ctrl && !key.meta) {
80013
- dispatch({ type: "SET_LOG_FILTER", value: state.logFilterString + input });
80014
- return;
80015
- }
80016
- return;
80017
- }
80018
- if (state.overlay === "context-menu") {
80019
- if (key.escape) {
80020
- dispatch({ type: "SET_OVERLAY", overlay: null });
80021
- return;
80022
- }
80023
- if (input === "j" || key.downArrow) {
80024
- dispatch({ type: "CONTEXT_MENU_NAV", delta: 1, itemCount: contextActions.length });
80025
- return;
80026
- }
80027
- if (input === "k" || key.upArrow) {
80028
- dispatch({ type: "CONTEXT_MENU_NAV", delta: -1, itemCount: contextActions.length });
80029
- return;
80030
- }
80031
- if (key.return) {
80032
- const action = contextActions[state.contextMenuIndex];
80033
- if (action && selectedItem) {
80034
- if (action.confirm) {
80035
- dispatch({ type: "SET_CONFIRM", action: () => {
80036
- action.handler(selectedItem);
80037
- addToast(action.label, "info");
80038
- }, message: action.confirmMessage || "Are you sure?" });
80039
- } else {
80040
- action.handler(selectedItem);
80041
- addToast(action.label, "info");
80042
- }
80043
- dispatch({ type: "SET_OVERLAY", overlay: null });
80044
- }
80045
- return;
80046
- }
80047
- const match = contextActions.find((a) => a.key === input);
80048
- if (match && selectedItem) {
80049
- if (match.confirm) {
80050
- dispatch({ type: "SET_CONFIRM", action: () => {
80051
- match.handler(selectedItem);
80052
- addToast(match.label, "info");
80053
- }, message: match.confirmMessage || "Are you sure?" });
80054
- } else {
80055
- match.handler(selectedItem);
80056
- addToast(match.label, "info");
80057
- }
80058
- dispatch({ type: "SET_OVERLAY", overlay: null });
80059
- }
80060
- return;
80061
- }
80062
- if (state.overlay === "help") {
80063
- if (key.escape || input === "?") {
80064
- dispatch({ type: "SET_OVERLAY", overlay: null });
80065
- }
80066
- return;
80067
- }
80068
- if (state.overlay === "version") {
80069
- if (key.escape || input === "V") {
80070
- dispatch({ type: "SET_OVERLAY", overlay: null });
80071
- }
80072
- return;
80073
- }
80074
- if (key.escape) {
80075
- if (state.filterString) {
80076
- dispatch({ type: "SET_FILTER", value: "" });
80077
- return;
80078
- }
80079
- if (state.focusTarget === "detail") {
80080
- dispatch({ type: "SET_FOCUS", target: "side" });
80081
- return;
80082
- }
80083
- return;
80084
- }
80085
- if (input === "?") {
80086
- dispatch({ type: "SET_OVERLAY", overlay: "help" });
80087
- return;
80088
- }
80089
- if (input === "V") {
80090
- dispatch({ type: "SET_OVERLAY", overlay: "version" });
80091
- return;
80092
- }
80093
- const num = parseInt(input, 10);
80094
- if (num >= 1 && num <= panels.length) {
80095
- panels[state.activePanelIndex]?.onDeactivate?.();
80096
- dispatch({ type: "SWITCH_PANEL", index: num - 1 });
80097
- panels[num - 1]?.onActivate?.();
80098
- return;
80099
- }
80100
- if (key.tab) {
80101
- dispatch({ type: "TOGGLE_FOCUS" });
80102
- return;
80103
- }
80104
- if (input === "z") {
80105
- dispatch({ type: "CYCLE_LAYOUT" });
80106
- addToast(`Layout: ${state.layoutMode === "normal" ? "Expanded" : "Normal"}`, "info");
80107
- return;
80108
- }
80109
- if (input === "/") {
80110
- dispatch({ type: "SET_OVERLAY", overlay: "filter" });
80111
- return;
80112
- }
80113
- if (input === "f") {
80114
- if (panel.id === "containers" && tabIdx === 0) {
80115
- dispatch({ type: "SET_OVERLAY", overlay: "log-filter" });
80116
- return;
80117
- }
80118
- }
80119
- if (input === "x") {
80120
- if (selectedItem && panel.getActions().length > 0) {
80121
- dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
80122
- }
80123
- return;
80124
- }
80125
- if (input === "[") {
80126
- dispatch({ type: "CYCLE_DETAIL_TAB", direction: -1, tabCount: detailTabs.length });
80127
- return;
80128
- }
80129
- if (input === "]") {
80130
- dispatch({ type: "CYCLE_DETAIL_TAB", direction: 1, tabCount: detailTabs.length });
80131
- return;
80132
- }
80133
- if (state.focusTarget === "side") {
80134
- if (input === "j" || key.downArrow) {
80135
- if (clampedSelection < currentItems.length - 1) {
80136
- dispatch({ type: "SELECT_ITEM", index: clampedSelection + 1 });
80137
- sideScroll.selectNext();
80138
- }
80139
- return;
80140
- }
80141
- if (input === "k" || key.upArrow) {
80142
- if (clampedSelection > 0) {
80143
- dispatch({ type: "SELECT_ITEM", index: clampedSelection - 1 });
80144
- sideScroll.selectPrev();
80145
- }
80146
- return;
80147
- }
80148
- if (input === "g") {
80149
- dispatch({ type: "SELECT_ITEM", index: 0 });
80150
- sideScroll.selectFirst();
80151
- return;
80152
- }
80153
- if (input === "G") {
80154
- dispatch({ type: "SELECT_ITEM", index: Math.max(0, currentItems.length - 1) });
80155
- sideScroll.selectLast();
80156
- return;
80157
- }
80158
- if (key.return) {
80159
- dispatch({ type: "SET_FOCUS", target: "detail" });
80160
- return;
80161
- }
80162
- }
80163
- if (state.focusTarget === "detail") {
80164
- if (input === "j" || key.downArrow) {
80165
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
80166
- return;
80167
- }
80168
- if (input === "k" || key.upArrow) {
80169
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta: -1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
80170
- return;
80171
- }
80172
- if (input === "h" || key.leftArrow) {
80173
- dispatch({ type: "SET_FOCUS", target: "side" });
80174
- return;
80175
- }
80176
- if (input === "g") {
80177
- dispatch({ type: "SCROLL_DETAIL", offset: 0 });
80178
- return;
80179
- }
80180
- if (input === "G") {
80181
- dispatch({ type: "SCROLL_DETAIL", offset: Math.max(0, detailLines.length - detailViewportHeight) });
80182
- return;
80183
- }
80184
- }
80185
- if (selectedItem) {
80186
- const actions = panel.getActions();
80187
- const actionMatch = actions.find((a) => a.key === input && (!a.condition || a.condition(selectedItem)));
80188
- if (actionMatch) {
80189
- if (actionMatch.confirm) {
80190
- dispatch({ type: "SET_CONFIRM", action: () => {
80191
- actionMatch.handler(selectedItem);
80192
- addToast(actionMatch.label, "info");
80193
- }, message: actionMatch.confirmMessage || "Are you sure?" });
80194
- } else {
80195
- actionMatch.handler(selectedItem);
80196
- addToast(actionMatch.label, "info");
80197
- }
80198
- }
80199
- }
80200
- });
80201
- if (tooSmall) {
80202
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TooSmallOverlay, { columns, rows });
80203
- }
80441
+ const panelActions = panel.getActions();
80442
+ const applicableActions = selectedItem ? panelActions.filter((a) => !a.condition || a.condition(selectedItem)) : [];
80443
+ const contextActions = state.overlay === "context-menu" ? applicableActions : [];
80204
80444
  const runningCount = metrics.containers.filter((c) => c.state === "running").length;
80205
80445
  const panelCounts = panels.map((p) => {
80206
- const items = p.getItems(metrics);
80207
80446
  if (p.id === "containers") {
80208
- const running = items.filter((it) => {
80209
- const d = it.data;
80210
- return d?.state === "running";
80211
- }).length;
80212
- return { total: items.length, running };
80447
+ return { total: metrics.containers.length, running: runningCount };
80213
80448
  }
80214
80449
  if (p.id === "services") {
80215
- const meaningful = items.filter((it) => it.data !== null).length;
80450
+ const meaningful = metrics.composeProjects.reduce((sum, proj) => sum + proj.services.length, 0);
80216
80451
  return { total: meaningful };
80217
80452
  }
80453
+ const items = p === panel ? allItems : p.getItems(metrics);
80218
80454
  return { total: items.length };
80219
80455
  });
80456
+ const handleMouse = useMouseHandler({
80457
+ state,
80458
+ dispatch,
80459
+ panels,
80460
+ panelCounts,
80461
+ currentItems,
80462
+ clampedSelection,
80463
+ sideWidth,
80464
+ sideScroll,
80465
+ detailLines,
80466
+ detailViewportHeight,
80467
+ detailTabs,
80468
+ rows,
80469
+ rotatePhrase
80470
+ });
80471
+ useKeyboardHandler({
80472
+ state,
80473
+ dispatch,
80474
+ panels,
80475
+ panel,
80476
+ selectedItem,
80477
+ contextActions,
80478
+ clampedSelection,
80479
+ currentItems,
80480
+ detailLines,
80481
+ detailViewportHeight,
80482
+ detailTabs,
80483
+ tabIdx,
80484
+ panelActions,
80485
+ sideScroll,
80486
+ addToast,
80487
+ rotatePhrase
80488
+ });
80489
+ if (tooSmall) {
80490
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TooSmallOverlay, { columns, rows });
80491
+ }
80220
80492
  const showNormalLayout = state.overlay !== "help" && state.overlay !== "exec" && state.overlay !== "version";
80221
- const allItems = panel.getItems(metrics);
80222
- const totalItemCount = allItems.length;
80223
- const panelActionHints = selectedItem ? panel.getActions().filter((a) => !a.condition || a.condition(selectedItem)).map((a) => `${a.key}:${a.label}`).join(" ") : "";
80493
+ const panelActionHints = applicableActions.map((a) => ({ key: a.key, label: a.label, destructive: !!a.confirm }));
80224
80494
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
80225
80495
  showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
80226
80496
  showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
@@ -80253,8 +80523,8 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
80253
80523
  )
80254
80524
  ] })
80255
80525
  ] }),
80256
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.2" }),
80257
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VersionOverlay, { version: "0.1.2" }),
80526
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.4" }),
80527
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VersionOverlay, { version: "0.1.4" }),
80258
80528
  state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
80259
80529
  ExecOverlay,
80260
80530
  {
@@ -80267,12 +80537,11 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
80267
80537
  {
80268
80538
  daemonConnected: metrics.daemonConnected,
80269
80539
  focusTarget: state.focusTarget,
80270
- panelHints: "",
80271
80540
  panelActionHints,
80272
80541
  filterString: state.filterString,
80273
80542
  containerCount: metrics.containers.length,
80274
80543
  runningCount,
80275
- version: "0.1.2",
80544
+ version: "0.1.4",
80276
80545
  matchCount: state.filterString ? currentItems.length : void 0,
80277
80546
  totalCount: state.filterString ? totalItemCount : void 0,
80278
80547
  lastRefresh: metrics.lastRefresh
@@ -80314,7 +80583,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
80314
80583
  async function dashboardAction(_opts, cmd) {
80315
80584
  const globalOpts = cmd.parent?.opts() ?? cmd.opts();
80316
80585
  const socketPath = globalOpts.socket;
80317
- const client = new import_sidekick_docker_shared10.DockerClient(socketPath ? { socketPath } : void 0);
80586
+ const client = new import_sidekick_docker_shared13.DockerClient(socketPath ? { socketPath } : void 0);
80318
80587
  const ok = await client.ping();
80319
80588
  if (!ok) {
80320
80589
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -80323,10 +80592,12 @@ async function dashboardAction(_opts, cmd) {
80323
80592
  const cwd2 = process.cwd();
80324
80593
  const state = new DockerState(client, cwd2);
80325
80594
  await state.refresh();
80326
- const composeClient = new import_sidekick_docker_shared10.ComposeClient();
80595
+ const composeClient = new import_sidekick_docker_shared13.ComposeClient();
80327
80596
  const onAction = () => {
80328
- state.refresh().then(() => scheduleRender()).catch(() => {
80329
- });
80597
+ state.refresh().then(() => scheduleRender()).catch((e) => console.debug("refresh failed:", e));
80598
+ };
80599
+ const onError = (msg) => {
80600
+ console.debug("panel action failed:", msg);
80330
80601
  };
80331
80602
  const logManager = new LogStreamManager(client, () => {
80332
80603
  state.setSelectedLogs(logManager.getLogs());
@@ -80353,8 +80624,7 @@ async function dashboardAction(_opts, cmd) {
80353
80624
  client.inspectContainer(itemId).then((info) => {
80354
80625
  state.setInspectedEnv(itemId, info.Config.Env || []);
80355
80626
  scheduleRender();
80356
- }).catch(() => {
80357
- });
80627
+ }).catch((e) => console.debug("inspectContainer failed:", e));
80358
80628
  }
80359
80629
  } else if (panelId === "services" && itemId) {
80360
80630
  logManager.select(null);
@@ -80372,24 +80642,24 @@ async function dashboardAction(_opts, cmd) {
80372
80642
  }
80373
80643
  };
80374
80644
  const panels = [
80375
- new ContainersPanel(client, onAction),
80376
- new ServicesPanel(composeClient, onAction, cwd2),
80377
- new ImagesPanel(client, onAction),
80378
- new VolumesPanel(client, onAction),
80379
- new NetworksPanel(client, onAction)
80645
+ new ContainersPanel(client, onAction, onError),
80646
+ new ServicesPanel(composeClient, onAction, cwd2, onError),
80647
+ new ImagesPanel(client, onAction, onError),
80648
+ new VolumesPanel(client, onAction, onError),
80649
+ new NetworksPanel(client, onAction, onError)
80380
80650
  ];
80381
- const watcher = new import_sidekick_docker_shared10.EventWatcher(client, {
80651
+ const watcher = new import_sidekick_docker_shared13.EventWatcher(client, {
80382
80652
  onEvent: (event) => {
80383
80653
  state.processEvent(event);
80384
80654
  scheduleRender();
80385
80655
  },
80386
- onError: () => {
80656
+ onError: (err) => {
80657
+ console.debug("event watcher error:", err.message);
80387
80658
  }
80388
80659
  });
80389
80660
  watcher.start();
80390
80661
  const refreshInterval = setInterval(() => {
80391
- state.refresh().then(() => scheduleRender()).catch(() => {
80392
- });
80662
+ state.refresh().then(() => scheduleRender()).catch((e) => console.debug("periodic refresh failed:", e));
80393
80663
  }, 3e4);
80394
80664
  const execTriggerRef = { current: null };
80395
80665
  const onExecFallback = (containerId) => {
@@ -80400,7 +80670,7 @@ async function dashboardAction(_opts, cmd) {
80400
80670
  stdio: "inherit"
80401
80671
  });
80402
80672
  instance = render2(
80403
- import_react36.default.createElement(Dashboard, {
80673
+ import_react37.default.createElement(Dashboard, {
80404
80674
  panels,
80405
80675
  metrics: getEnrichedMetrics(),
80406
80676
  onSelectionChange,
@@ -80412,7 +80682,7 @@ async function dashboardAction(_opts, cmd) {
80412
80682
  };
80413
80683
  const { render: render2 } = await init_build2().then(() => build_exports);
80414
80684
  let instance = render2(
80415
- import_react36.default.createElement(Dashboard, {
80685
+ import_react37.default.createElement(Dashboard, {
80416
80686
  panels,
80417
80687
  metrics: getEnrichedMetrics(),
80418
80688
  onSelectionChange,
@@ -80430,6 +80700,9 @@ async function dashboardAction(_opts, cmd) {
80430
80700
  onExecFallback(containerId);
80431
80701
  }
80432
80702
  });
80703
+ containersPanel.setOnCopyLogs((text) => {
80704
+ copyToClipboard(text);
80705
+ });
80433
80706
  let renderTimer = null;
80434
80707
  function getEnrichedMetrics() {
80435
80708
  const m = state.getMetrics();
@@ -80443,7 +80716,7 @@ async function dashboardAction(_opts, cmd) {
80443
80716
  renderTimer = setTimeout(() => {
80444
80717
  renderTimer = null;
80445
80718
  instance.rerender(
80446
- import_react36.default.createElement(Dashboard, {
80719
+ import_react37.default.createElement(Dashboard, {
80447
80720
  panels,
80448
80721
  metrics: getEnrichedMetrics(),
80449
80722
  onSelectionChange,
@@ -80493,9 +80766,9 @@ async function dashboardAction(_opts, cmd) {
80493
80766
  }
80494
80767
 
80495
80768
  // src/commands/ps.ts
80496
- var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
80769
+ var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
80497
80770
  async function psAction(opts) {
80498
- const client = new import_sidekick_docker_shared11.DockerClient();
80771
+ const client = new import_sidekick_docker_shared14.DockerClient();
80499
80772
  const ok = await client.ping();
80500
80773
  if (!ok) {
80501
80774
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -80522,9 +80795,9 @@ async function psAction(opts) {
80522
80795
  }
80523
80796
 
80524
80797
  // src/commands/logs.ts
80525
- var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
80798
+ var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
80526
80799
  async function logsAction(container, opts) {
80527
- const client = new import_sidekick_docker_shared12.DockerClient();
80800
+ const client = new import_sidekick_docker_shared15.DockerClient();
80528
80801
  const ok = await client.ping();
80529
80802
  if (!ok) {
80530
80803
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -80541,7 +80814,7 @@ async function logsAction(container, opts) {
80541
80814
  console.log(`${prefix}${ts} ${entry.message}${reset}`);
80542
80815
  }
80543
80816
  } catch (err) {
80544
- const msg = err instanceof Error ? err.message : String(err);
80817
+ const msg = (0, import_sidekick_docker_shared15.errorMessage)(err);
80545
80818
  if (msg.includes("no such container") || msg.includes("No such container")) {
80546
80819
  console.error(`Error: Container "${container}" not found.`);
80547
80820
  } else {
@@ -80553,7 +80826,7 @@ async function logsAction(container, opts) {
80553
80826
 
80554
80827
  // src/cli.ts
80555
80828
  var program2 = new Command();
80556
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.2").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
80829
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.4").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
80557
80830
  await dashboardAction(_opts, cmd);
80558
80831
  });
80559
80832
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {