reslop 0.2.6 → 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 +27 -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 +61 -66
  10. package/lib/{files.js → common/files.js} +51 -52
  11. package/lib/common/find.js +61 -0
  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 +117 -51
  18. package/lib/dashboard/npm.js +133 -32
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +41 -31
  21. package/lib/dashboard/workspace.js +151 -0
  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 -95
  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 +240 -71
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +36 -39
  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/dashboard/blocks.js +490 -0
  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 +22 -8
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +127 -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 +29 -13
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +89 -0
  66. package/lib/render/primitives.js +109 -16
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +214 -0
  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} +19 -61
  72. package/lib/render/tiles.js +203 -47
  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 +36 -44
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +122 -56
  100. package/lib/session/compose.js +197 -146
  101. package/lib/session/dashboard.js +183 -37
  102. package/lib/session/dispatch.js +120 -22
  103. package/lib/session/files-pane.js +60 -24
  104. package/lib/session/find.js +81 -0
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +96 -66
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +8 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/npm/packages.js +150 -0
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +216 -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} +166 -59
  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 +72 -59
  126. package/lib/session/workspace.js +416 -0
  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 -505
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -437
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -628
  148. package/lib/render/dash-blocks.js +0 -245
  149. package/lib/render/present.js +0 -19
  150. package/lib/render/report.js +0 -43
  151. package/lib/review.js +0 -744
  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,370 @@
