numux 2.16.0 → 2.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -212,7 +212,7 @@ export default defineConfig({
212
212
  <!-- generated:options -->
213
213
  | Flag | Description |
214
214
  |------|-------------|
215
- | `-s,` `--sort` `<config|alphabetical|topological>` | Tab display order |
215
+ | `-s,` `--sort` `<config|alphabetical|topological|status>` | Tab display order |
216
216
  | `-w,` `--workspace` `<script>` | Run a package.json script across all workspaces |
217
217
  | `-n,` `--name` `<name=command>` | Add a named process |
218
218
  | `-c,` `--color` `<colors>` | Comma-separated colors (hex or names: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple) |
@@ -277,7 +277,7 @@ Top-level options apply to all processes (process-level settings override):
277
277
  | `stopSignal` | `'SIGTERM' \| 'SIGINT' \| 'SIGHUP'` | Global stop signal, inherited by all processes |
278
278
  | `errorMatcher` | `boolean \| string` | Global error matcher, inherited by all processes. `true` = detect ANSI red output, string = regex |
279
279
  | `watch` | `string \| string[]` | Global watch patterns, inherited by processes without their own watch |
280
- | `sort` | `'config' \| 'alphabetical' \| 'topological'` | Tab display order. `'config'` preserves definition order (package.json script order for wildcards), `'alphabetical'` sorts by process name, `'topological'` sorts by dependency tiers. |
280
+ | `sort` | `'config' \| 'alphabetical' \| 'topological' \| 'status'` | Tab display order. `'config'` preserves definition order (package.json script order for wildcards), `'alphabetical'` sorts by process name, `'topological'` sorts by dependency tiers, `'status'` uses config order but moves finished/stopped/failed/skipped tabs to the bottom. |
281
281
  | `prefix` | `boolean` | Use prefixed output mode instead of TUI (for CI/scripts) |
282
282
  | `timestamps` | `boolean \| string` | Add timestamps to output lines. `true` uses default `HH:mm:ss.SSS` format, or pass a format string (e.g. `"HH:mm:ss"`) |
283
283
  | `killOthers` | `boolean` | Kill all processes when any one exits (regardless of exit code) |
package/dist/man/numux.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH "NUMUX" "1" "May 2026" "2.16.0" "numux manual"
1
+ .TH "NUMUX" "1" "May 2026" "2.16.2" "numux manual"
2
2
  .SH "NAME"
3
3
  \fBnumux\fR
4
4
  .P
@@ -486,9 +486,9 @@ _
486
486
  T{
487
487
  \fBsort\fP
488
488
  T}|T{
489
- \fB&#39;config&#39; | &#39;alphabetical&#39; | &#39;topological&#39;\fP
489
+ \fB&#39;config&#39; | &#39;alphabetical&#39; | &#39;topological&#39; | &#39;status&#39;\fP
490
490
  T}|T{
491
- Tab display order\. \fB&#39;config&#39;\fP preserves definition order (package\.json script order for wildcards), \fB&#39;alphabetical&#39;\fP sorts by process name, \fB&#39;topological&#39;\fP sorts by dependency tiers\.
491
+ Tab display order\. \fB&#39;config&#39;\fP preserves definition order (package\.json script order for wildcards), \fB&#39;alphabetical&#39;\fP sorts by process name, \fB&#39;topological&#39;\fP sorts by dependency tiers, \fB&#39;status&#39;\fP uses config order but moves finished/stopped/failed/skipped tabs to the bottom\.
492
492
  T}
493
493
  _
494
494
  T{
package/dist/numux.js CHANGED
@@ -230,7 +230,7 @@ export default defineConfig({
230
230
  options: { title: "Options", body: `<!-- generated:options -->
231
231
  | Flag | Description |
232
232
  |------|-------------|
233
- | \`-s,\` \`--sort\` \`<config|alphabetical|topological>\` | Tab display order |
233
+ | \`-s,\` \`--sort\` \`<config|alphabetical|topological|status>\` | Tab display order |
234
234
  | \`-w,\` \`--workspace\` \`<script>\` | Run a package.json script across all workspaces |
235
235
  | \`-n,\` \`--name\` \`<name=command>\` | Add a named process |
236
236
  | \`-c,\` \`--color\` \`<colors>\` | Comma-separated colors (hex or names: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple) |
@@ -283,7 +283,7 @@ numux logs api | tail -f # Follow process log output
283
283
  | \`stopSignal\` | \`'SIGTERM' \\| 'SIGINT' \\| 'SIGHUP'\` | Global stop signal, inherited by all processes |
284
284
  | \`errorMatcher\` | \`boolean \\| string\` | Global error matcher, inherited by all processes. \`true\` = detect ANSI red output, string = regex |
285
285
  | \`watch\` | \`string \\| string[]\` | Global watch patterns, inherited by processes without their own watch |
286
- | \`sort\` | \`'config' \\| 'alphabetical' \\| 'topological'\` | Tab display order. \`'config'\` preserves definition order (package.json script order for wildcards), \`'alphabetical'\` sorts by process name, \`'topological'\` sorts by dependency tiers. |
286
+ | \`sort\` | \`'config' \\| 'alphabetical' \\| 'topological' \\| 'status'\` | Tab display order. \`'config'\` preserves definition order (package.json script order for wildcards), \`'alphabetical'\` sorts by process name, \`'topological'\` sorts by dependency tiers, \`'status'\` uses config order but moves finished/stopped/failed/skipped tabs to the bottom. |
287
287
  | \`prefix\` | \`boolean\` | Use prefixed output mode instead of TUI (for CI/scripts) |
288
288
  | \`timestamps\` | \`boolean \\| string\` | Add timestamps to output lines. \`true\` uses default \`HH:mm:ss.SSS\` format, or pass a format string (e.g. \`"HH:mm:ss"\`) |
289
289
  | \`killOthers\` | \`boolean\` | Kill all processes when any one exits (regardless of exit code) |
@@ -548,7 +548,7 @@ var init_help = __esm(() => {
548
548
  var require_package = __commonJS((exports, module) => {
549
549
  module.exports = {
550
550
  name: "numux",
551
- version: "2.16.0",
551
+ version: "2.16.2",
552
552
  description: "Terminal multiplexer with dependency orchestration",
553
553
  type: "module",
554
554
  license: "MIT",
@@ -738,7 +738,7 @@ var FLAGS = [
738
738
  short: "-s",
739
739
  key: "sort",
740
740
  description: "Tab display order",
741
- valueName: "<config|alphabetical|topological>",
741
+ valueName: "<config|alphabetical|topological|status>",
742
742
  completionHint: "none"
743
743
  },
744
744
  {
@@ -1912,7 +1912,7 @@ function validateErrorMatcher(name, value) {
1912
1912
  }
1913
1913
  return;
1914
1914
  }
1915
- var VALID_SORT_VALUES = new Set(["config", "alphabetical", "topological"]);
1915
+ var VALID_SORT_VALUES = new Set(["config", "alphabetical", "topological", "status"]);
1916
1916
  function validateSort(value) {
1917
1917
  if (typeof value === "string") {
1918
1918
  if (!VALID_SORT_VALUES.has(value)) {
@@ -3556,7 +3556,7 @@ function openLink(link) {
3556
3556
  }
3557
3557
 
3558
3558
  // src/ui/pane.ts
3559
- var RENDER_LIMIT = 5000;
3559
+ var RENDER_LIMIT = 1500;
3560
3560
  var MAX_SCROLLBACK_LINES = 1e6;
3561
3561
  var MAX_BUFFER_BYTES = 500 * 1024 * 1024;
3562
3562
 
@@ -3570,6 +3570,9 @@ class Pane {
3570
3570
  _timestampFormat = null;
3571
3571
  lineTimestamps = [];
3572
3572
  lineCounter = 0;
3573
+ signedLineCount = 0;
3574
+ timestampUpdateTimer = null;
3575
+ static TIMESTAMP_UPDATE_DEBOUNCE_MS = 32;
3573
3576
  _onScroll = null;
3574
3577
  _onCopy = null;
3575
3578
  _onLinkClick = null;
@@ -3630,6 +3633,8 @@ class Pane {
3630
3633
  this.bytesFed = 0;
3631
3634
  this.lineTimestamps = [];
3632
3635
  this.lineCounter = 0;
3636
+ this.signedLineCount = 0;
3637
+ this.timestampGutter?.clearAllLineSigns();
3633
3638
  }
3634
3639
  const now = Date.now();
3635
3640
  if (this.lineCounter === 0) {
@@ -3644,10 +3649,18 @@ class Pane {
3644
3649
  }
3645
3650
  const text = this.decoder.decode(data, { stream: true });
3646
3651
  this.terminal.feed(text);
3647
- if (this._timestampFormat) {
3648
- this.updateTimestampSigns();
3652
+ if (this._timestampFormat && this.lineTimestamps.length !== this.signedLineCount) {
3653
+ this.scheduleTimestampUpdate();
3649
3654
  }
3650
3655
  }
3656
+ scheduleTimestampUpdate() {
3657
+ if (this.timestampUpdateTimer)
3658
+ return;
3659
+ this.timestampUpdateTimer = setTimeout(() => {
3660
+ this.timestampUpdateTimer = null;
3661
+ this.updateTimestampSigns();
3662
+ }, Pane.TIMESTAMP_UPDATE_DEBOUNCE_MS);
3663
+ }
3651
3664
  resize(cols, rows) {
3652
3665
  this.terminal.cols = cols;
3653
3666
  this.terminal.rows = rows;
@@ -3725,6 +3738,11 @@ class Pane {
3725
3738
  this.bytesFed = 0;
3726
3739
  this.lineTimestamps = [];
3727
3740
  this.lineCounter = 0;
3741
+ this.signedLineCount = 0;
3742
+ if (this.timestampUpdateTimer) {
3743
+ clearTimeout(this.timestampUpdateTimer);
3744
+ this.timestampUpdateTimer = null;
3745
+ }
3728
3746
  if (this._timestampFormat) {
3729
3747
  this.timestampGutter?.clearAllLineSigns();
3730
3748
  }
@@ -3736,6 +3754,11 @@ class Pane {
3736
3754
  if (wasEnabled === isEnabled && this._timestampFormat === newFormat)
3737
3755
  return;
3738
3756
  this._timestampFormat = newFormat;
3757
+ if (this.timestampUpdateTimer) {
3758
+ clearTimeout(this.timestampUpdateTimer);
3759
+ this.timestampUpdateTimer = null;
3760
+ }
3761
+ this.signedLineCount = 0;
3739
3762
  if (isEnabled && !wasEnabled) {
3740
3763
  this.scrollBox.remove(this.terminal.id);
3741
3764
  const gutterWidth = (newFormat?.length ?? 8) + 1;
@@ -3775,8 +3798,14 @@ class Pane {
3775
3798
  signs.set(i, { before: formatTimestamp(new Date(this.lineTimestamps[i]), fmt) });
3776
3799
  }
3777
3800
  this.timestampGutter.setLineSigns(signs);
3801
+ this.signedLineCount = this.lineTimestamps.length;
3778
3802
  }
3779
3803
  destroy() {
3804
+ if (this.timestampUpdateTimer) {
3805
+ clearTimeout(this.timestampUpdateTimer);
3806
+ this.timestampUpdateTimer = null;
3807
+ }
3808
+ this.timestampGutter?.clearTarget();
3780
3809
  this.terminal.destroy();
3781
3810
  }
3782
3811
  }
@@ -4102,6 +4131,11 @@ var STATUS_ICON_HEX = {
4102
4131
  skipped: "#888888"
4103
4132
  };
4104
4133
  var TERMINAL_STATUSES = new Set(["finished", "stopped", "failed", "skipped"]);
4134
+ function getDisplayOrder(originalNames, statuses) {
4135
+ const active = originalNames.filter((n) => !TERMINAL_STATUSES.has(statuses.get(n)));
4136
+ const terminal = originalNames.filter((n) => TERMINAL_STATUSES.has(statuses.get(n)));
4137
+ return [...active, ...terminal];
4138
+ }
4105
4139
  function formatTab(name, status) {
4106
4140
  return `${STATUS_ICONS[status]} ${name}`;
4107
4141
  }
@@ -4115,11 +4149,6 @@ function formatDescription(status, exitCode, restartCount) {
4115
4149
  }
4116
4150
  return desc;
4117
4151
  }
4118
- function getDisplayOrder(originalNames, statuses) {
4119
- const active = originalNames.filter((n) => !TERMINAL_STATUSES.has(statuses.get(n)));
4120
- const terminal = originalNames.filter((n) => TERMINAL_STATUSES.has(statuses.get(n)));
4121
- return [...active, ...terminal];
4122
- }
4123
4152
  function resolveOptionColors(names, statuses, processColors, inputWaiting, erroredProcesses, searchMatchProcesses) {
4124
4153
  return names.map((name) => {
4125
4154
  const status = statuses.get(name);
@@ -4209,12 +4238,14 @@ class TabBar {
4209
4238
  statuses;
4210
4239
  baseDescriptions;
4211
4240
  processColors;
4241
+ reorderByStatus;
4212
4242
  inputWaiting = new Set;
4213
4243
  erroredProcesses = new Set;
4214
4244
  searchMatchCounts = new Map;
4215
- constructor(renderer, names, colors) {
4245
+ constructor(renderer, names, colors, reorderByStatus = false) {
4216
4246
  this.originalNames = names;
4217
4247
  this.names = [...names];
4248
+ this.reorderByStatus = reorderByStatus;
4218
4249
  this.statuses = new Map(names.map((n) => [n, "pending"]));
4219
4250
  this.baseDescriptions = new Map(names.map((n) => [n, "pending"]));
4220
4251
  this.processColors = colors ?? new Map;
@@ -4284,17 +4315,19 @@ class TabBar {
4284
4315
  return this.names.length;
4285
4316
  }
4286
4317
  refreshOptions() {
4287
- const currentIdx = this.renderable.getSelectedIndex();
4288
- const currentName = this.names[currentIdx];
4289
- this.names = getDisplayOrder(this.originalNames, this.statuses);
4318
+ if (this.reorderByStatus) {
4319
+ const currentIdx = this.renderable.getSelectedIndex();
4320
+ const currentName = this.names[currentIdx];
4321
+ this.names = getDisplayOrder(this.originalNames, this.statuses);
4322
+ const newIdx = this.names.indexOf(currentName);
4323
+ if (newIdx >= 0 && newIdx !== currentIdx) {
4324
+ this.renderable.setSelectedIndex(newIdx);
4325
+ }
4326
+ }
4290
4327
  this.renderable.options = this.names.map((n) => ({
4291
4328
  name: formatTab(n, this.statuses.get(n)),
4292
4329
  description: this.getDescription(n)
4293
4330
  }));
4294
- const newIdx = this.names.indexOf(currentName);
4295
- if (newIdx >= 0 && newIdx !== currentIdx) {
4296
- this.renderable.setSelectedIndex(newIdx);
4297
- }
4298
4331
  this.updateOptionColors();
4299
4332
  }
4300
4333
  getDescription(name) {
@@ -4372,7 +4405,7 @@ class App {
4372
4405
  border: false
4373
4406
  });
4374
4407
  const processHexColors = buildProcessHexColorMap(this.names, this.config);
4375
- this.tabBar = new TabBar(this.renderer, this.names, processHexColors);
4408
+ this.tabBar = new TabBar(this.renderer, this.names, processHexColors, this.config.sort === "status");
4376
4409
  const contentRow = new BoxRenderable3(this.renderer, {
4377
4410
  id: "content-row",
4378
4411
  flexDirection: "row",
package/dist/types.d.ts CHANGED
@@ -93,7 +93,8 @@ export interface NumuxConfig<K extends string = string> {
93
93
  watch?: string | string[];
94
94
  /**
95
95
  * Tab display order. `'config'` preserves definition order (package.json script order for wildcards),
96
- * `'alphabetical'` sorts by process name, `'topological'` sorts by dependency tiers.
96
+ * `'alphabetical'` sorts by process name, `'topological'` sorts by dependency tiers,
97
+ * `'status'` uses config order but moves finished/stopped/failed/skipped tabs to the bottom.
97
98
  * @default 'config'
98
99
  */
99
100
  sort?: SortOrder;
@@ -123,7 +124,7 @@ export interface NumuxConfig<K extends string = string> {
123
124
  logDir?: string;
124
125
  processes: Record<K, NumuxProcessConfig<K> | NumuxScriptPattern<K> | string | true>;
125
126
  }
126
- export type SortOrder = 'config' | 'alphabetical' | 'topological';
127
+ export type SortOrder = 'config' | 'alphabetical' | 'topological' | 'status';
127
128
  /** Process config after validation — dependsOn is always normalized to an array */
128
129
  export interface ResolvedProcessConfig extends Omit<NumuxProcessConfig, 'dependsOn' | 'workspaces'> {
129
130
  dependsOn?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "2.16.0",
3
+ "version": "2.16.2",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",