reslop 0.2.4 → 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 (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +1 -2
  3. package/lib/capabilities.js +0 -4
  4. package/lib/clipboard.js +39 -1
  5. package/lib/deps-diff.js +0 -3
  6. package/lib/deps.js +144 -176
  7. package/lib/diff/inline.js +66 -1
  8. package/lib/diff/unit.js +11 -28
  9. package/lib/editor.js +69 -9
  10. package/lib/files.js +17 -16
  11. package/lib/git-deps.js +58 -95
  12. package/lib/github.js +7 -7
  13. package/lib/gitlab.js +3 -3
  14. package/lib/highlight/core.js +11 -0
  15. package/lib/highlight/dot.js +2 -12
  16. package/lib/highlight/log.js +4 -11
  17. package/lib/highlight/txt.js +2 -12
  18. package/lib/keys.js +22 -12
  19. package/lib/manifest.js +0 -4
  20. package/lib/npm-commands.js +1 -22
  21. package/lib/remote.js +1 -7
  22. package/lib/render/branches.js +4 -13
  23. package/lib/render/chrome.js +85 -31
  24. package/lib/render/commits.js +95 -207
  25. package/lib/render/diff.js +31 -1
  26. package/lib/render/files.js +7 -16
  27. package/lib/render/frame.js +95 -110
  28. package/lib/render/notes.js +71 -16
  29. package/lib/render/npm.js +104 -18
  30. package/lib/render/primitives.js +108 -45
  31. package/lib/render/unit.js +4 -1
  32. package/lib/report-parse.js +0 -1
  33. package/lib/report-run.js +0 -1
  34. package/lib/report-test.js +40 -47
  35. package/lib/report-tools.js +0 -7
  36. package/lib/review-merge.js +101 -131
  37. package/lib/review.js +64 -77
  38. package/lib/select.js +52 -0
  39. package/lib/session/actions.js +23 -22
  40. package/lib/session/branches.js +5 -3
  41. package/lib/session/change-actions.js +173 -146
  42. package/lib/session/commit.js +32 -13
  43. package/lib/session/compose.js +120 -77
  44. package/lib/session/dispatch.js +36 -21
  45. package/lib/session/edit-click.js +57 -0
  46. package/lib/session/files-pane.js +24 -24
  47. package/lib/session/items.js +69 -89
  48. package/lib/session/lifecycle.js +31 -96
  49. package/lib/session/load.js +51 -58
  50. package/lib/session/navigation.js +105 -96
  51. package/lib/session/npm.js +158 -77
  52. package/lib/session/ops.js +192 -51
  53. package/lib/session/pointer.js +11 -5
  54. package/lib/session/progress.js +9 -6
  55. package/lib/session/review.js +35 -54
  56. package/lib/session/tasks.js +292 -0
  57. package/lib/session/terminal.js +13 -17
  58. package/lib/session/unit.js +3 -4
  59. package/lib/session/update.js +76 -97
  60. package/lib/session/view.js +17 -14
  61. package/lib/session/watch.js +47 -27
  62. package/lib/session.js +34 -79
  63. package/lib/update.js +1 -5
  64. package/lib/utilities.js +21 -1
  65. package/lib/wrap.js +15 -0
  66. package/package.json +1 -1
  67. package/lib/session/todos.js +0 -293
@@ -3,7 +3,7 @@
3
3
  const ansi = require('../ansi.js');
4
4
  const files = require('../files.js');
5
5
  const { THEME_NAMES, setTheme, themeName } = ansi;
6
- const { isTodosEntry, TODO_FILE } = files;
6
+ const { isTasksEntry, TASKS_FILE } = files;
7
7
  const npm = require('../render/npm.js');
8
8
  const { logViewRows } = npm;
9
9
  const unit = require('./unit.js');
@@ -13,7 +13,7 @@ const { setFileScope, openUnitFile, moveUnitBlock } = unit;
13
13
 
14
14
  const LAYOUTS = ['unified', 'mixed', 'side'];
15
15
 
16
- const current = (ui) => ui.nav.current(ui.items, ui.todoItem);
16
+ const current = (ui) => ui.nav.current(ui.items, ui.tasksItem);
17
17
 
18
18
  const clampIndex = (ui) => ui.nav.clampIndex(ui.items.length);
19
19
 
@@ -32,14 +32,15 @@ const syncFileCursor = (ui) => {
32
32
  const showFiles = (ui) => {
33
33
  const nav = ui.nav;
34
34
  const held =
35
- nav.todoOpen === true ||
35
+ nav.tasksOpen === true ||
36
36
  nav.pane === 'branches' ||
37
37
  nav.pane === 'commits' ||
38
38
  nav.pane === 'npm';
39
39
  nav.pane = 'files';
40
+ nav.resetListScroll('files');
40
41
  nav.clearSelection();
41
42
  followReviewPath(ui);
42
- nav.todoOpen = false;
43
+ nav.tasksOpen = false;
43
44
  if (held) ui.lifecycle.catchUpWatch();
44
45
  };
45
46
 
@@ -54,12 +55,12 @@ const onFiles = (ui) => {
54
55
  if (ui.nav.fileScope === 'file') {
55
56
  const list = ui.fileList();
56
57
  const entry = list[ui.nav.fileCursor];
57
- if (entry && !isTodosEntry(entry)) openUnitFile(ui, entry);
58
+ if (entry && !isTasksEntry(entry)) openUnitFile(ui, entry);
58
59
  return;
59
60
  }
60
61
  ui.nav.pane = 'diff';
61
62
  ui.nav.clearSelection();
62
- if (ui.nav.reviewPath === TODO_FILE) ui.composer.openTodoPage();
63
+ if (ui.nav.reviewPath === TASKS_FILE) ui.composer.openTasksPage();
63
64
  return;
64
65
  }
65
66
  showFiles(ui);
@@ -72,18 +73,18 @@ const onOpenFile = (ui) => {
72
73
  if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
73
74
  if (ui.nav.pane === 'npm') return void ui.npm.run();
74
75
  if (ui.nav.pane !== 'files') {
75
- if (ui.nav.todoOpen) ui.composer.editFocusedTodo();
76
+ if (ui.nav.tasksOpen) ui.composer.editFocusedTask();
76
77
  return;
77
78
  }
78
79
  const list = ui.fileList();
79
80
  const entry = list[ui.nav.fileCursor];
80
81
  if (!entry) return;
81
- if (isTodosEntry(entry)) {
82
- ui.composer.openTodoPage();
82
+ if (isTasksEntry(entry)) {
83
+ ui.composer.openTasksPage();
83
84
  ui.status = '';
84
85
  return;
85
86
  }
86
- ui.nav.todoOpen = false;
87
+ ui.nav.tasksOpen = false;
87
88
  ui.nav.index = entry.openIndex ?? entry.firstIndex;
88
89
  if (ui.nav.fileScope === 'file') {
89
90
  return void openUnitFile(ui, entry);
@@ -107,8 +108,8 @@ const onFileMove = (ui, delta) => {
107
108
  if (next === ui.nav.fileCursor) return;
108
109
  ui.nav.fileCursor = next;
109
110
  const entry = list[next];
110
- if (isTodosEntry(entry)) {
111
- ui.nav.reviewPath = TODO_FILE;
111
+ if (isTasksEntry(entry)) {
112
+ ui.nav.reviewPath = TASKS_FILE;
112
113
  } else {
113
114
  ui.nav.reviewPath = entry.path;
114
115
  ui.nav.index = entry.firstIndex;
@@ -133,8 +134,8 @@ const moveRemaining = (ui, step) => {
133
134
 
134
135
  const onNext = (ui) => {
135
136
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, 1);
136
- if (ui.nav.todoOpen) {
137
- ui.composer.closeTodoPage();
137
+ if (ui.nav.tasksOpen) {
138
+ ui.composer.closeTasksPage();
138
139
  if (ui.items.length) ui.nav.index = 0;
139
140
  ui.nav.scroll = 0;
140
141
  syncReviewPath(ui);
@@ -146,8 +147,8 @@ const onNext = (ui) => {
146
147
 
147
148
  const onPrev = (ui) => {
148
149
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, -1);
149
- if (ui.nav.todoOpen) return;
150
- if (ui.nav.index <= 0) return void ui.composer.openTodoPage();
150
+ if (ui.nav.tasksOpen) return;
151
+ if (ui.nav.index <= 0) return void ui.composer.openTasksPage();
151
152
  moveRemaining(ui, -1);
152
153
  };
153
154
 
@@ -175,8 +176,8 @@ const onScroll = (ui, delta) => {
175
176
  return void ui.commits.onCommitMove(delta);
176
177
  }
177
178
  if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
178
- if (ui.nav.todoOpen && ui.composer.todoRowCount() > 1) {
179
- return void ui.composer.moveTodoFocus(delta);
179
+ if (ui.nav.tasksOpen && ui.composer.taskRowCount() > 1) {
180
+ return void ui.composer.moveTaskFocus(delta);
180
181
  }
181
182
  const frame = ui.lastFrame;
182
183
  const max = frame ? frame.scrollMax : null;
@@ -193,11 +194,11 @@ const onJump = (ui, toEnd) => {
193
194
  const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
194
195
  return void onScroll(ui, delta);
195
196
  }
196
- if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
197
- const last = ui.composer.todoRowCount() - 1;
197
+ if (ui.nav.tasksOpen && ui.composer.taskRowCount() > 0) {
198
+ const last = ui.composer.taskRowCount() - 1;
198
199
  const target = toEnd ? last : 0;
199
- const at = ui.composer.clampedTodoFocus();
200
- return void ui.composer.moveTodoFocus(target - at);
200
+ const at = ui.composer.clampedTaskFocus();
201
+ return void ui.composer.moveTaskFocus(target - at);
201
202
  }
202
203
  const frame = ui.lastFrame;
203
204
  const max = frame ? frame.scrollMax : 0;
@@ -230,7 +231,6 @@ const createFilesPane = (ui) => ({
230
231
  onEscape: () => onEscape(ui),
231
232
  onFiles: () => onFiles(ui),
232
233
  onOpenFile: () => onOpenFile(ui),
233
- onFileMove: (delta) => onFileMove(ui, delta),
234
234
  onNext: () => onNext(ui),
235
235
  onPrev: () => onPrev(ui),
236
236
  scrollStep: (fraction) => scrollStep(ui, fraction),
@@ -243,4 +243,4 @@ const createFilesPane = (ui) => ({
243
243
  onDiffScope: () => setFileScope(ui, 'diff'),
244
244
  });
245
245
 
246
- module.exports = { LAYOUTS, createFilesPane };
246
+ module.exports = { createFilesPane };
@@ -1,10 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const accessors = require('./accessors.js');
4
- const { stateView } = accessors;
5
-
6
3
  const files = require('../files.js');
7
- const { itemPath, TODO_FILE, isTodoItem } = files;
4
+ const { itemPath, TASKS_FILE, isTaskItem } = files;
8
5
  const review = require('../review.js');
9
6
  const { feedbackKey } = review;
10
7
  const diff = require('../diff/diff.js');
@@ -29,7 +26,7 @@ const npmBusyKind = (item) => {
29
26
  const blockKey = (item) => {
30
27
  if (!item || !item.file) return '';
31
28
  const rel = itemPath(item);
32
- if (isTodoItem(item)) return `todo:${TODO_FILE}`;
29
+ if (isTaskItem(item)) return `todo:${TASKS_FILE}`;
33
30
  const depId = depChangeKey(item);
34
31
  if (depId) return `${rel}:dep:${depId}`;
35
32
  if (!item.hunk) {
@@ -44,13 +41,13 @@ const blockKey = (item) => {
44
41
 
45
42
  const itemKey = (item) => {
46
43
  const key = blockKey(item);
47
- if (!key || isTodoItem(item)) return key;
44
+ if (!key || isTaskItem(item)) return key;
48
45
  const origin = item.origin ?? '';
49
46
  return `${origin}:${key}`;
50
47
  };
51
48
 
52
49
  const blockLinesKey = (item) => {
53
- if (!item || !item.file || isTodoItem(item)) return '';
50
+ if (!item || !item.file || isTaskItem(item)) return '';
54
51
  if (!item.hunk) return '';
55
52
  const parts = [itemPath(item)];
56
53
  for (const line of item.hunk.lines) {
@@ -132,27 +129,25 @@ const alignLoadedItems = (prev, next) => {
132
129
  return ordered;
133
130
  };
134
131
 
135
- const restoredIndex = (items, item) => {
136
- if (!item || !item.file) return -1;
137
- const origin = item.origin ?? '';
132
+ const originOf = (item) => item.origin ?? '';
133
+
134
+ const hunkStart = (item) => (item.hunk ? item.hunk.newStart : 0);
135
+
136
+ const sameLinesIndex = (items, item) => {
138
137
  const lines = blockLinesKey(item);
139
- if (lines) {
140
- let any = -1;
141
- for (let i = 0; i < items.length; i++) {
142
- if (blockLinesKey(items[i]) !== lines) continue;
143
- if (any < 0) any = i;
144
- if ((items[i].origin ?? '') === origin) return i;
145
- }
146
- if (any >= 0) return any;
147
- }
148
- const key = itemKey(item);
149
- if (key) {
150
- const exact = items.findIndex((entry) => itemKey(entry) === key);
151
- if (exact >= 0) return exact;
138
+ if (!lines) return -1;
139
+ let fallback = -1;
140
+ for (let i = 0; i < items.length; i++) {
141
+ if (blockLinesKey(items[i]) !== lines) continue;
142
+ if (originOf(items[i]) === originOf(item)) return i;
143
+ if (fallback < 0) fallback = i;
152
144
  }
145
+ return fallback;
146
+ };
147
+
148
+ const nearestHunkIndex = (items, item) => {
153
149
  const rel = itemPath(item);
154
150
  if (!rel) return -1;
155
- const start = item.hunk ? item.hunk.newStart : 0;
156
151
  let fileMatch = -1;
157
152
  let best = -1;
158
153
  let bestDist = Infinity;
@@ -160,19 +155,27 @@ const restoredIndex = (items, item) => {
160
155
  const entry = items[i];
161
156
  if (itemPath(entry) !== rel) continue;
162
157
  if (fileMatch < 0) fileMatch = i;
163
- if ((entry.origin ?? '') !== origin) continue;
164
- const at = entry.hunk ? entry.hunk.newStart : 0;
165
- const dist = Math.abs(at - start);
158
+ if (originOf(entry) !== originOf(item)) continue;
159
+ const dist = Math.abs(hunkStart(entry) - hunkStart(item));
166
160
  if (dist >= bestDist) continue;
167
161
  bestDist = dist;
168
162
  best = i;
169
163
  }
170
- if (best >= 0) return best;
171
- return fileMatch;
164
+ return best >= 0 ? best : fileMatch;
165
+ };
166
+
167
+ const restoredIndex = (items, item) => {
168
+ if (!item || !item.file) return -1;
169
+ const byLines = sameLinesIndex(items, item);
170
+ if (byLines >= 0) return byLines;
171
+ const key = itemKey(item);
172
+ const exact = key ? items.findIndex((entry) => itemKey(entry) === key) : -1;
173
+ if (exact >= 0) return exact;
174
+ return nearestHunkIndex(items, item);
172
175
  };
173
176
 
174
177
  const itemFeedKey = (item) => {
175
- if (!item || !item.file || isTodoItem(item)) return '';
178
+ if (!item || !item.file || isTaskItem(item)) return '';
176
179
  const hunk = item.hunk;
177
180
  const depId = depChangeKey(item);
178
181
  const file = depId ? `${itemPath(item)}#${depId}` : itemPath(item);
@@ -190,88 +193,65 @@ const sameBlock = (left, right) => {
190
193
  };
191
194
 
192
195
  const canEditCode = (item) => {
193
- if (!item || isTodoItem(item)) return false;
196
+ if (!item || isTaskItem(item)) return false;
194
197
  if (!item.hunk || !item.file) return false;
195
198
  if (item.file.isBinary) return false;
196
199
  return true;
197
200
  };
198
201
 
199
- const createItemCollection = () => {
200
- const state = {
201
- items: [],
202
- dismissed: new Set(),
203
- };
202
+ class ItemCollection {
203
+ constructor() {
204
+ this.items = [];
205
+ this.dismissed = new Set();
206
+ }
204
207
 
205
- const reset = () => {
206
- state.items = [];
207
- state.dismissed = new Set();
208
- };
208
+ reset() {
209
+ this.items = [];
210
+ this.dismissed = new Set();
211
+ }
209
212
 
210
- const isDismissed = (item) => state.dismissed.has(itemKey(item));
213
+ isDismissed(item) {
214
+ return this.dismissed.has(itemKey(item));
215
+ }
211
216
 
212
- const replace = (items) => {
213
- const aligned = alignLoadedItems(state.items, items);
214
- const next = [];
215
- for (const item of aligned) {
216
- if (isDismissed(item)) continue;
217
- next.push(item);
218
- }
219
- state.items = next;
220
- return next;
221
- };
217
+ replace(items) {
218
+ const aligned = alignLoadedItems(this.items, items);
219
+ this.items = aligned.filter((item) => !this.isDismissed(item));
220
+ return this.items;
221
+ }
222
222
 
223
- const dismiss = (item) => {
224
- state.dismissed.add(itemKey(item));
225
- };
223
+ dismiss(item) {
224
+ this.dismissed.add(itemKey(item));
225
+ }
226
226
 
227
- const clearDismissed = () => {
228
- state.dismissed.clear();
229
- };
227
+ clearDismissed() {
228
+ this.dismissed.clear();
229
+ }
230
230
 
231
- const findRestoredIndex = (item) => restoredIndex(state.items, item);
231
+ findRestoredIndex(item) {
232
+ return restoredIndex(this.items, item);
233
+ }
232
234
 
233
- const keepOrigin = (item, origin) => {
235
+ keepOrigin(item, origin) {
234
236
  const next = { ...item, origin };
235
- const { length } = state.items;
236
- const replaced = new Array(length);
237
- for (let i = 0; i < length; i++) {
238
- const entry = state.items[i];
239
- replaced[i] = sameBlock(entry, item) ? next : entry;
240
- }
237
+ const replaced = this.items.map((entry) =>
238
+ sameBlock(entry, item) ? next : entry,
239
+ );
241
240
  if (next.dep) {
242
- state.items = replaced;
241
+ this.items = replaced;
243
242
  return;
244
243
  }
245
- state.items = refreshIndexPatches(replaced, next);
246
- };
247
-
248
- return stateView(
249
- state,
250
- {
251
- reset,
252
- replace,
253
- dismiss,
254
- clearDismissed,
255
- isDismissed,
256
- findRestoredIndex,
257
- keepOrigin,
258
- },
259
- ['items'],
260
- ['dismissed'],
261
- );
262
- };
244
+ this.items = refreshIndexPatches(replaced, next);
245
+ }
246
+ }
263
247
 
264
248
  module.exports = {
265
- depChangeKey,
266
249
  needsReload,
267
250
  npmBusyKind,
268
- blockKey,
269
- itemKey,
270
251
  itemFeedKey,
271
- blockLinesKey,
272
252
  alignLoadedItems,
273
253
  restoredIndex,
274
254
  sameBlock,
275
255
  canEditCode,
276
- createItemCollection,
256
+ ItemCollection,
277
257
  };
@@ -4,11 +4,11 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const sessionLoad = require('./load.js');
7
- const { isAbortError } = sessionLoad;
7
+ const { isAbortError, tagLoaded, fetchSnapshot, fetchExtras } = sessionLoad;
8
8
  const watch = require('./watch.js');
9
- const { createDiskWatcher, POLL_MS, DEBOUNCE_MS } = watch;
9
+ const { createDiskWatcher, DEBOUNCE_MS } = watch;
10
10
  const files = require('../files.js');
11
- const { itemPath, relativeAge, TODO_FILE } = files;
11
+ const { itemPath, relativeAge } = files;
12
12
  const deps = require('../deps.js');
13
13
  const { MANIFEST, LOCKFILE, depFileMeta } = deps;
14
14
  const review = require('../review.js');
@@ -82,7 +82,7 @@ const failOpen = (api, error) => {
82
82
 
83
83
  const restoreAfterLoad = (api, here, wasTodo) => {
84
84
  const { collection, nav, ui } = api;
85
- nav.todoOpen = wasTodo;
85
+ nav.tasksOpen = wasTodo;
86
86
  if (here && !wasTodo) {
87
87
  const idx = collection.findRestoredIndex(here);
88
88
  if (idx >= 0) nav.index = idx;
@@ -152,7 +152,7 @@ const applyLoaded = (api, loaded) => {
152
152
  if (loaded.generation !== undefined) {
153
153
  if (!loader.accept(loaded.generation, ui.done)) return false;
154
154
  }
155
- const wasTodo = nav.todoOpen === true;
155
+ const wasTodo = nav.tasksOpen === true;
156
156
  const here = ui.current();
157
157
  ui.top = loaded.top ?? api.loadConfig.cwd;
158
158
  collection.replace(loaded.items);
@@ -243,6 +243,13 @@ const clearNpmExtras = (api) => {
243
243
  api.npmExtras = null;
244
244
  };
245
245
 
246
+ const isWanted = (api, gen) => {
247
+ if (!api.loader.isCurrent(gen)) return false;
248
+ if (!api.ui.done) return true;
249
+ api.ops.clearBusy();
250
+ return false;
251
+ };
252
+
246
253
  const followExtras = async (api, loaded, gen, kind) => {
247
254
  const extra = loadOptions(api, {
248
255
  paths: api.loadConfig.paths,
@@ -250,25 +257,18 @@ const followExtras = async (api, loaded, gen, kind) => {
250
257
  });
251
258
  let next;
252
259
  try {
253
- next = await api.loader.fetchExtras(api.loadConfig.repo, loaded, extra);
260
+ next = await fetchExtras(api.loadConfig.repo, loaded, extra);
254
261
  } catch (error) {
255
- if (!api.loader.isCurrent(gen)) return;
256
- return void failOpen(api, error);
257
- }
258
- if (!api.loader.isCurrent(gen)) return;
259
- if (api.ui.done) {
260
- api.ops.clearBusy();
262
+ if (api.loader.isCurrent(gen)) failOpen(api, error);
261
263
  return;
262
264
  }
263
- if (!next) {
264
- api.ops.clearBusy();
265
+ if (!isWanted(api, gen)) return;
266
+ if (next) {
267
+ applyLoaded(api, tagLoaded(next, gen));
268
+ cacheNpmExtras(api, next);
269
+ } else {
265
270
  api.loader.pendingExtras = false;
266
- api.ui.paint();
267
- finishIfEmpty(api, kind);
268
- return;
269
271
  }
270
- applyLoaded(api, api.loader.tag(next, gen));
271
- cacheNpmExtras(api, next);
272
272
  api.ops.clearBusy();
273
273
  api.ui.paint();
274
274
  finishIfEmpty(api, kind);
@@ -301,7 +301,13 @@ const resetNavAfterLoad = (api, extra = {}) => {
301
301
  if (extra.afterLoad) extra.afterLoad();
302
302
  };
303
303
 
304
+ const refreshListsForPane = (api) => {
305
+ const pane = api.ui.nav.pane;
306
+ if (pane === 'commits' && api.ui.commits) api.ui.commits.refresh();
307
+ };
308
+
304
309
  const afterSnapshot = (api, kind, extra = {}) => {
310
+ refreshListsForPane(api);
305
311
  if (kind !== 'open' && kind !== 'watch') {
306
312
  return void resetNavAfterLoad(api, extra);
307
313
  }
@@ -319,7 +325,7 @@ const runSnapshot = async (api, gen, kind, extra = {}) => {
319
325
  beginSnapshot(api, extra);
320
326
  let loaded;
321
327
  try {
322
- loaded = await api.loader.fetchSnapshot(
328
+ loaded = await fetchSnapshot(
323
329
  api.loadConfig.repo,
324
330
  api.loadConfig.cwd,
325
331
  api.loadConfig.paths,
@@ -329,15 +335,11 @@ const runSnapshot = async (api, gen, kind, extra = {}) => {
329
335
  }),
330
336
  );
331
337
  } catch (error) {
332
- if (!api.loader.isCurrent(gen)) return;
333
- return void failOpen(api, error);
334
- }
335
- if (!api.loader.isCurrent(gen)) return;
336
- if (api.ui.done) {
337
- api.ops.clearBusy();
338
+ if (api.loader.isCurrent(gen)) failOpen(api, error);
338
339
  return;
339
340
  }
340
- applyLoaded(api, api.loader.tag(loaded, gen));
341
+ if (!isWanted(api, gen)) return;
342
+ applyLoaded(api, tagLoaded(loaded, gen));
341
343
  afterSnapshot(api, kind, extra);
342
344
  if (loaded.pending === true || npmExtrasStale(api)) {
343
345
  api.ops.setBusy('checking npm');
@@ -383,7 +385,7 @@ const refreshFromRepo = (api, extra = {}) => {
383
385
  api.loadConfig.paths,
384
386
  snapshotOpts(api, extra),
385
387
  );
386
- applyLoaded(api, api.loader.tag(loaded, gen));
388
+ applyLoaded(api, tagLoaded(loaded, gen));
387
389
  afterSnapshot(api, kind, extra);
388
390
  if (loaded.pending === true || npmExtrasStale(api)) {
389
391
  api.ops.setBusy('checking npm');
@@ -467,59 +469,6 @@ const catchUpWatch = (api) => {
467
469
  refreshFromRepo(api, { keepView: true, quiet: true });
468
470
  };
469
471
 
470
- const stampFile = (root, rel) => {
471
- const full = path.join(root, rel);
472
- let stat;
473
- try {
474
- stat = fs.statSync(full);
475
- } catch {
476
- return `${rel}:missing`;
477
- }
478
- return `${rel}:${stat.mtimeMs}:${stat.size}`;
479
- };
480
-
481
- const worktreeStamp = (api) => {
482
- const root = api.ui.top || api.loadConfig.cwd;
483
- const parts = [];
484
- for (const rel of itemRels(api.collection)) {
485
- if (!rel || rel === TODO_FILE) continue;
486
- parts.push(stampFile(root, rel));
487
- }
488
- return parts.join('\n');
489
- };
490
-
491
- const watchesWorktree = (nav) =>
492
- nav.todoOpen === true ||
493
- nav.pane === 'branches' ||
494
- nav.pane === 'commits' ||
495
- nav.pane === 'npm';
496
-
497
- const currentFileStamp = (api) => {
498
- const { nav } = api;
499
- const root = api.ui.top || api.loadConfig.cwd;
500
- if (watchesWorktree(nav)) return worktreeStamp(api);
501
- const pane = nav.pane;
502
- if (pane !== 'diff' && pane !== 'unit') return '';
503
- const rel = pane === 'unit' ? nav.reviewPath : itemPath(api.ui.current());
504
- if (!rel) return '';
505
- return stampFile(root, rel);
506
- };
507
-
508
- const pollCurrentFile = (api) => {
509
- const stamp = currentFileStamp(api);
510
- if (!stamp) {
511
- api.watchStamp = '';
512
- return;
513
- }
514
- if (!api.watchStamp) {
515
- api.watchStamp = stamp;
516
- return;
517
- }
518
- if (stamp === api.watchStamp) return;
519
- api.watchStamp = stamp;
520
- onDiskChange(api);
521
- };
522
-
523
472
  const reviewFileStamp = (api) => {
524
473
  const store = api.review && api.review.store;
525
474
  if (!store || !store.reviewPath) return '';
@@ -553,14 +502,12 @@ const applyReviewChange = (api) => {
553
502
  }
554
503
  mergeReview(store, disk);
555
504
  api.reviewStamp = stamp;
556
- if (typeof api.ui.paint === 'function') api.ui.paint();
505
+ api.ui.paint();
557
506
  };
558
507
 
559
508
  const stopWatch = (api) => {
560
509
  clearWatchRetry(api);
561
- api.watchStamp = '';
562
510
  api.reviewStamp = '';
563
- if (api.ui.term) api.ui.term.stopTimer('watch');
564
511
  if (!api.diskWatcher) return;
565
512
  api.diskWatcher.close();
566
513
  api.diskWatcher = null;
@@ -579,26 +526,15 @@ const startWatch = (api) => {
579
526
  });
580
527
  syncItemWatches(api);
581
528
  noteReviewStamp(api);
582
- if (!api.ui.term) return;
583
- api.ui.term.startTimer(
584
- 'watch',
585
- () => {
586
- pollCurrentFile(api);
587
- applyReviewChange(api);
588
- },
589
- POLL_MS,
590
- );
591
529
  };
592
530
 
593
531
  const createLifecycle = (api) => {
594
532
  api.diskWatcher = null;
595
533
  api.watchRetry = null;
596
- api.watchStamp = '';
597
534
  api.reviewStamp = '';
598
535
  api.ignoreWatchUntil = 0;
599
536
  api.npmExtras = null;
600
537
  return {
601
- applyLoaded: (loaded) => applyLoaded(api, loaded),
602
538
  load: () => load(api),
603
539
  openLoad: () => openLoad(api),
604
540
  loadReady: () => loadReady(api),
@@ -609,7 +545,6 @@ const createLifecycle = (api) => {
609
545
  applyReviewChange: () => applyReviewChange(api),
610
546
  noteReviewStamp: () => noteReviewStamp(api),
611
547
  catchUpWatch: () => catchUpWatch(api),
612
- pollCurrentFile: () => pollCurrentFile(api),
613
548
  startWatch: () => startWatch(api),
614
549
  stopWatch: () => stopWatch(api),
615
550
  ignoreWatch: (ms) => ignoreWatch(api, ms),