reslop 0.2.3 → 0.2.5

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 (89) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +8 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/capabilities.js +0 -4
  6. package/lib/cli.js +19 -10
  7. package/lib/clipboard.js +45 -4
  8. package/lib/deps-diff.js +61 -118
  9. package/lib/deps.js +166 -235
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +68 -2
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +15 -30
  17. package/lib/editor.js +91 -13
  18. package/lib/files.js +54 -27
  19. package/lib/git-branches.js +107 -154
  20. package/lib/git-deps.js +78 -155
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +9 -8
  24. package/lib/gitlab.js +5 -4
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/core.js +11 -0
  27. package/lib/highlight/css.js +2 -1
  28. package/lib/highlight/csv.js +2 -1
  29. package/lib/highlight/dot.js +3 -12
  30. package/lib/highlight/html.js +2 -1
  31. package/lib/highlight/json.js +2 -1
  32. package/lib/highlight/log.js +5 -11
  33. package/lib/highlight/txt.js +3 -12
  34. package/lib/highlight.js +2 -1
  35. package/lib/keys.js +22 -12
  36. package/lib/manifest.js +4 -6
  37. package/lib/npm-commands.js +82 -24
  38. package/lib/remote.js +21 -20
  39. package/lib/render/branches.js +9 -27
  40. package/lib/render/chrome.js +109 -35
  41. package/lib/render/commits.js +216 -216
  42. package/lib/render/diff.js +160 -47
  43. package/lib/render/files.js +9 -26
  44. package/lib/render/frame.js +97 -118
  45. package/lib/render/notes.js +74 -26
  46. package/lib/render/npm.js +104 -30
  47. package/lib/render/present.js +2 -1
  48. package/lib/render/primitives.js +128 -52
  49. package/lib/render/render.js +2 -1
  50. package/lib/render/unit.js +17 -11
  51. package/lib/report-model.js +6 -4
  52. package/lib/report-parse.js +8 -14
  53. package/lib/report-render.js +38 -21
  54. package/lib/report-run.js +12 -11
  55. package/lib/report-test.js +45 -51
  56. package/lib/report-tools.js +44 -25
  57. package/lib/review-merge.js +102 -131
  58. package/lib/review.js +69 -78
  59. package/lib/select.js +52 -0
  60. package/lib/session/actions.js +72 -87
  61. package/lib/session/branches.js +18 -52
  62. package/lib/session/change-actions.js +175 -147
  63. package/lib/session/commit.js +65 -61
  64. package/lib/session/compose.js +152 -83
  65. package/lib/session/dispatch.js +53 -46
  66. package/lib/session/edit-click.js +57 -0
  67. package/lib/session/files-pane.js +30 -51
  68. package/lib/session/items.js +73 -90
  69. package/lib/session/lifecycle.js +37 -98
  70. package/lib/session/load.js +52 -57
  71. package/lib/session/navigation.js +105 -95
  72. package/lib/session/npm.js +205 -71
  73. package/lib/session/ops.js +200 -45
  74. package/lib/session/pointer.js +33 -8
  75. package/lib/session/progress.js +9 -6
  76. package/lib/session/review.js +35 -53
  77. package/lib/session/tasks.js +292 -0
  78. package/lib/session/terminal.js +17 -19
  79. package/lib/session/unit.js +3 -4
  80. package/lib/session/update.js +76 -96
  81. package/lib/session/view.js +45 -16
  82. package/lib/session/watch.js +47 -27
  83. package/lib/session.js +66 -97
  84. package/lib/source.js +6 -3
  85. package/lib/update.js +5 -13
  86. package/lib/utilities.js +25 -3
  87. package/lib/wrap.js +18 -1
  88. package/package.json +1 -1
  89. package/lib/session/todos.js +0 -293
package/lib/gitlab.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { isPathInScope } = require('./files.js');
3
+ const files = require('./files.js');
4
+ const { isPathInScope } = files;
4
5
  const remote = require('./remote.js');
5
6
  const { isRecord } = remote;
6
7
  const { createClient, listUrl, parseNextPage } = remote;
