reslop 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +504 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +16 -0
  7. package/lib/agents/models.js +460 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +123 -96
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +475 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +111 -48
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +15 -43
  47. package/lib/input/line-edit.js +77 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +640 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +319 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +46 -14
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +9 -0
  81. package/lib/review/format.js +426 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +958 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -39
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +181 -145
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +85 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +75 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +156 -63
  120. package/lib/session/tasks.js +320 -65
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +2 -2
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -0,0 +1,85 @@
1
+ {
2
+ "busy": [
3
+ "pulling",
4
+ "pushing",
5
+ "loading",
6
+ "importing",
7
+ "checking npm",
8
+ "updating reslop",
9
+ "npm i",
10
+ "npm uninstall",
11
+ "npm audit fix",
12
+ "running",
13
+ "checking out",
14
+ "creating branch",
15
+ "rebasing",
16
+ "dropping",
17
+ "committing",
18
+ "updating",
19
+ "rewording",
20
+ "force pushing"
21
+ ],
22
+ "info": [
23
+ "copied",
24
+ "staged",
25
+ "unstaged",
26
+ "not staged",
27
+ "dropped",
28
+ "already staged",
29
+ "read only",
30
+ "transitive",
31
+ "nothing to review",
32
+ "nothing to commit",
33
+ "not installed",
34
+ "no plan",
35
+ "returned",
36
+ "stopped",
37
+ "started",
38
+ "unified",
39
+ "mixed",
40
+ "side-by-side",
41
+ "line numbers",
42
+ "no line numbers",
43
+ "dark",
44
+ "light",
45
+ "saved",
46
+ "reloaded",
47
+ "not a diff block",
48
+ "file scope",
49
+ "diff mode",
50
+ "committed",
51
+ "amended",
52
+ "reworded",
53
+ "fixup",
54
+ "updated",
55
+ "imported",
56
+ "pulled",
57
+ "pushed",
58
+ "force pushed",
59
+ "checked out",
60
+ "created",
61
+ "empty branch name",
62
+ "empty commit message",
63
+ "terminated",
64
+ "verbose",
65
+ "filtered",
66
+ "fast on",
67
+ "fast off",
68
+ "logged in",
69
+ "logging in",
70
+ "ignored",
71
+ "already ignored",
72
+ "brief",
73
+ "full"
74
+ ],
75
+ "infoPrefix": [
76
+ "checked out ",
77
+ "created ",
78
+ "rebased onto ",
79
+ "dropped ",
80
+ "exit ",
81
+ "model ",
82
+ "updated ",
83
+ "installed "
84
+ ]
85
+ }
@@ -1,45 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const metautil = require('metautil');
4
- const { bytesToSize } = metautil;
5
- const files = require('../files.js');
6
- const { relativeAge } = files;
7
- const ansi = require('../ansi.js');
8
- const { visibleWidth, graphemes, graphemeWidth } = ansi;
3
+ const { num } = require('../common/format.js');
4
+ const { visibleWidth, graphemes, graphemeWidth } = require('../term/ansi.js');
9
5
  const tiles = require('./tiles.js');
10
6
  const { seg, segmentsWidth, clipSegments, titleSegments } = tiles;
11
- const tree = require('../dashboard/tree.js');
12
- const { HEAT_MS } = tree;
7
+ const { HEAT_MS } = require('../dashboard/tree.js');
13
8
 
14
9
  const MIN_FLEX = 6;
15
- const SECONDS_AGO = /^\d+s ago$/;
16
-
17
- const trimmed = (value) =>
18
- value >= 10 ? `${Math.round(value)}` : `${Math.round(value * 10) / 10}`;
19
-
20
- const num = (value) => {
21
- const n = Math.max(0, Math.round(value));
22
- if (n >= 1e6) return `${trimmed(n / 1e6)}m`;
23
- if (n >= 1e4) return `${trimmed(n / 1e3)}k`;
24
- return `${n}`;
25
- };
26
-
27
- const size = (bytes) => bytesToSize(bytes > 0 ? bytes : 0);
28
-
29
- const ago = (at, now) => {
30
- if (!at) return '';
31
- const text = relativeAge(at, now);
32
- return SECONDS_AGO.test(text) ? '<1m ago' : text;
33
- };
34
-
35
- const duration = (ms) => {
36
- const value = Math.max(0, ms);
37
- if (value < 1000) return `${Math.round(value)}ms`;
38
- if (value < 60000) return `${(value / 1000).toFixed(1)}s`;
39
- const minutes = Math.floor(value / 60000);
40
- const seconds = Math.floor((value % 60000) / 1000);
41
- return `${minutes}m${`${seconds}`.padStart(2, '0')}s`;
42
- };
43
10
 