1
+ 'use strict';
2
+
3
+ const ansi = require('../term/ansi.js');
4
+ const actions = require('../input/actions.js');
5
+ const files = require('../common/files.js');
6
+ const primitives = require('./primitives.js');
7
+ const { fill, branchLabel } = primitives;
8
+ const { npmStatus } = require('./dashboard/blocks.js');
9
+ const { menuBoxWidth, dropBox, menuFaces } = require('./menu.js');
10
+ const field = require('./field.js');
11
+ const { paintPlanFace, paintImportStatus, paintFindStatus } = field;
12
+ const { fileTotals } = files;
13
+ const { CONFIRM, confirmPrefix, promptFromChoices } = actions;
14
+ const { THEME, paint, visibleWidth } = ansi;
15
+ const { truncateVisible } = ansi;
16
+ const { paintMarkedWord, QUIT_MARK } = require('./footer.js');
17
+
18
+ const WAVE = '·•●•·';
19
+
20
+ const BUSY_BAR = Array.from(WAVE, (_, frame) => {
21
+ const start = (WAVE.length - frame) % WAVE.length;
22
+ return WAVE.slice(start) + WAVE.slice(0, start);
23
+ });
24
+
25
+ const {
26
+ busy: BUSY_PROGRESS,
27
+ info: INFO_STATUS,
28
+ infoPrefix: INFO_PREFIX,
29
+ } = require('./status.json');
30
+
31
+ const formatBusyStatus = (status, frame = 0) => {
32
+ const kind = `${status ?? ''}`;
33
+ if (!BUSY_PROGRESS.includes(kind)) return kind;
34
+ const i = Math.abs(frame) % BUSY_BAR.length;
35
+ return `${kind} ${BUSY_BAR[i]}`;
36
+ };
37
+
38
+ const STATUS_MARK = /(\+[0-9]+(?:\/[0-9]+)?|-[0-9]+(?:\/[0-9]+)?)/g;
39
+
40
+ const OLD_LOGS = /old logs \S+/;
41
+
42
+ const isInfoStatus = (status) => {
43
+ const busy = BUSY_PROGRESS.includes(status);
44
+ const known = INFO_STATUS.includes(status);
45
+ const prefixed = INFO_PREFIX.some((prefix) => status.startsWith(prefix));
46
+ return busy || known || prefixed;
47
+ };
48
+
49
+ const joinStatus = (parts) => parts.filter(Boolean).join(' ');
50
+
51
+ const countsKind = (sourceKind, revShort) => {
52
+ const remote = sourceKind === 'pr' || sourceKind === 'mr';
53
+ if (remote || sourceKind === 'issue') return sourceKind;
54
+ return revShort ? 'commit' : 'worktree';
55
+ };
56
+
57
+ const isWorktree = (view) =>
58
+ countsKind(view.sourceKind, view.revShort) === 'worktree';
59
+
60
+ const notesLine = (counts) => {
61
+ const done = counts.tasksDone ?? 0;
62
+ const total = counts.tasks ?? 0;
63
+ return `tasks ${done}/${total}`;
64
+ };
65
+
66
+ const PLAN_LEAD = 'Plan file: ';
67
+
68
+ const closedPlan = (view) => `${view.planName ?? ''}`;
69
+
70
+ const planLabel = (view) => {
71
+ const closed = closedPlan(view);
72
+ if (!closed) return '';
73
+ const query = view.planOpen ? `${view.planQuery ?? ''}` : '';
74
+ if (!query) return closed;
75
+ const room = Math.max(visibleWidth(closed), visibleWidth(query));
76
+ const shown = truncateVisible(query, room);
77
+ const pad = Math.max(0, room - visibleWidth(shown));
78
+ return `${shown}${' '.repeat(pad)}`;
79
+ };
80
+
81
+ const reposLead = (view) => {
82
+ const tiles = view.repos && view.repos.tiles ? view.repos.tiles : [];
83
+ const count = tiles.length;
84
+ if (!count) return 'repos';
85
+ const at = (view.repos.cursor ?? 0) + 1;
86
+ return `${at}/${count}`;
87
+ };
88
+
89
+ const agentsLead = (view) => {
90
+ if (view.agentView) return view.agentStatus || 'log';
91
+ const list = view.agents ?? [];
92
+ const found = list.filter((row) => row.bin).length;
93
+ return `${found}/${list.length}`;
94
+ };
95
+
96
+ const statusLead = (view) => {
97
+ if (view.pane === 'repos') return reposLead(view);
98
+ if (view.pane === 'agents') return agentsLead(view);
99
+ const counts = view.counts ?? {};
100
+ const kind = countsKind(view.sourceKind, view.revShort);
101
+ if (kind === 'worktree') return branchLabel(view.branch);
102
+ if (kind === 'commit') {
103
+ return `commit ${view.revShort} ${counts.commit ?? 0}`;
104
+ }
105
+ const label = view.sourceLabel || kind;
106
+ return `${kind} ${label} ${counts.pr ?? 0}`;
107
+ };
108
+
109
+ const STATUS_QUIET = ['npm', 'packages', 'branches', 'agents'];
110
+
111
+ const planStatus = (view) => {
112
+ const combo = planLabel(view);
113
+ if (!combo) return '';
114
+ return `${PLAN_LEAD}${combo}`;
115
+ };
116
+
117
+ const statusLeft = (view) => {
118
+ if (view.pane === 'repos') return ` ${statusLead(view)}`;
119
+ if (view.pane === 'tasks') {
120
+ const lead = joinStatus([statusLead(view), planStatus(view)]);
121
+ return ` ${lead}`;
122
+ }
123
+ const quiet = STATUS_QUIET.includes(view.pane);
124
+ const notes = quiet ? '' : notesLine(view.counts ?? {});
125
+ return ` ${joinStatus([statusLead(view), notes])}`;
126
+ };
127
+
128
+ const planAt = (view, text) => {
129
+ const combo = planLabel(view);
130
+ if (!combo) return -1;
131
+ const lead = statusLead(view);
132
+ const head = lead ? ` ${lead} ${PLAN_LEAD}` : ` ${PLAN_LEAD}`;
133
+ const line = `${text ?? ''}`;
134
+ if (!line.startsWith(head)) return -1;
135
+ const rest = line.slice(head.length);
136
+ if (rest.startsWith(combo) || combo.startsWith(rest)) return head.length;
137
+ return -1;
138
+ };
139
+
140
+ const planFace = (view, text) => {
141
+ const at = planAt(view, text);
142
+ if (at < 0) return null;
143
+ const combo = planLabel(view);
144
+ const rest = text.slice(at);
145
+ const face = rest.startsWith(combo) ? combo : rest;
146
+ return { at, face, x: visibleWidth(text.slice(0, at)) };
147
+ };
148
+
149
+ const statusDelta = (total) => {
150
+ const stagedAdd = total.stagedAdded ?? 0;
151
+ const unstagedAdd = total.unstagedAdded ?? 0;
152
+ const stagedDel = total.stagedRemoved ?? 0;
153
+ const unstagedDel = total.unstagedRemoved ?? 0;
154
+ const hasDelta =
155
+ stagedAdd || unstagedAdd || stagedDel || unstagedDel || total.added;
156
+ if (!hasDelta && !total.removed) return '';
157
+ const plus = `+${stagedAdd}/${stagedAdd + unstagedAdd}`;
158
+ const minus = `-${stagedDel}/${stagedDel + unstagedDel}`;
159
+ return `${plus} ${minus}`;
160
+ };
161
+
162
+ const DIFF_QUIET = ['npm', 'packages', 'tasks', 'branches', 'agents'];
163
+
164
+ const statusStats = (view) => {
165
+ if (view.pane === 'tasks') return notesLine(view.counts ?? {});
166
+ const counts = view.counts ?? {};
167
+ const staged = counts.staged ?? 0;
168
+ const total = fileTotals(view.files ?? []);
169
+ const { remaining, staged: listed, added, removed } = total;
170
+ const hasGit = remaining || listed || added || removed;
171
+ const unstaged = counts.unstaged ?? 0;
172
+ const n = hasGit ? listed : staged;
173
+ const m = hasGit ? remaining : staged + unstaged;
174
+ const delta = DIFF_QUIET.includes(view.pane) ? '' : statusDelta(total);
175
+ const ratio = STATUS_QUIET.includes(view.pane) ? '' : `${n}/${m}`;
176
+ const packages = view.pane === 'packages' ? npmStatus(view.packages) : '';
177
+ return joinStatus([view.npmLogs, delta, ratio, packages]);
178
+ };
179
+
180
+ const statusLayout = (view, status, width) => {
181
+ const msg = formatBusyStatus(status, view.progressFrame ?? 0);
182
+ const isError = msg && !isInfoStatus(status);
183
+ const msgFg = isError ? THEME.errorFg : THEME.chromeFg;
184
+ const stats = statusStats(view);
185
+ const right = stats ? `${stats} ` : '';
186
+ const rightW = visibleWidth(right);
187
+ const msgW = visibleWidth(msg);
188
+ const side = msg ? 1 : 0;
189
+ const room = Math.max(0, width - rightW - msgW - side * 2);
190
+ const clipped = truncateVisible(statusLeft(view), room);
191
+ const leftW = visibleWidth(clipped);
192
+ const extra = Math.max(0, width - leftW - rightW - msgW);
193
+ const padLeft = msg ? Math.floor(extra / 2) : extra;
194
+ const padRight = msg ? extra - padLeft : 0;
195
+ return { clipped, msg, msgFg, right, padLeft, padRight };
196
+ };
197
+
198
+ const choiceHits = (choices, prefix, y) => {
199
+ let x = visibleWidth(prefix);
200
+ return choices.map((choice) => {
201
+ const w = visibleWidth(`${choice.lead}${choice.label}`);
202
+ const hit = { id: choice.letter, y, x0: x, x1: x + w };
203
+ x += w;
204
+ return hit;
205
+ });
206
+ };
207
+
208
+ const paintChoicePrompt = (choices, width, color, prefix) => {
209
+ const bgRgb = THEME.chromeBg;
210
+ const body = `${prefix}${promptFromChoices(choices)}`;
211
+ const pad = Math.max(0, width - visibleWidth(body));
212
+ if (!color) return body + ' '.repeat(pad);
213
+ let out = paint(prefix, THEME.mutedFg, bgRgb, color);
214
+ for (const choice of choices) {
215
+ const label = `${choice.lead}${choice.label}`;
216
+ out += paintMarkedWord(label, choice.letter, color, QUIT_MARK);
217
+ }
218
+ return out + fill(pad, THEME.mutedFg, bgRgb, color);
219
+ };
220
+
221
+ const paintStatusLeft = (text, color) => {
222
+ if (!color) return text;
223
+ const bgRgb = THEME.chromeBg;
224
+ const found = text.match(OLD_LOGS);
225
+ if (found) {
226
+ const token = found[0];
227
+ const before = text.slice(0, found.index);
228
+ const after = text.slice(found.index + token.length);
229
+ return (
230
+ paintStatusLeft(before, color) +
231
+ paint(token, THEME.shaFg, bgRgb, color) +
232
+ paintStatusLeft(after, color)
233
+ );
234
+ }
235
+ let out = '';
236
+ let last = 0;
237
+ for (const match of text.matchAll(STATUS_MARK)) {
238
+ const token = match[0];
239
+ const tokenFg = token.startsWith('+') ? THEME.addLineFg : THEME.delLineFg;
240
+ out += paint(text.slice(last, match.index), THEME.mutedFg, bgRgb, color);
241
+ out += paint(token, tokenFg, bgRgb, color);
242
+ last = match.index + token.length;
243
+ }
244
+ return out + paint(text.slice(last), THEME.mutedFg, bgRgb, color);
245
+ };
246
+
247
+ const paintBranchHead = (text, branch, color) => {
248
+ const name = branchLabel(branch);
249
+ if (!name || !text.startsWith(' ')) return null;
250
+ const shown = text.startsWith(` ${name}`) ? name : text.slice(1);
251
+ if (!shown || !name.startsWith(shown)) return null;
252
+ const rest = text.slice(1 + shown.length);
253
+ const bgRgb = THEME.chromeBg;
254
+ return (
255
+ paint(' ', THEME.mutedFg, bgRgb, color) +
256
+ paint(shown, THEME.buttonHotFg, bgRgb, color, true) +
257
+ paintStatusLeft(rest, color)
258
+ );
259
+ };
260
+
261
+ const paintTasksLeft = (view, text, color) => {
262
+ const place = planFace(view, text);
263
+ if (!place) {
264
+ const head = paintBranchHead(text, view.branch, color);
265
+ return head ?? paintStatusLeft(text, color);
266
+ }
267
+ const before = text.slice(0, place.at);
268
+ const after = text.slice(place.at + place.face.length);
269
+ const searching = Boolean(view.planOpen && view.planQuery);
270
+ const comboFg = searching ? THEME.searchFg : THEME.shaFg;
271
+ const comboBg = searching ? THEME.searchBg : THEME.chromeBg;
272
+ const branch = paintBranchHead(before, view.branch, color);
273
+ const head = branch ?? paintStatusLeft(before, color);
274
+ const face = paintPlanFace(view, place.face, comboFg, comboBg, color);
275
+ return head + face + paintStatusLeft(after, color);
276
+ };
277
+
278
+ const statusHead = (view, clipped, color) => {
279
+ if (view.pane === 'tasks') return paintTasksLeft(view, clipped, color);
280
+ if (isWorktree(view)) return paintBranchHead(clipped, view.branch, color);
281
+ return null;
282
+ };
283
+
284
+ const paintStatusLine = (view, status, width, color) => {
285
+ if (view.import) return paintImportStatus(view, width, color);
286
+ if (view.find) return paintFindStatus(view, width, color);
287
+ const confirm = CONFIRM[view.mode];
288
+ if (confirm) {
289
+ const prefix = confirmPrefix(confirm, view);
290
+ return paintChoicePrompt(confirm.choices, width, color, prefix);
291
+ }
292
+ const layout = statusLayout(view, status, width);
293
+ const leftGap = ' '.repeat(layout.padLeft);
294
+ const rightGap = ' '.repeat(layout.padRight);
295
+ if (!color) {
296
+ return layout.clipped + leftGap + layout.msg + rightGap + layout.right;
297
+ }
298
+ const bgRgb = THEME.chromeBg;
299
+ const head = statusHead(view, layout.clipped, color);
300
+ const left = head ?? paintStatusLeft(layout.clipped, color);
301
+ return (
302
+ left +
303
+ paint(leftGap, THEME.mutedFg, bgRgb, color) +
304
+ paint(layout.msg, layout.msgFg, bgRgb, color) +
305
+ paint(rightGap, THEME.mutedFg, bgRgb, color) +
306
+ paintStatusLeft(layout.right, color)
307
+ );
308
+ };
309
+
310
+ const statusChoiceHits = (view, y) => {
311
+ const spec = CONFIRM[view.mode];
312
+ if (!spec) return [];
313
+ return choiceHits(spec.choices, confirmPrefix(spec, view), y);
314
+ };
315
+
316
+ const statusPlanHit = (view, width, y) => {
317
+ if (view.pane !== 'tasks' || view.find || view.import) return null;
318
+ if (CONFIRM[view.mode]) return null;
319
+ const layout = statusLayout(view, view.status ?? '', width);
320
+ const place = planFace(view, layout.clipped);
321
+ if (!place) return null;
322
+ const x0 = place.x;
323
+ return { id: 'plan', y, x0, x1: x0 + visibleWidth(place.face) };
324
+ };
325
+
326
+ const planMenu = (view, width, color, statusY) => {
327
+ if (view.pane !== 'tasks' || !view.planOpen || statusY < 3) return null;
328
+ const names = view.planNames ?? [];
329
+ const layout = statusLayout(view, view.status ?? '', width);
330
+ const place = planFace(view, layout.clipped);
331
+ if (!place) return null;
332
+ const x = Math.max(0, place.x - 1);
333
+ const room = Math.max(1, statusY - 2);
334
+ const box = dropBox(names, view.planCursor, view.planScroll, room);
335
+ const boxW = menuBoxWidth(names, visibleWidth(place.face), width - x);
336
+ const faces = menuFaces(names, box, boxW, color);
337
+ const lines = [];
338
+ const hits = [];
339
+ for (let i = 0; i < faces.length; i++) {
340
+ const y = statusY - box.inner + i;
341
+ lines.push({ y, text: faces[i].face });
342
+ if (names[faces[i].index] === undefined) continue;
343
+ const id = 'plan-item';
344
+ const x0 = x;
345
+ const x1 = x + boxW;
346
+ const cursor = faces[i].index;
347
+ const hit = { id, y, x0, x1, cursor };
348
+ hits.push(hit);
349
+ }
350
+ return { x, lines, hits, scroll: box.start };
351
+ };
352
+
353
+ const statusBranchHit = (view, width, y) => {
354
+ if (view.find || view.import || CONFIRM[view.mode]) return null;
355
+ const name = branchLabel(view.branch);
356
+ if (!name || !isWorktree(view)) return null;
357
+ const layout = statusLayout(view, view.status ?? '', width);
358
+ const at = layout.clipped.indexOf(name);
359
+ if (at < 0) return null;
360
+ return { id: 'branch', y, x0: at, x1: at + visibleWidth(name) };
361
+ };
362
+
363
+ module.exports = {
364
+ formatBusyStatus,
365
+ paintStatusLine,
366
+ statusChoiceHits,
367
+ statusBranchHit,
368
+ statusPlanHit,
369
+ planMenu,
370
+ };
@@ -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)],
@@ -67,13 +42,18 @@ const flexCell = (text, tone = 'muted') => ({
67
42
 
68
43
  const blankCell = { segs: [], align: 'l', flex: false };
69
44
 
45
+ const measuredWidth = (item) => {
46
+ if (typeof item.measure === 'number') return item.measure;
47
+ return segmentsWidth(item.segs);
48
+ };
49
+
70
50
  const columnWidths = (rows, count) => {
71
51
  const widths = new Array(count).fill(0);
72
52
  for (const row of rows) {
73
53
  for (let c = 0; c < count; c++) {
74
54
  const item = row[c];
75
55
  if (!item || (item.flex && !item.keep)) continue;
76
- widths[c] = Math.max(widths[c], segmentsWidth(item.segs));
56
+ widths[c] = Math.max(widths[c], measuredWidth(item));
77
57
  }
78
58
  }
79
59
  return widths;
@@ -241,23 +221,6 @@ const titleAside = (tile, aside, width, badge = []) => {
241
221
  return [...left, seg(' '.repeat(Math.max(1, gap))), ...aside];
242
222
  };
243
223
 
244
- const stat = (label, value, tone = 'text', bold = false) => ({
245
- label,
246
- value: typeof value === 'string' ? [seg(value, tone, bold)] : value,
247
- });
248
-
249
- const pairRows = (items, perRow = 2) => {
250
- const rows = [];
251
- for (let i = 0; i < items.length; i += perRow) {
252
- const row = [];
253
- for (const item of items.slice(i, i + perRow)) {
254
- row.push(cell(item.label, 'muted'), cellSegs(item.value));
255
- }
256
- rows.push(row);
257
- }
258
- return rows;
259
- };
260
-
261
224
  const pickGroups = (folders, exts, rows) => {
262
225
  const count = Math.max(0, rows);
263
226
  const dirs = folders.slice(0, count);
@@ -270,19 +233,14 @@ const labelOf = (key, isDir) => {
270
233
  };
271
234
 
272
235
  module.exports = {
273
- num,
274
- size,
275
- ago,
276
- duration,
277
236
  isHot,
278
237
  delta,
238
+ notice,
279
239
  waiting,
280
240
  cell,
281
241
  cellSegs,
282
242
  flexCell,
283
243
  tableLines,
284
- stat,
285
- pairRows,
286
244
  pickGroups,
287
245
  labelOf,
288
246
  titleAside,