@@ -160,7 +161,7 @@ const noteToImported = (note, items, paths) => {
160
161
 
161
162
  const discussionToNotes = (discussions, items, paths = []) => {
162
163
  const feedback = [];
163
- const todos = [];
164
+ const tasks = [];
164
165
  const threads = discussions ?? [];
165
166
  for (const discussion of threads) {
166
167
  if (!isRecord(discussion)) continue;
@@ -169,10 +170,10 @@ const discussionToNotes = (discussions, items, paths = []) => {
169
170
  const mapped = noteToImported(note, items, paths);
170
171
  if (!mapped) continue;
171
172
  if (mapped.kind === 'feedback') feedback.push(mapped);
172
- else todos.push(mapped);
173
+ else tasks.push(mapped);
173
174
  }
174
175
  }
175
- return { feedback, todos };
176
+ return { feedback, tasks };
176
177
  };
177
178
 
178
179
  const loadDiscussion = async (mr, options) => {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { SQUOTE, emit, wordStyle, readUntilNl } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { SQUOTE, emit, wordStyle, readUntilNl } = core;
4
5
 
5
6
  const BASH_QUOTE = `${SQUOTE}\``;
6
7
 
@@ -41,6 +41,16 @@ const emit = (out, style, text) => {
41
41
  out.push({ text, style: style || 'plain' });
42
42
  };
43
43
 
44
+ const highlightLines = (highlightLine) => (source) => {
45
+ const lines = source.split('\n');
46
+ const out = [];
47
+ for (let i = 0; i < lines.length; i += 1) {
48
+ if (i) emit(out, 'plain', '\n');
49
+ for (const t of highlightLine(lines[i])) out.push(t);
50
+ }
51
+ return out;
52
+ };
53
+
44
54
  const tokensText = (out) => {
45
55
  let s = '';
46
56
  for (const t of out) s += t.text;
@@ -177,6 +187,7 @@ module.exports = {
177
187
  ANY_QUOTE,
178
188
  EXPECT_STYLE,
179
189
  emit,
190
+ highlightLines,
180
191
  tokensText,
181
192
  wordStyle,
182
193
  peekIdent,
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { QUOTES, emit, readBlockComment, readString } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { QUOTES, emit, readBlockComment, readString } = core;
4
5
 
5
6
  const CSS_NEXT_STYLE = {
6
7
  ':': 'property',
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit } = core;
4
5
 
5
6
  const splitCsv = (line) => {
6
7
  const cells = [];
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit, highlightLines } = core;
4
5
 
5
6
  const highlightDotLine = (line) => {
6
7
  if (!line) return [];
@@ -29,16 +30,6 @@ const highlightDotLine = (line) => {
29
30
  return out;
30
31
  };
31
32
 
32
- const highlight = (source) => {
33
- const lines = source.split('\n');
34
- const out = [];
35
- for (let i = 0; i < lines.length; i += 1) {
36
- if (i) emit(out, 'plain', '\n');
37
- for (const t of highlightDotLine(lines[i])) out.push(t);
38
- }
39
- return out;
40
- };
41
-
42
33
  module.exports = {
43
34
  langs: ['dot'],
44
35
  aliases: {
@@ -50,5 +41,5 @@ module.exports = {
50
41
  yml: 'dot',
51
42
  toml: 'dot',
52
43
  },
53
- highlight,
44
+ highlight: highlightLines(highlightDotLine),
54
45
  };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { SQUOTE, emit, skipQuoted } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { SQUOTE, emit, skipQuoted } = core;
4
5
 
5
6
  const colorHtmlTag = (tag) => {
6
7
  const out = [];
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit, readString } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit, readString } = core;
4
5
 
5
6
  const JSON_LITERAL = {
6
7
  true: 'literal',
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit, highlightLines } = core;
4
5
 
5
6
  const LOG_DATETIME_RE = new RegExp(
6
7
  '^(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}' +
@@ -45,14 +46,7 @@ const highlightLogLine = (line) => {
45
46
  return out;
46
47
  };
47
48
 
48
- const highlight = (source) => {
49
- const lines = source.split('\n');
50
- const out = [];
51
- for (let i = 0; i < lines.length; i += 1) {
52
- if (i) emit(out, 'plain', '\n');
53
- for (const t of highlightLogLine(lines[i])) out.push(t);
54
- }
55
- return out;
49
+ module.exports = {
50
+ langs: ['log'],
51
+ highlight: highlightLines(highlightLogLine),
56
52
  };
57
-
58
- module.exports = { langs: ['log'], highlight };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit, highlightLines } = core;
4
5
 
5
6
  const highlightTxtLine = (line) => {
6
7
  if (!line) return [];
@@ -19,18 +20,8 @@ const highlightTxtLine = (line) => {
19
20
  return out;
20
21
  };
21
22
 
22
- const highlight = (source) => {
23
- const lines = source.split('\n');
24
- const out = [];
25
- for (let i = 0; i < lines.length; i += 1) {
26
- if (i) emit(out, 'plain', '\n');
27
- for (const t of highlightTxtLine(lines[i])) out.push(t);
28
- }
29
- return out;
30
- };
31
-
32
23
  module.exports = {
33
24
  langs: ['txt', 'py', 'md'],
34
25
  aliases: { text: 'txt', plain: 'txt', markdown: 'md', python: 'py' },
35
- highlight,
26
+ highlight: highlightLines(highlightTxtLine),
36
27
  };
package/lib/highlight.js CHANGED
@@ -3,7 +3,8 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const { tokensText } = require('./highlight/core.js');
6
+ const core = require('./highlight/core.js');
7
+ const { tokensText } = core;
7
8
 
8
9
  const PLUGIN_DIR = path.join(__dirname, 'highlight');
9
10
  const SKIP = ['core.js'];
package/lib/keys.js CHANGED
@@ -4,8 +4,8 @@ const actions = require('./session/actions.js');
4
4
 
5
5
  const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS } = actions;
6
6
  const { DIFF_DISABLED, FILES_DISABLED, FILES_GIT_DISABLED } = actions;
7
- const { FILES_HIDDEN, FILES_TODO_DISABLED, TODO_DISABLED, BRANCHES_DISABLED } =
8
- actions;
7
+ const { FILES_HIDDEN, FILES_TASKS_DISABLED } = actions;
8
+ const { TASKS_DISABLED, BRANCHES_DISABLED } = actions;
9
9
  const { COMMITS_DISABLED, UNIT_DISABLED, NPM_DISABLED } = actions;
10
10
  const { disabledActions, diffGitDisabled, branchGitDisabled } = actions;
11
11
  const { commitGitDisabled } = actions;
@@ -65,21 +65,31 @@ const parseCsiSeq = (rest) => {
65
65
  return { params: body.slice(0, i), final: body[i], eat: 2 + i + 1 };
66
66
  };
67
67
 
68
- const ctrlHeld = (modText) => {
68
+ const modifierBits = (modText) => {
69
69
  const mod = parseInt(modText, 10);
70
- return Number.isFinite(mod) && ((mod - 1) & 4) !== 0;
70
+ if (!Number.isFinite(mod)) return 0;
71
+ return mod - 1;
72
+ };
73
+
74
+ const modifiedKey = (base, bits) => {
75
+ const arrow =
76
+ base === 'left' || base === 'right' || base === 'up' || base === 'down';
77
+ const end = base === 'home' || base === 'end';
78
+ if (!arrow && !end) return base;
79
+ const ctrl = (bits & 4) !== 0;
80
+ const shift = (bits & 1) !== 0;
81
+ if (ctrl && shift && arrow) return `ctrl-shift-${base}`;
82
+ if (ctrl && arrow) return `ctrl-${base}`;
83
+ if (shift) return `shift-${base}`;
84
+ return base;
71
85
  };
72
86
 
73
87
  const keyFromCsi = (paramsText, final) => {
74
88
  const params = paramsText ? paramsText.split(';') : [];
75
- if (final === '~') return CSI_TILDE[params[0]] ?? null;
76
- const base = CSI_KEYS[final];
89
+ const base = final === '~' ? CSI_TILDE[params[0]] : CSI_KEYS[final];
77
90
  if (!base) return null;
78
91
  if (params.length < 2) return base;
79
- const arrow =
80
- base === 'left' || base === 'right' || base === 'up' || base === 'down';
81
- if (ctrlHeld(params[1]) && arrow) return `ctrl-${base}`;
82
- return base;
92
+ return modifiedKey(base, modifierBits(params[1]));
83
93
  };
84
94
 
85
95
  const mouseKindFromButton = (button, press) => {
@@ -194,8 +204,8 @@ module.exports = {
194
204
  FILES_DISABLED,
195
205
  FILES_HIDDEN,
196
206
  FILES_GIT_DISABLED,
197
- FILES_TODO_DISABLED,
198
- TODO_DISABLED,
207
+ FILES_TASKS_DISABLED,
208
+ TASKS_DISABLED,
199
209
  BRANCHES_DISABLED,
200
210
  COMMITS_DISABLED,
201
211
  NPM_DISABLED,
package/lib/manifest.js CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('node:path');
4
- const { isHashObject, jsonParse } = require('metautil');
5
- const { parseVersion, cmpVersion } = require('./utilities.js');
4
+ const metautil = require('metautil');
5
+ const { isHashObject, jsonParse } = metautil;
6
+ const utilities = require('./utilities.js');
7
+ const { parseVersion, cmpVersion } = utilities;
6
8
 
7
9
  const MANIFEST = 'package.json';
8
10
  const LOCKFILE = 'package-lock.json';
@@ -308,9 +310,7 @@ const proposedWanted = (out, vul) => {
308
310
  module.exports = {
309
311
  MANIFEST,
310
312
  LOCKFILE,
311
- DEP_KIND,
312
313
  SECTION_KEYS,
313
- posixRel,
314
314
  depFileMeta,
315
315
  relOf,
316
316
  listFilePath,
@@ -319,8 +319,6 @@ module.exports = {
319
319
  lockRootSections,
320
320
  lockEntries,
321
321
  lockPackageCount,
322
- cloneJson,
323
- stringifyJson,
324
322
  mergeDepFile,
325
323
  applyWantedRange,
326
324
  findDeclared,
@@ -2,10 +2,14 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { spawn } = require('node:child_process');
6
- const { jsonParse } = require('metautil');
7
- const { stripAnsi, visibleWidth } = require('./ansi.js');
8
- const { npmBin, npmOpts, spawnBase } = require('./utilities.js');
5
+ const childProcess = require('node:child_process');
6
+ const { spawn } = childProcess;
7
+ const metautil = require('metautil');
8
+ const { jsonParse } = metautil;
9
+ const ansi = require('./ansi.js');
10
+ const { stripAnsi, visibleWidth } = ansi;
11
+ const utilities = require('./utilities.js');
12
+ const { npmBin, npmOpts, spawnBase, unquote, dateStamp } = utilities;
9
13
 
10
14
  const MANIFEST = 'package.json';
11
15
  const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies'];
@@ -124,17 +128,6 @@ const withoutNodeFrame = (line) => {
124
128
  return null;
125
129
  };
126
130
 
127
- const unquote = (value) => {
128
- const text = `${value ?? ''}`;
129
- if (text.length < 2) return text;
130
- const open = text[0];
131
- const close = text[text.length - 1];
132
- const code = open.charCodeAt(0);
133
- const quoted = open === close && (code === 34 || code === 39);
134
- if (!quoted) return text;
135
- return text.slice(1, -1);
136
- };
137
-
138
131
  const objectEnd = (lines, start) => {
139
132
  if (stripAnsi(lines[start]).trim() !== '{') return -1;
140
133
  for (let i = start + 1; i < lines.length; i++) {
@@ -280,14 +273,79 @@ const logFileName = (command, date, index) => {
280
273
  return `${date}-${safe}-${n}.log`;
281
274
  };
282
275
 
283
- const dateStamp = (now) => {
284
- const y = now.getFullYear();
285
- const m = `${now.getMonth() + 1}`.padStart(2, '0');
286
- const d = `${now.getDate()}`.padStart(2, '0');
287
- return `${y}-${m}-${d}`;
276
+ const LOG_DIR = '.log';
277
+ const LOG_STALE_DAYS = 5;
278
+ const DAY_MS = 24 * 60 * 60 * 1000;
279
+ const LOG_DATE = /^(\d{4})-(\d{2})-(\d{2})-/;
280
+
281
+ const logDay = (name) => {
282
+ const match = LOG_DATE.exec(name);
283
+ if (!match) return null;
284
+ const year = Number(match[1]);
285
+ const month = Number(match[2]);
286
+ const day = Number(match[3]);
287
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
288
+ return Date.UTC(year, month - 1, day) / DAY_MS;
288
289
  };
289
290
 
290
- const LOG_DIR = '.log';
291
+ const todayIndex = (now) =>
292
+ Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) / DAY_MS;
293
+
294
+ const isStaleLog = (name, now) => {
295
+ const day = logDay(name);
296
+ if (day === null) return false;
297
+ return todayIndex(now) - day > LOG_STALE_DAYS;
298
+ };
299
+
300
+ const readLogNames = (dir) => {
301
+ try {
302
+ return fs.readdirSync(dir);
303
+ } catch (error) {
304
+ if (error.code === 'ENOENT') return [];
305
+ throw error;
306
+ }
307
+ };
308
+
309
+ const staleLogFiles = (root, now = new Date()) => {
310
+ const dir = path.join(root, LOG_DIR);
311
+ const names = readLogNames(dir);
312
+ const files = [];
313
+ let bytes = 0;
314
+ for (const name of names) {
315
+ if (!name.endsWith('.log') || !isStaleLog(name, now)) continue;
316
+ const full = path.join(dir, name);
317
+ let stat;
318
+ try {
319
+ stat = fs.statSync(full);
320
+ } catch (error) {
321
+ if (error.code === 'ENOENT') continue;
322
+ throw error;
323
+ }
324
+ if (!stat.isFile()) continue;
325
+ bytes += stat.size;
326
+ files.push(full);
327
+ }
328
+ return { bytes, files };
329
+ };
330
+
331
+ const removeStaleLogs = (root, now = new Date()) => {
332
+ const found = staleLogFiles(root, now);
333
+ for (const full of found.files) fs.rmSync(full, { force: true });
334
+ return found.bytes;
335
+ };
336
+
337
+ const scaled = (value) => {
338
+ if (value >= 10) return `${Math.round(value)}`;
339
+ return `${Math.round(value * 10) / 10}`;
340
+ };
341
+
342
+ const formatSize = (bytes) => {
343
+ const size = bytes > 0 ? bytes : 0;
344
+ if (size < 1024) return `${size}b`;
345
+ const kb = size / 1024;
346
+ if (kb < 1024) return `${scaled(kb)}k`;
347
+ return `${scaled(kb / 1024)}m`;
348
+ };
291
349
 
292
350
  const nextLogFile = (root, command, now = new Date()) => {
293
351
  const date = dateStamp(now);
@@ -327,8 +385,6 @@ const parseScriptLine = (text) => {
327
385
  return { name, command };
328
386
  };
329
387
 
330
- const scriptLine = (entry) => `${entry.name}: ${entry.command}`;
331
-
332
388
  const orderedScripts = (pkg) => {
333
389
  const scripts = pkg.scripts;
334
390
  if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
@@ -497,8 +553,10 @@ module.exports = {
497
553
  reduceOutput,
498
554
  logFileName,
499
555
  writeLog,
556
+ staleLogFiles,
557
+ removeStaleLogs,
558
+ formatSize,
500
559
  parseScriptLine,
501
- scriptLine,
502
560
  saveScript,
503
561
  removeScript,
504
562
  reorderScript,
package/lib/remote.js CHANGED
@@ -1,10 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const { delay, jsonParse, isHashObject } = require('metautil');
4
- const { oneLine } = require('./utilities.js');
5
- const { itemPath, isPathInScope } = require('./files.js');
6
- const { parseDiff, itemsFromFiles, isCtxType } = require('./diff/diff.js');
7
- const { foldDepItems } = require('./deps.js');
3
+ const metautil = require('metautil');
4
+ const { delay, jsonParse, isHashObject } = metautil;
5
+ const utilities = require('./utilities.js');
6
+ const { oneLine } = utilities;
7
+ const files = require('./files.js');
8
+ const { itemPath, isPathInScope } = files;
9
+ const diff = require('./diff/diff.js');
10
+ const { parseDiff, itemsFromFiles, isCtxType } = diff;
11
+ const deps = require('./deps.js');
12
+ const { foldDepItems } = deps;
8
13
 
9
14
  const RETRY_ATTEMPTS = 3;
10
15
  const RETRY_DELAY_MS = 200;
@@ -323,13 +328,15 @@ const changeFiles = (items) => {
323
328
 
324
329
  const loadChange = async (ref, options, source) => {
325
330
  const { client, url, diffUrl = url, diffAccept } = source;
326
- const paths = options.paths ?? [];
327
- const request = {
328
- fetch: options.fetch ?? globalThis.fetch,
329
- token: options.token ?? '',
330
- retry: options.retry ?? {},
331
- signal: options.signal,
332
- };
331
+ const {
332
+ cwd,
333
+ paths = [],
334
+ fetch = globalThis.fetch,
335
+ token = '',
336
+ retry = {},
337
+ signal,
338
+ } = options;
339
+ const request = { fetch, token, retry, signal };
333
340
  const jsonPage = await client.request(url, request);
334
341
  const data = client.parseJson(jsonPage.body, `${client.subject} payload`);
335
342
  const diffPage = await client.request(diffUrl, {
@@ -340,7 +347,7 @@ const loadChange = async (ref, options, source) => {
340
347
  const items = foldDepItems(itemsFromFiles(files, 'pr'));
341
348
  const change = source.toChange(ref, data, items);
342
349
  const sourceLabel = `${source.prefix}${change.number}`;
343
- let imported = { feedback: [], todos: [] };
350
+ let imported = { feedback: [], tasks: [] };
344
351
  try {
345
352
  const discussion = await source.loadDiscussion(ref, request);
346
353
  imported = source.discussionToNotes(discussion, items, paths);
@@ -348,26 +355,20 @@ const loadChange = async (ref, options, source) => {
348
355
  if (isAbortError(error, request.signal)) throw error;
349
356
  // Discussion import is optional.
350
357
  }
351
- return { top: options.cwd, items, sourceLabel, change, imported };
358
+ return { top: cwd, items, sourceLabel, change, imported };
352
359
  };
353
360
 
354
361
  module.exports = {
355
- RETRY_ATTEMPTS,
356
- RETRY_DELAY_MS,
357
- TRANSIENT_STATUS,
358
362
  isRecord,
359
363
  isAbortError,
360
364
  createTransport,
361
- headerValue,
362
365
  parseLinkNext,
363
366
  parseNextPage,
364
367
  createClient,
365
368
  listUrl,
366
- findItemForLine,
367
369
  importedText,
368
370
  importedTodo,
369
371
  noteFromLocation,
370
- changePath,
371
372
  filterChangeFiles,
372
373
  changeFiles,
373
374
  loadChange,
@@ -1,11 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('../ansi.js');
4
+ const files = require('../files.js');
4
5
  const primitives = require('./primitives.js');
6
+ const { shortAge } = files;
5
7
  const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
6
8
 
7
9
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
8
- const { FILE_MARK, paneResult } = primitives;
10
+ const { FILE_MARK } = primitives;
9
11
  const { paintCursorList, paintListEdit } = primitives;
10
12
  const { branchLabel } = primitives;
11
13
 
@@ -61,7 +63,7 @@ const branchRowFg = (entry, selected) => {
61
63
 
62
64
  const branchRowFields = (entry) => {
63
65
  const sha = entry.sha ?? '';
64
- const date = entry.date ?? '';
66
+ const date = shortAge(entry.date ?? '');
65
67
  const gone = entry.gone ? 'gone' : '';
66
68
  const ahead = entry.ahead ? `⇡${entry.ahead}` : '';
67
69
  const behind = entry.behind ? `⇣${entry.behind}` : '';
@@ -110,9 +112,6 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
110
112
  return out;
111
113
  };
112
114
 
113
- const paintBranchEdit = (compose, width, color) =>
114
- paintListEdit(compose, width, color);
115
-
116
115
  const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
117
116
  const branches = view.branches ?? [];
118
117
  const cols = measureColumns(branches, branchRowFields, BRANCH_COL_ALIGN);
@@ -124,28 +123,11 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
124
123
  const cursor = view.branchCursor ?? 0;
125
124
  const compose = view.compose;
126
125
  const editing = compose && compose.kind === 'branch';
127
- const tail = editing ? paintBranchEdit(compose, width, color) : null;
128
- const paintRow = (entry, rowWidth, rowColor, selected) =>
129
- paintBranchRow(entry, rowWidth, rowColor, selected, cols);
130
- const painted = paintCursorList(
131
- branches,
132
- cursor,
133
- width,
134
- color,
135
- bodyH,
136
- headerLines,
137
- paintRow,
138
- tail,
139
- view.listScroll,
140
- );
141
- return paneResult({
142
- body: painted.body,
143
- fileHits: painted.fileHits,
144
- todoOwners: [],
145
- cursor: painted.cursor,
146
- splitBody: false,
147
- listScroll: painted.offset,
148
- });
126
+ const tail = editing ? paintListEdit(compose, width, color) : null;
127
+ const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
128
+ const paintRow = (entry, selected) =>
129
+ paintBranchRow(entry, width, color, selected, cols);
130
+ return paintCursorList(branches, cursor, pane, paintRow, tail);
149
131
  };
150
132
 
151
133
  module.exports = { paintBodyBranches };