reslop 0.2.7 → 0.3.1

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 +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -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} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -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 +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -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 +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -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} +48 -19
  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 +14 -0
  81. package/lib/review/format.js +420 -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 +976 -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 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -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} +158 -63
  120. package/lib/session/tasks.js +321 -66
  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} +4 -3
  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
@@ -1,28 +1,25 @@
1
1
  'use strict';
2
2
 
3
- const files = require('../files.js');
4
- const { shortAge } = files;
5
- const npmInfo = require('../dashboard/npm.js');
6
- const { TOP_PACKAGES } = npmInfo;
7
- const utilities = require('../utilities.js');
8
- const { parseVersion, cmpVersion } = utilities;
9
- const ansi = require('../ansi.js');
10
- const { THEME, visibleWidth } = ansi;
11
- const { minuteOf } = require('../dashboard/model.js');
12
- const tiles = require('./tiles.js');
13
- const { seg, barSegments, spinner } = tiles;
14
- const table = require('./dash-table.js');
15
- const { num, size, delta, waiting, cell, cellSegs, flexCell } = table;
16
- const { tableLines, pickGroups, labelOf, stat, pairRows } = table;
3
+ const { shortAge, num, size } = require('../../common/format.js');
4
+ const { TOP_PACKAGES } = require('../../dashboard/npm.js');
5
+ const { clamp } = require('../../common/utilities.js');
6
+ const { visibleWidth, truncateVisible } = require('../../term/ansi.js');
7
+ const { minuteOf } = require('../../dashboard/model.js');
8
+ const { seg, barSegments, spinner } = require('../tiles.js');
9
+ const table = require('../table.js');
10
+ const { delta, waiting, notice, cell } = table;
11
+ const { cellSegs, flexCell, tableLines, pickGroups, labelOf } = table;
17
12
  const { titleAside, captionOver } = table;
13
+ const { cmpText, hasRange, inRange } = require('../../deps/manifest.js');
18
14
 
19
15
  const BAR_W = 6;
20
16
  const FOLDER = '📁';
21
17
  const MARK_GAP = 1;
22
- const ACTIVITY_LABEL = 'activity ';
18
+ const ACTIVITY_LABEL = 'activity';
23
19
  const PULSE = '∙';