44
11
  const isHot = (at, now) => at > 0 && now - at < HEAT_MS;
45
12
 
@@ -49,7 +16,15 @@ const delta = (value, now, at) => {
49
16
  return seg(`▼${num(-value)}`, 'del', true);
50
17
  };
51
18
 
52
- const waiting = (text) => ({ badge: [], lines: [[seg(text, 'muted')]] });
19
+ const notice = (text, titleLine) => {
20
+ const rows = Array.isArray(text) ? text : [text];
21
+ const lines = rows.map((row) => [seg(row, 'muted')]);
22
+ const block = { badge: [], center: true, lines };
23
+ if (titleLine) block.titleLine = titleLine;
24
+ return block;
25
+ };
26
+
27
+ const waiting = (text) => notice(text);
53
28
 
54
29
  const cell = (text, tone = 'text', align = 'l', bold = false) => ({
55
30
  segs: [seg(text, tone, bold)],
@@ -246,23 +221,6 @@ const titleAside = (tile, aside, width, badge = []) => {
246
221
  return [...left, seg(' '.repeat(Math.max(1, gap))), ...aside];
247
222
  };
248
223
 
249
- const stat = (label, value, tone = 'text', bold = false) => ({
250
- label,
251
- value: typeof value === 'string' ? [seg(value, tone, bold)] : value,
252
- });
253
-
254
- const pairRows = (items, perRow = 2) => {
255
- const rows = [];
256
- for (let i = 0; i < items.length; i += perRow) {
257
- const row = [];
258
- for (const item of items.slice(i, i + perRow)) {
259
- row.push(cell(item.label, 'muted'), cellSegs(item.value));
260
- }
261
- rows.push(row);
262
- }
263
- return rows;
264
- };
265
-
266
224
  const pickGroups = (folders, exts, rows) => {
267
225
  const count = Math.max(0, rows);
268
226
  const dirs = folders.slice(0, count);
@@ -275,19 +233,14 @@ const labelOf = (key, isDir) => {
275
233
  };
276
234
 
277
235
  module.exports = {
278
- num,
279
- size,
280
- ago,
281
- duration,
282
236
  isHot,
283
237
  delta,
238
+ notice,
284
239
  waiting,
285
240
  cell,
286
241
  cellSegs,
287
242
  flexCell,
288
243
  tableLines,
289
- stat,
290
- pairRows,
291
244
  pickGroups,
292
245
  labelOf,
293
246
  titleAside,
@@ -1,18 +1,25 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('../ansi.js');
4
- const { THEME, paint, visibleWidth, truncateVisible } = ansi;
3
+ const { clamp } = require('../common/utilities.js');
4
+
5
+ const {
6
+ THEME,
7
+ paint,
8
+ visibleWidth,
9
+ truncateVisible,
10
+ } = require('../term/ansi.js');
5
11
 
6
12
  const GAP_X = 2;
7
13
  const GAP_Y = 1;
8
14
  const MIN_W = 34;
9
15
  const MIN_H = 6;
10
16
  const MAX_COLS = 4;
17
+ const MAX_NORMAL = 3;
18
+ const MAX_WIDE = 2;
11
19
  const RULE = '─';
12
20
  const ELLIPSIS = '…';
13
21
  const BAR_ON = '█';
14
22
  const BAR_OFF = '░';
15
- const SPARK = '▁▂▃▄▅▆▇█';
16
23
  const SPINNER = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
17
24
 
18
25
  const seg = (text, tone = 'text', bold = false) => ({
@@ -36,8 +43,13 @@ const toneColors = () => ({
36
43
  pkg: THEME.pkgFg,
37
44
  chain: THEME.pkgChainFg,
38
45
  heat: THEME.heatFg,
46
+ heat1: THEME.heat1,
47
+ heat2: THEME.heat2,
48
+ heat3: THEME.heat3,
49
+ heat4: THEME.heat4,
39
50
  blink: THEME.heatBlinkFg,
40
51
  key: THEME.dashKeyFg,
52
+ caption: THEME.dashCaptionFg,
41
53
  });
42
54
 
43
55
  const headColors = () => ({
@@ -91,7 +103,7 @@ const paintSegments = (segs, width, bg, color, fill = ' ', colors = null) => {
91
103
  };
92
104
 
93
105
  const barSegments = (ratio, width, tone = 'add') => {
94
- const clamped = Math.min(1, Math.max(0, Number.isFinite(ratio) ? ratio : 0));
106
+ const clamped = clamp(Number.isFinite(ratio) ? ratio : 0, 0, 1);
95
107
  const on = Math.round(clamped * width);
96
108
  return [
97
109
  seg(BAR_ON.repeat(on), tone),
@@ -99,52 +111,67 @@ const barSegments = (ratio, width, tone = 'add') => {
99
111
  ];
100
112
  };
101
113
 
102
- const sparkline = (values, width) => {
103
- const count = values.length;
104
- if (!count || width < 1) return '';
105
- const max = Math.max(...values);
106
- const top = SPARK.length - 1;
107
- let out = '';
108
- for (let i = 0; i < width; i++) {
109
- const span = width <= 1 ? count - 1 : (i * (count - 1)) / (width - 1);
110
- const at = Math.round(span);
111
- const level = max > 0 ? Math.round((values[at] / max) * top) : 0;
112
- out += SPARK[level];
113
- }
114
- return out;
114
+ const RUN_ICON = {
115
+ passed: ['✔', 'add'],
116
+ failed: ['✖', 'error'],
117
+ aborted: ['⊘', 'muted'],
115
118
  };
116
119
 
117
120
  const spinner = (frame) => SPINNER[Math.abs(frame ?? 0) % SPINNER.length];
118
121
 
122
+ const blinkTone = (frame) => (Math.abs(frame) % 2 ? 'hot' : 'warn');
123
+
119
124
  const spread = (total, parts) => {
120
125
  const base = Math.floor(total / parts);
121
126
  const extra = total % parts;
122
127
  return Array.from({ length: parts }, (_, i) => base + (i < extra ? 1 : 0));
123
128
  };
124
129
 
125
- const placeBand = (shown, from, size, width) => {
130
+ const isWide = (tile) => tile.size === 'wide';
131
+
132
+ const rowCap = (row, tile, maxCols) => {
133
+ const wide = isWide(tile) || row.some(isWide);
134
+ const cap = wide ? MAX_WIDE : MAX_NORMAL;
135
+ return Math.min(maxCols, cap);
136
+ };
137
+
138
+ const canFit = (row, tile, maxCols) => row.length < rowCap(row, tile, maxCols);
139
+
140
+ const packRows = (shown, maxCols) => {
141
+ const rows = [];
142
+ let current = [];
143
+ for (const tile of shown) {
144
+ if (current.length && !canFit(current, tile, maxCols)) {
145
+ rows.push(current);
146
+ current = [];
147
+ }
148
+ current.push(tile);
149
+ }
150
+ if (current.length) rows.push(current);
151
+ return rows;
152
+ };
153
+
154
+ const placeBand = (row, width) => {
155
+ const size = row.length;
126
156
  const room = Math.max(0, width - (size - 1) * GAP_X);
127
157
  const widths = spread(room, size);
128
158
  const tiles = [];
129
159
  let x = 0;
130
160
  for (let i = 0; i < size; i++) {
131
- tiles.push({ id: shown[from + i].id, x, w: widths[i] });
161
+ tiles.push({ id: row[i].id, x, w: widths[i] });
132
162
  x += widths[i] + GAP_X;
133
163
  }
134
164
  return tiles;
135
165
  };
136
166
 
137
- const placeTiles = (shown, cols, width, height) => {
138
- const rows = Math.ceil(shown.length / cols);
139
- const sizes = spread(shown.length, rows);
140
- const heights = spread(height - (rows - 1) * GAP_Y, rows);
167
+ const placePacked = (rows, width, height) => {
168
+ const count = Math.max(1, rows.length);
169
+ const heights = spread(height - (count - 1) * GAP_Y, count);
141
170
  const bands = [];
142
- let from = 0;
143
171
  let y = 0;
144
- for (let row = 0; row < rows; row++) {
145
- const tiles = placeBand(shown, from, sizes[row], width);
172
+ for (let row = 0; row < rows.length; row++) {
173
+ const tiles = placeBand(rows[row], width);
146
174
  bands.push({ y, h: Math.max(1, heights[row]), tiles });
147
- from += sizes[row];
148
175
  y += heights[row] + GAP_Y;
149
176
  }
150
177
  return bands;
@@ -152,16 +179,16 @@ const placeTiles = (shown, cols, width, height) => {
152
179
 
153
180
  const layoutTiles = (width, height, tiles) => {
154
181
  const cap = Math.floor((width + GAP_X) / (MIN_W + GAP_X));
155
- const maxCols = Math.max(1, Math.min(MAX_COLS, cap));
182
+ const maxCols = clamp(cap, 1, MAX_COLS);
156
183
  const ranked = [...tiles].sort((left, right) => left.rank - right.rank);
157
184
  for (let count = tiles.length; count >= 1; count--) {
158
185
  const keep = new Set(ranked.slice(0, count).map((tile) => tile.id));
159
186
  const shown = tiles.filter((tile) => keep.has(tile.id));
160
- const cols = Math.min(maxCols, count);
161
- const rows = Math.ceil(count / cols);
162
- const rowH = Math.floor((height - (rows - 1) * GAP_Y) / rows);
187
+ const rows = packRows(shown, maxCols);
188
+ const n = rows.length;
189
+ const rowH = Math.floor((height - (n - 1) * GAP_Y) / n);
163
190
  if (rowH >= MIN_H || count === 1) {
164
- return placeTiles(shown, cols, width, height);
191
+ return placePacked(rows, width, height);
165
192
  }
166
193
  }
167
194
  return [];
@@ -202,18 +229,48 @@ const rowBackground = (index, filled, backs) => {
202
229
  return backs.tile;
203
230
  };
204
231
 
232
+ const centeredSegments = (line, room) => {
233
+ const clipped = clipSegments(line, room);
234
+ const pad = Math.floor((room - segmentsWidth(clipped)) / 2);
235
+ if (pad < 1) return clipped;
236
+ return [seg(' '.repeat(pad)), ...clipped];
237
+ };
238
+
239
+ const centeredBody = (lines, room, inner) => {
240
+ const count = Math.min(lines.length, room);
241
+ const start = Math.floor((room - count) / 2);
242
+ const rows = [];
243
+ for (let i = 0; i < room; i++) {
244
+ const at = i - start;
245
+ const line = at >= 0 && at < count ? lines[at] : null;
246
+ if (!line || !line.length) {
247
+ rows.push([]);
248
+ continue;
249
+ }
250
+ rows.push(centeredSegments(line, inner));
251
+ }
252
+ return rows;
253
+ };
254
+
205
255
  const paintTile = (tile, content, width, height, color, backs = null) => {
206
256
  const shade = backs ?? {
207
257
  head: THEME.dashHeadBg,
208
258
  tile: THEME.dashTileBg,
209
259
  row: THEME.dashRowBg,
210
260
  };
211
- const { lines, footer } = content;
261
+ const lines = content.lines ?? [];
262
+ const { footer } = content;
212
263
  const rows = [paintHead(tile, content, width, color, shade.head)];
213
264
  const bodyRoom = footer ? height - 2 : height - 1;
265
+ const inner = Math.max(0, width - 2);
266
+ const centered = content.center ? centeredBody(lines, bodyRoom, inner) : null;
214
267
  for (let i = 0; i < bodyRoom; i++) {
215
- const bg = rowBackground(i, i < lines.length, shade);
216
- rows.push(paintFramed(lines[i] ?? [], width, bg, color));
268
+ let line = [];
269
+ if (centered) line = centered[i];
270
+ else if (lines[i]) line = lines[i];
271
+ const filled = !centered && i < lines.length;
272
+ const bg = rowBackground(i, filled, shade);
273
+ rows.push(paintFramed(line, width, bg, color));
217
274
  }
218
275
  if (footer && height > 1) {
219
276
  const bg = rowBackground(bodyRoom, true, shade);
@@ -272,12 +329,13 @@ const equalGrid = (width, height, count, minW = MIN_W, minH = MIN_H) => {
272
329
  const shown = Math.min(safe, cap);
273
330
  const score = shown * 1000000 + size.tileW * size.tileH;
274
331
  if (!best || score > best.score) {
275
- best = { cols, rows, cap, ...size, score };
332
+ best = { width, cols, rows, cap, ...size, score };
276
333
  }
277
334
  }
278
335
  }
279
336
  if (best) return best;
280
337
  return {
338
+ width,
281
339
  cols: 1,
282
340
  rows: 1,
283
341
  cap: 1,
@@ -290,7 +348,7 @@ const equalGrid = (width, height, count, minW = MIN_W, minH = MIN_H) => {
290
348
  const pageWindow = (count, cursor, cap) => {
291
349
  const room = Math.max(1, cap);
292
350
  if (count <= room) return { start: 0, end: count };
293
- const at = Math.min(Math.max(0, cursor), count - 1);
351
+ const at = clamp(cursor, 0, count - 1);
294
352
  let start = Math.floor(at / room) * room;
295
353
  if (start + room > count) start = count - room;
296
354
  return { start, end: start + room };
@@ -300,22 +358,24 @@ const placeEqualTiles = (tiles, grid) => {
300
358
  const bands = [];
301
359
  const cols = Math.max(1, grid.cols);
302
360
  const rows = Math.ceil(tiles.length / cols);
361
+ const gaps = Math.max(0, cols - 1) * GAP_X;
362
+ const full = grid.width ?? cols * grid.tileW + gaps;
363
+ const room = Math.max(0, full - gaps);
364
+ const widths = spread(room, cols);
303
365
  for (let row = 0; row < rows; row++) {
304
366
  const slice = tiles.slice(row * cols, (row + 1) * cols);
305
367
  if (!slice.length) break;
306
368
  const placed = [];
369
+ let x = 0;
307
370
  for (let i = 0; i < slice.length; i++) {
308
- placed.push({
309
- id: slice[i].id,
310
- x: i * (grid.tileW + GAP_X),
311
- w: grid.tileW,
312
- });
371
+ const w = widths[i];
372
+ placed.push({ id: slice[i].id, x, w });
373
+ x += w + GAP_X;
313
374
  }
314
- bands.push({
315
- y: row * (grid.tileH + GAP_Y),
316
- h: grid.tileH,
317
- tiles: placed,
318
- });
375
+ const y = row * (grid.tileH + GAP_Y);
376
+ const h = grid.tileH;
377
+ const band = { y, h, tiles: placed };
378
+ bands.push(band);
319
379
  }
320
380
  return bands;
321
381
  };
@@ -340,8 +400,9 @@ module.exports = {
340
400
  segmentsWidth,
341
401
  clipSegments,
342
402
  barSegments,
343
- sparkline,
403
+ RUN_ICON,
344
404
  spinner,
405
+ blinkTone,
345
406
  titleSegments,
346
407
  layoutTiles,
347
408
  equalGrid,
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const detect = require('../detect.js');
4
- const diffLib = require('../diff/diff.js');
5
- const primitives = require('./primitives.js');
6
- const diff = require('./diff.js');
7
-
8
- const { detectLang } = detect;
9
- const { sameHunk } = diffLib;
10
- const { paneResult } = primitives;
11
- const { paintDiffLine, expandSoftRows, codeHits } = diff;
3
+ const { detectLang } = require('../highlight/highlight.js');
4
+ const { sameHunk } = require('../diff/diff.js');
5
+ const { paneResult, lazyRows } = require('./primitives.js');
6
+ const {
7
+ paintDiffLine,
8
+ expandSoftRows,
9
+ codeHits,
10
+ viewDigits,
11
+ } = require('./diff.js');
12
12
 
13
13
  const paintBodyUnit = (view, width, color) => {
14
14
  const { item, codeOverlay: overlay } = view;
@@ -17,14 +17,33 @@ const paintBodyUnit = (view, width, color) => {
17
17
  view.mode === 'compose' && typeof overlay?.cursor === 'number';
18
18
  const scrollCol = (editing ? overlay.scrollCol : 0) ?? 0;
19
19
  const lines = view.unitLines ?? [];
20
- const source = editing ? lines : expandSoftRows(lines, width, false, true);
21
- const body = source.map((line) => {
20
+ const digits = viewDigits(view);
21
+ let source = lines;
22
+ if (!editing) source = expandSoftRows(lines, width, false, true, digits);
23
+ const body = lazyRows(source.length, (index) => {
24
+ const line = source[index];
22
25
  const changed = line.type === 'add' || line.type === 'del';
23
26
  const outside = changed && !(item && sameHunk(line.item, item));
24
27
  const row = editing || outside ? { ...line, mark: ' ' } : line;
25
- return paintDiffLine(row, width, color, row.origin, lang, scrollCol);
28
+ return paintDiffLine(
29
+ row,
30
+ width,
31
+ color,
32
+ row.origin,
33
+ lang,
34
+ scrollCol,
35
+ digits,
36
+ );
26
37
  });
27
- const hits = codeHits(source, false, width, overlay, editing, scrollCol);
38
+ const hits = codeHits(
39
+ source,
40
+ false,
41
+ width,
42
+ overlay,
43
+ editing,
44
+ scrollCol,
45
+ digits,
46
+ );
28
47
  return paneResult({ body, ...hits });
29
48
  };
30
49
 
@@ -1,10 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('./ansi.js');
4
- const { stripAnsi } = ansi;
5
- const commands = require('./npm-commands.js');
6
-
7
- const { traceLine, relativize } = commands;
3
+ const { stripAnsi } = require('../term/ansi.js');
4
+ const { traceLine, relativize } = require('../runs/output.js');
5
+ const { trimText } = require('../common/utilities.js');
8
6
 
9
7
  const summary = () => ({
10
8
  tests: null,
@@ -59,7 +57,7 @@ const userTrace = (text, root) => {
59
57
  };
60
58
 
61
59
  const normalizeMessage = (message) => {
62
- const text = `${message ?? ''}`.trim().replace(/\s+/g, ' ');
60
+ const text = trimText(message).replace(/\s+/g, ' ');
63
61
  return text.replace(/\s+\S+:\d+:\d+$/, '');
64
62
  };
65
63
 
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('./ansi.js');
4
- const { stripAnsi } = ansi;
5
- const model = require('./report-model.js');
6
- const testParse = require('./report-test.js');
7
- const toolParse = require('./report-tools.js');
3
+ const { jsonParse } = require('metautil');
4
+ const { stripAnsi } = require('../term/ansi.js');
5
+ const model = require('./model.js');
6
+ const testParse = require('./test.js');
7
+ const toolParse = require('./tools.js');
8
8
 
9
9
  const { problem, report, userTrace, groupReport, envelope } = model;
10
10
  const { parseTap, parseNodeJsonl, parseTestJson, parseSpec } = testParse;
@@ -27,11 +27,7 @@ const toolHint = (program, args) => {
27
27
  const parseJson = (text) => {
28
28
  const trimmed = text.trim();
29
29
  if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) return null;
30
- try {
31
- return JSON.parse(trimmed);
32
- } catch {
33
- return null;
34
- }
30
+ return jsonParse(trimmed);
35
31
  };
36
32
 
37
33
  const parseJsonReports = (text, root) => {
@@ -91,10 +87,9 @@ const mergeReports = (left, right) => {
91
87
  const buildDocument = (stdout, stderr, root, exit, hint) => {
92
88
  const out = stripAnsi(stdout);
93
89
  const err = stripAnsi(stderr);
94
- const reports = mergeReports(
95
- parseReports(out, root),
96
- parseReports(err, root),
97
- );
90
+ const fromOut = parseReports(out, root);
91
+ const fromErr = parseReports(err, root);
92
+ const reports = mergeReports(fromOut, fromErr);
98
93
  const text = out.trim() ? out : err;
99
94
  const grouped = withHint(reports, text, exit, hint).map(groupReport);
100
95
  const rest = stderrRest(err, grouped);