20
+ const TRACE = '─';
24
21
  const ACTIVITY_MARK = [
25
- ['fail', 'x'],
22
+ ['fail', '⨯'],
26
23
  ['commit', '◉'],
27
24
  ['pass', '*'],
28
25
  ['abort', '!'],
@@ -30,6 +27,17 @@ const ACTIVITY_MARK = [
30
27
  ['rebase', '↻'],
31
28
  ['npm', '✦'],
32
29
  ];
30
+ const MARK_TONE = {
31
+ '⨯': 'error',
32
+ '◉': 'key',
33
+ '*': 'add',
34
+ '!': 'error',
35
+ '⎇': 'sha',
36
+ '↻': 'sha',
37
+ '✦': 'key',
38
+ '+': 'add',
39
+ '-': 'del',
40
+ };
33
41
  const UNKNOWN = '…';
34
42
 
35
43
  const share = (value, total) => (total > 0 ? value / total : 0);
@@ -84,14 +92,15 @@ const filesTotal = (data, ctx) => {
84
92
  const filesBlock = (model, width, height, ctx, tile) => {
85
93
  const data = model.files;
86
94
  if (!data.ready) return waiting('scanning…');
95
+ if (!data.total.files) return notice('no files');
87
96
  const picked = pickGroups(data.dirs, data.exts, height - 1);
88
97
  const labels = [
89
98
  cell(''),
90
99
  cell(''),
91
- cell('files', 'muted', 'r'),
92
- cell('size', 'muted', 'r'),
93
- cell('lines', 'muted', 'r'),
94
- cell(''),
100
+ cell('files', 'caption', 'r'),
101
+ cell('size', 'caption', 'r'),
102
+ cell('lines', 'caption', 'r'),
103
+ cell('share', 'caption', 'r'),
95
104
  ];
96
105
  const rows = [labels];
97
106
  for (const group of picked.dirs) rows.push(fileRow(group, true, data));
@@ -138,14 +147,6 @@ const heatIndex = (level) => {
138
147
  return 0;
139
148
  };
140
149
 
141
- const heatColor = (index) => {
142
- if (index === 1) return THEME.heat1;
143
- if (index === 2) return THEME.heat2;
144
- if (index === 3) return THEME.heat3;
145
- if (index === 4) return THEME.heat4;
146
- return null;
147
- };
148
-
149
150
  const markGlyph = (marks) => {
150
151
  const list = marks ?? [];
151
152
  for (const [kind, glyph] of ACTIVITY_MARK) {
@@ -165,63 +166,56 @@ const diffGlyph = (bucket) => {
165
166
 
166
167
  const pulseGlyph = (level) => {
167
168
  const index = heatIndex(level);
168
- if (index >= 4) return '◎';
169
+ if (index >= 4) return '●';
169
170
  if (index >= 3) return '•';
170
171
  return '◦';
171
172
  };
172
173
 
173
- const activityCell = (bucket, blink, track, pulse) => {
174
+ const activityCell = (bucket, blink, pulse) => {
174
175
  const level = bucket ? bucket.level : 0;
175
176
  const glyph = markGlyph(bucket ? bucket.marks : null);
176
- const index = heatIndex(level);
177
177
  const mark = glyph || diffGlyph(bucket);
178
178
  const phase = blink ? pulseGlyph(level) : PULSE;
179
- const ch = pulse ? phase : mark || ' ';
180
- const item = seg(ch, blink ? 'blink' : 'heat', Boolean(glyph) && !pulse);
181
- let bg = track ? THEME.heatTrack : null;
182
- const heat = heatColor(index);
183
- if (heat) bg = heat;
184
- if (blink) bg = THEME.heatBlink;
185
- item.bg = bg;
186
- return item;
179
+ const ch = pulse ? phase : mark || TRACE;
180
+ if (blink) return seg(ch, 'key', true);
181
+ if (!pulse && mark) {
182
+ return seg(ch, MARK_TONE[mark] || 'sha', Boolean(glyph));
183
+ }
184
+ return seg(ch, 'sha');
187
185
  };
188
186
 
189
187
  const activityCells = (activity, ctx, width) => {
190
188
  const minutes = activity && activity.minutes ? activity.minutes : [];
191
189
  const byAt = new Map();
192
- let start = null;
193
190
  for (const row of minutes) {
194
191
  byAt.set(row.at, row);
195
- if (start === null || row.at < start) start = row.at;
196
192
  }
197
193
  const current = minuteOf(ctx.now);
198
194
  const flash = Boolean(activity && activity.live);
199
195
  const on = Math.abs(ctx.frame ?? 0) % 2 === 1;
196
+ const body = Math.max(0, width - 2);
200
197
  const cells = [];
201
- for (let i = 0; i < width; i++) {
202
- const at = current - (width - 1 - i);
203
- const last = i === width - 1;
198
+ for (let i = 0; i < body; i++) {
199
+ const at = current - (body - 1 - i);
200
+ const last = i === body - 1;
204
201
  const pulse = flash && last;
205
202
  const blink = pulse && on;
206
- const track = start !== null && at >= start;
207
- cells.push(activityCell(byAt.get(at), blink, track, pulse));
203
+ cells.push(activityCell(byAt.get(at), blink, pulse));
208
204
  }
209
- return cells;
205
+ return [seg(' ', 'muted'), ...cells, seg(' ', 'muted')];
210
206
  };
211
207
 
212
208
  const activityFooter = (data, width, ctx) => {
213
- const room = width - 1 - ACTIVITY_LABEL.length;
214
- if (room < 2) return null;
209
+ const lineWidth = width + 1;
210
+ const room = lineWidth - ACTIVITY_LABEL.length;
211
+ if (room < 3) return null;
215
212
  const cells = activityCells(data.activity, ctx, room);
216
- return [seg(ACTIVITY_LABEL, 'muted'), ...cells];
213
+ return [seg(ACTIVITY_LABEL, 'sha'), ...cells];
217
214
  };
218
215
 
219
216
  const diffsBlock = (model, width, height, ctx, tile) => {
220
217
  const data = model.diffs;
221
- if (!data.files) {
222
- const rows = pairRows([stat('changes', 'working tree clean', 'muted')], 1);
223
- return { badge: [], lines: tableLines(rows, width) };
224
- }
218
+ if (!data.files) return notice('working tree clean');
225
219
  const footer = activityFooter(data, width, ctx);
226
220
  const room = footer ? height - 1 : height;
227
221
  const picked = pickGroups(data.dirs, data.exts, room);
@@ -229,37 +223,28 @@ const diffsBlock = (model, width, height, ctx, tile) => {
229
223
  for (const group of picked.dirs) rows.push(diffRow(group, true));
230
224
  for (const group of picked.exts) rows.push(diffRow(group, false));
231
225
  const lines = tableLines(rows, width, 2, true);
232
- return {
233
- titleLine: captionOver(tile, lines[0]),
234
- lines: lines.slice(1),
235
- footer,
236
- footerFlush: true,
237
- };
226
+ const titleLine = captionOver(tile, lines[0]);
227
+ const body = lines.slice(1);
228
+ const footerFlush = true;
229
+ return { titleLine, lines: body, footer, footerFlush };
238
230
  };
239
231
 
240
- const countSegs = (label, value) => [
241
- seg(`${label}: `, 'muted'),
242
- seg(`${value}`, 'text'),
243
- ];
232
+ const compactSize = (bytes) => size(bytes).replaceAll(' ', '');
244
233
 
245
- const markSegs = (value, tone, mark) => {
234
+ const alertSegs = (value, tone, mark) => {
246
235
  if (!value) return [];
247
- return [seg(' '), seg(mark), seg(`${value}`, tone, true)];
236
+ return [seg(' '), seg(mark, tone), seg(` ${value}`, tone, true)];
248
237
  };
249
238
 
250
239
  const npmAside = (data) => {
251
240
  const modules = data.modules;
241
+ const bytes = compactSize(modules.bytes);
242
+ const counts = `deps:${data.deps} dev:${data.dev}`;
243
+ const text = `${counts} all:${modules.count}/${bytes}`;
252
244
  return [
253
- ...countSegs('deps', data.deps),
254
- seg(' '),
255
- ...countSegs('dev', data.dev),
256
- seg(' '),
257
- ...countSegs('all', modules.count),
258
- seg(' (', 'muted'),
259
- seg(size(modules.bytes), 'text'),
260
- seg(')', 'muted'),
261
- ...markSegs(data.audit, 'error', '🚨 '),
262
- ...markSegs(data.outdated, 'warn', '⚠️ '),
245
+ seg(text, 'caption'),
246
+ ...alertSegs(data.audit, 'error', '🚨'),
247
+ ...alertSegs(data.outdated, 'warn', '⚠️'),
263
248
  ];
264
249
  };
265
250
 
@@ -273,38 +258,6 @@ const npmStatus = (data) => {
273
258
  return text;
274
259
  };
275
260
 
276
- const cmpText = (left, right) => {
277
- const a = parseVersion(left);
278
- const b = parseVersion(right);
279
- if (!a || !b) return null;
280
- return cmpVersion(a, b);
281
- };
282
-
283
- const boundHolds = (cmp, op) => {
284
- if (op === '<') return cmp < 0;
285
- if (op === '<=') return cmp <= 0;
286
- if (op === '>') return cmp > 0;
287
- return cmp >= 0;
288
- };
289
-
290
- const inClause = (version, clause) => {
291
- const ver = parseVersion(version);
292
- if (!ver) return false;
293
- const bounds = [...clause.matchAll(/(>=|<=|>|<)\s*(\d+\.\d+\.\d+)/g)];
294
- if (!bounds.length) return false;
295
- for (const match of bounds) {
296
- const bound = parseVersion(match[2]);
297
- if (!bound) return false;
298
- if (!boundHolds(cmpVersion(ver, bound), match[1])) return false;
299
- }
300
- return true;
301
- };
302
-
303
- const inRange = (version, range) =>
304
- `${range ?? ''}`.split('||').some((clause) => inClause(version, clause));
305
-
306
- const hasRange = (range) => /[<>]=?\s*\d+\.\d+\.\d+/.test(`${range ?? ''}`);
307
-
308
261
  const vulnTone = (text, pkg) => {
309
262
  if (!pkg.audit || !text || text === UNKNOWN) return '';
310
263
  const fix = pkg.fix || '';
@@ -369,19 +322,17 @@ const nameTone = (pkg) => {
369
322
  const packageName = (pkg) => {
370
323
  const segs = [seg(pkg.name, nameTone(pkg))];
371
324
  if (pkg.chain) segs.push(seg(` 🢒 ${pkg.chain}`, 'chain'));
372
- return {
373
- segs,
374
- align: 'l',
375
- flex: true,
376
- keep: true,
377
- measure: visibleWidth(pkg.name),
378
- };
325
+ const align = 'l';
326
+ const flex = true;
327
+ const keep = true;
328
+ const measure = visibleWidth(pkg.name);
329
+ return { segs, align, flex, keep, measure };
379
330
  };
380
331
 
381
332
  const versionCell = (text, pkg) => cell(text, versionTone(text, pkg), 'r');
382
333
 
383
334
  const packageHead = () => {
384
- const title = flexCell('');
335
+ const title = flexCell('name');
385
336
  title.keep = true;
386
337
  return [
387
338
  title,
@@ -410,7 +361,8 @@ const packageRow = (pkg, marks) => {
410
361
  const packageLines = (packages, width, limit) => {
411
362
  const all = limit === null;
412
363
  const headed = packages.length > 0 && (all || limit > 1);
413
- const room = all ? packages.length : Math.max(0, limit - (headed ? 1 : 0));
364
+ const head = headed ? 1 : 0;
365
+ const room = all ? packages.length : Math.max(0, limit - head);
414
366
  const shown = packages.slice(0, room);
415
367
  const rows = shown.map((pkg) => packageRow(pkg, all));
416
368
  if (headed) rows.unshift(packageHead());
@@ -424,6 +376,7 @@ const npmContent = (data, width, limit, ctx, tile) => {
424
376
  const titleLine = titleAside(tile, npmAside(data), width, badge);
425
377
  const listed = data.modules && data.modules.packages;
426
378
  const packages = listed ? listed : [];
379
+ if (!packages.length) return notice('no packages', titleLine);
427
380
  const short = limit === null ? packages : packages.slice(0, TOP_PACKAGES);
428
381
  return { titleLine, lines: packageLines(short, width, limit) };
429
382
  };
@@ -431,23 +384,42 @@ const npmContent = (data, width, limit, ctx, tile) => {
431
384
  const npmBlock = (model, width, height, ctx, tile) =>
432
385
  npmContent(model.npm, width, height, ctx, tile);
433
386
 
434
- const TASK_GAP = 2;
387
+ const NAME_GAP = 2;
388
+ const RATIO_GAP = 1;
435
389
 
436
390
  const ratioLabel = (done, total) => `${done}/${total}`;
437
391
 
438
392
  const taskSpecs = (data) => {
439
- const rows = [{ title: '', done: data.done, total: data.total, bold: true }];
393
+ const rows = [];
440
394
  for (const kind of data.kinds) {
441
- rows.push({
442
- title: kind.title,
443
- done: kind.done,
444
- total: kind.total,
445
- bold: false,
446
- });
395
+ const { title, done, total } = kind;
396
+ const bold = false;
397
+ const row = { title, done, total, bold };
398
+ rows.push(row);
447
399
  }
400
+ const title = 'Total';
401
+ const titleTone = 'add';
402
+ const { done, total } = data;
403
+ const bold = true;
404
+ const row = { title, titleTone, done, total, bold };
405
+ rows.push(row);
448
406
  return rows;
449
407
  };
450
408
 
409
+ const taskRows = (specs, room) => {
410
+ if (room < 1) return [];
411
+ const total = specs.at(-1);
412
+ const kinds = specs.slice(0, -1).slice(0, room - 1);
413
+ return [...kinds, total];
414
+ };
415
+
416
+ const planAside = (tile, plan, width) => {
417
+ if (!plan) return [];
418
+ const room = Math.max(0, width - tile.title.length - 1);
419
+ const shown = truncateVisible(plan, room);
420
+ return shown ? [seg(shown, 'caption')] : [];
421
+ };
422
+
451
423
  const widest = (rows, read) => {
452
424
  let width = 0;
453
425
  for (const row of rows) {
@@ -458,32 +430,54 @@ const widest = (rows, read) => {
458
430
  };
459
431
 
460
432
  const taskBarWidth = (width, nameW, ratioW) => {
461
- const room = width - nameW - ratioW - TASK_GAP * 2;
433
+ const room = width - nameW - ratioW - NAME_GAP - RATIO_GAP;
462
434
  return room > 0 ? room : 0;
463
435
  };
464
436
 
437
+ const taskBarSegments = (ratio, width) => {
438
+ if (width < 1) return [];
439
+ if (width < 3) return [seg(' '.repeat(width), 'muted')];
440
+ const inner = width - 2;
441
+ const clamped = clamp(Number.isFinite(ratio) ? ratio : 0, 0, 1);
442
+ const on = Math.round(clamped * inner);
443
+ const off = inner - on;
444
+ const parts = [seg(' ', 'muted')];
445
+ if (on) parts.push(seg(TRACE.repeat(on), 'add'));
446
+ if (off) parts.push(seg(' '.repeat(off), 'muted'));
447
+ parts.push(seg(' ', 'muted'));
448
+ return parts;
449
+ };
450
+
465
451
  const taskLine = (row, barW) => {
466
452
  const tone = row.bold ? 'text' : 'muted';
467
- const line = [cell(row.title, 'text', 'l', row.bold)];
453
+ const titleTone = row.titleTone ?? 'text';
454
+ const titleBold = row.titleTone ? false : row.bold;
455
+ const line = [cell(row.title, titleTone, 'l', titleBold)];
468
456
  if (barW > 0) {
469
- line.push(cellSegs(barSegments(share(row.done, row.total), barW)));
457
+ const ratio = share(row.done, row.total);
458
+ line.push(cellSegs(taskBarSegments(ratio, barW)));
470
459
  }
471
- line.push(cell(ratioLabel(row.done, row.total), tone, 'r', row.bold));
460
+ const count = cell(ratioLabel(row.done, row.total), tone, 'r', row.bold);
461
+ count.gap = RATIO_GAP;
462
+ line.push(count);
472
463
  return line;
473
464
  };
474
465
 
475
466
  const tasksBlock = (model, width, height, ctx, tile) => {
476
- const specs = taskSpecs(model.tasks);
467
+ const data = model.tasks;
468
+ if (!data.total) {
469
+ const aside = planAside(tile, data.plan, width);
470
+ return notice('no tasks', titleAside(tile, aside, width));
471
+ }
472
+ const specs = taskSpecs(data);
477
473
  const nameW = widest(specs, (row) => row.title);
478
474
  const ratioW = widest(specs, (row) => ratioLabel(row.done, row.total));
479
475
  const barW = taskBarWidth(width, nameW, ratioW);
480
- const rows = specs.map((row) => taskLine(row, barW));
481
- const lines = tableLines(rows, width, TASK_GAP);
482
- const shown = Math.max(0, height);
483
- return {
484
- titleLine: captionOver(tile, lines[0]),
485
- lines: lines.slice(1, 1 + shown),
486
- };
476
+ const visible = taskRows(specs, Math.max(0, height));
477
+ const rows = visible.map((row) => taskLine(row, barW));
478
+ const lines = tableLines(rows, width, NAME_GAP);
479
+ const aside = planAside(tile, data.plan, width);
480
+ return { titleLine: titleAside(tile, aside, width), lines };
487
481
  };
488
482
 
489
483
  module.exports = {
@@ -1,30 +1,33 @@
1
1
  'use strict';
2
2
 
3
- const primitives = require('./primitives.js');
4
- const { paintBodyFill, paneResult } = primitives;
5
- const tiles = require('./tiles.js');
3
+ const { paintBodyFill, paneResult } = require('../primitives.js');
4
+ const tiles = require('../tiles.js');
6
5
  const { layoutTiles, paintTile, composeBands, tileHits, pageFill } = tiles;
7
- const blocks = require('./dash-blocks.js');
6
+ const blocks = require('./blocks.js');
8
7
  const { filesBlock, diffsBlock, npmBlock, tasksBlock } = blocks;
9
- const activity = require('./dash-activity.js');
8
+ const activity = require('./activity.js');
10
9
  const { commitsBlock, branchesBlock, runsBlock } = activity;
10
+ const { agentsBlock } = require('../agents.js');
11
+ const { DASH_TILES } = require('../../input/actions.js');
11
12
 
12
- const TILES = [
13
- { id: 'files', key: 'f', title: 'files', rank: 3, build: filesBlock },
14
- { id: 'diffs', key: 'd', title: 'diffs', rank: 0, build: diffsBlock },
15
- { id: 'tasks', key: 't', title: 'tasks', rank: 4, build: tasksBlock },
16
- {
17
- id: 'branches',
18
- key: 'b',
19
- title: 'branches',
20
- rank: 5,
21
- build: branchesBlock,
22
- },
23
- { id: 'commits', key: 'c', title: 'commits', rank: 2, build: commitsBlock },
24
- { id: 'run', key: 'r', title: 'run', rank: 1, build: runsBlock },
25
- { id: 'npm', key: 'n', title: 'npm', rank: 6, build: npmBlock },
26
- ];
27
- const TILE_BY_ID = new Map(TILES.map((tile) => [tile.id, tile]));
13
+ const TILE_VIEW = {
14
+ files: { rank: 3, size: 'normal', build: filesBlock },
15
+ diffs: { rank: 0, size: 'normal', build: diffsBlock },
16
+ tasks: { rank: 4, size: 'normal', build: tasksBlock },
17
+ branches: { rank: 5, size: 'wide', build: branchesBlock },
18
+ commits: { rank: 2, size: 'wide', build: commitsBlock },
19
+ run: { rank: 1, size: 'normal', build: runsBlock },
20
+ npm: { rank: 6, size: 'normal', build: npmBlock },
21
+ agents: { rank: 7, size: 'normal', build: agentsBlock },
22
+ };
23
+
24
+ const TILES = DASH_TILES.map(({ id, key }) => {
25
+ const title = id;
26
+ const view = TILE_VIEW[id];
27
+ return { id, key, title, ...view };
28
+ });
29
+
30
+ const TILE_BY_ID = Object.fromEntries(TILES.map((tile) => [tile.id, tile]));
28
31
  const MARGIN_MIN_H = 12;
29
32
 
30
33
  const paintTiles = (model, bands, color) => {
@@ -32,7 +35,7 @@ const paintTiles = (model, bands, color) => {
32
35
  const ctx = { now: model.now, frame: model.frame };
33
36
  for (const band of bands) {
34
37
  for (const place of band.tiles) {
35
- const tile = TILE_BY_ID.get(place.id);
38
+ const tile = TILE_BY_ID[place.id];
36
39
  const inner = Math.max(0, place.w - 2);
37
40
  const room = Math.max(0, band.h - 1);
38
41
  const content = tile.build(model, inner, room, ctx, tile);