reslop 0.2.6 → 0.2.7

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 (42) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/lib/ansi.js +42 -2
  3. package/lib/api.js +1 -1
  4. package/lib/cli.js +21 -1
  5. package/lib/dashboard/model.js +28 -3
  6. package/lib/dashboard/npm.js +116 -19
  7. package/lib/files.js +13 -7
  8. package/lib/find.js +61 -0
  9. package/lib/git-deps.js +1 -0
  10. package/lib/git-worktree.js +22 -4
  11. package/lib/keys.js +26 -1
  12. package/lib/render/chrome.js +257 -79
  13. package/lib/render/dash-blocks.js +297 -46
  14. package/lib/render/dash-table.js +6 -1
  15. package/lib/render/files.js +23 -4
  16. package/lib/render/frame.js +4 -0
  17. package/lib/render/notes.js +13 -2
  18. package/lib/render/packages.js +93 -0
  19. package/lib/render/primitives.js +13 -6
  20. package/lib/render/repos.js +226 -0
  21. package/lib/render/search.js +25 -0
  22. package/lib/render/tiles.js +107 -12
  23. package/lib/review.js +5 -4
  24. package/lib/session/actions.js +136 -30
  25. package/lib/session/branches.js +3 -5
  26. package/lib/session/commit.js +31 -4
  27. package/lib/session/compose.js +19 -4
  28. package/lib/session/dashboard.js +160 -15
  29. package/lib/session/dispatch.js +38 -4
  30. package/lib/session/files-pane.js +17 -2
  31. package/lib/session/find.js +94 -0
  32. package/lib/session/lifecycle.js +27 -0
  33. package/lib/session/navigation.js +2 -0
  34. package/lib/session/packages.js +143 -0
  35. package/lib/session/pointer.js +14 -0
  36. package/lib/session/tasks.js +3 -3
  37. package/lib/session/view.js +15 -2
  38. package/lib/session/watch.js +5 -0
  39. package/lib/session/workspace.js +455 -0
  40. package/lib/session.js +14 -0
  41. package/lib/workspace.js +166 -0
  42. package/package.json +1 -1
@@ -4,6 +4,8 @@ const ansi = require('../ansi.js');
4
4
  const keys = require('../keys.js');
5
5
  const files = require('../files.js');
6
6
  const primitives = require('./primitives.js');
7
+ const blocks = require('./dash-blocks.js');
8
+ const { npmStatus } = blocks;
7
9
 
8
10
  const { itemPath, REPO_TASKS_LABEL, isTasksEntry, fileTotals } = files;
9
11
  const { ACTION_IDS, actionLetter } = keys;
@@ -12,10 +14,15 @@ const { FILES_HIDDEN, FILES_GIT_DISABLED, BRANCHES_DISABLED } = keys;
12
14
  const { COMMITS_DISABLED, TASKS_DISABLED, DIFF_DISABLED, UNIT_DISABLED } = keys;
13
15
  const { NPM_DISABLED } = keys;
14
16
  const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = keys;
17
+ const { entryHasDiff, lineHasDiff, withoutMoves } = keys;
15
18
  const { CONFIRM, confirmPrefix, promptFromChoices } = keys;
16
19
  const { THEME, paint, visibleWidth } = ansi;
17
20
  const { truncateVisible, fg, RESET, EL, seq } = ansi;
18
21
  const { fill, branchLabel, isTaskView } = primitives;
22
+ const searchPaint = require('./search.js');
23
+ const { paintPathText } = searchPaint;
24
+ const locate = require('../find.js');
25
+ const { activeHit, spansFor, fileMatches } = locate;
19
26
 
20
27
  const WAVE = '·•●•·';
21
28
  const BUSY_BAR = Array.from(WAVE, (_, frame) => {
@@ -57,6 +64,7 @@ const INFO_STATUS = [
57
64
  'dropped',
58
65
  'already staged',
59
66
  'read only',
67
+ 'transitive',
60
68
  'nothing to review',
61
69
  'nothing to commit',
62
70
  'unified',
@@ -112,7 +120,6 @@ const BUTTON_LEAD = ' ';
112
120
  const BUTTON_GAP = ' ';
113
121
  const BUTTON_EDGE = 1;
114
122
  const BACK_ARROW = '🢐';
115
- const STOP_MARK = '⊗';
116
123
  const BACK_KEY = 'esc';
117
124
 
118
125
  const BUTTON_MARK = {
@@ -160,8 +167,10 @@ const headerScreen = (view) => {
160
167
  const { pane } = view;
161
168
  if (
162
169
  pane === 'dashboard' ||
170
+ pane === 'repos' ||
163
171
  pane === 'branches' ||
164
172
  pane === 'npm' ||
173
+ pane === 'packages' ||
165
174
  pane === 'tasks'
166
175
  ) {
167
176
  return pane;
@@ -205,44 +214,74 @@ const headerText = (view) => {
205
214
  return `${piecesText(pieces)}${label}`;
206
215
  };
207
216
 
217
+ const headerFind = (view, label, clipped) => {
218
+ const query = view.find && view.find.query ? view.find.query : '';
219
+ const target = headerTarget(view);
220
+ if (!query || label !== target || !label) return [];
221
+ const hit = activeHit(view.files ?? [], view.find);
222
+ return spansFor(clipped, label, query, hit);
223
+ };
224
+
225
+ const paintHeaderRoles = (pieces) => {
226
+ let out = `${seq(THEME.headerChromeFg, THEME.headerBg)}${EL}`;
227
+ for (const piece of pieces) {
228
+ out += `${fg(HEADER_ROLE_FG[piece.role])}${piece.text}`;
229
+ }
230
+ return out;
231
+ };
232
+
233
+ const paintHeaderHits = (clipped, spans, color) => {
234
+ let out = '';
235
+ let offset = 0;
236
+ for (const seg of pathSegments(clipped)) {
237
+ const local = [];
238
+ const end = offset + seg.text.length;
239
+ for (const span of spans) {
240
+ const start = Math.max(span.start, offset);
241
+ const stop = Math.min(span.end, end);
242
+ if (start >= stop) continue;
243
+ local.push({
244
+ start: start - offset,
245
+ end: stop - offset,
246
+ on: span.on === true,
247
+ });
248
+ }
249
+ const roleFg = HEADER_ROLE_FG[seg.role];
250
+ out += paintPathText(seg.text, roleFg, THEME.headerBg, color, false, local);
251
+ offset = end;
252
+ }
253
+ return out;
254
+ };
255
+
208
256
  const paintHeader = (view, width, color) => {
209
257
  const { pieces, label } = headerLead(view);
210
258
  const right = ' ';
211
259
  const used = visibleWidth(piecesText(pieces)) + visibleWidth(right);
212
260
  const room = Math.max(0, width - used);
213
261
  const clipped = truncateVisible(label, room);
214
- const pad = Math.max(0, room - visibleWidth(clipped));
215
- pieces.push(...pathSegments(clipped));
216
- if (pad) pieces.push({ text: ' '.repeat(pad), role: 'chrome' });
217
- pieces.push({ text: right, role: 'chrome' });
218
- if (!color) return piecesText(pieces);
219
- let out = `${seq(THEME.headerChromeFg, THEME.headerBg)}${EL}`;
220
- for (const piece of pieces) {
221
- out += `${fg(HEADER_ROLE_FG[piece.role])}${piece.text}`;
262
+ const pad = ' '.repeat(Math.max(0, room - visibleWidth(clipped)));
263
+ const spans = headerFind(view, label, clipped);
264
+ if (!color || !spans.length) {
265
+ pieces.push(...pathSegments(clipped));
266
+ if (pad) pieces.push({ text: pad, role: 'chrome' });
267
+ pieces.push({ text: right, role: 'chrome' });
268
+ if (!color) return piecesText(pieces);
269
+ return `${paintHeaderRoles(pieces)}${RESET}`;
222
270
  }
271
+ let out = paintHeaderRoles(pieces);
272
+ out += paintHeaderHits(clipped, spans, color);
273
+ out += `${fg(HEADER_ROLE_FG.chrome)}${pad}${right}`;
223
274
  return `${out}${RESET}`;
224
275
  };
225
276
 
226
- const backFooterId = (view) => {
227
- const { pane } = view;
228
- if (pane === 'branches' || pane === 'commits' || pane === 'tasks') {
229
- return 'back';
230
- }
231
- if (pane === 'npm') return view.npmView ? 'npmStop' : 'back';
232
- return isTaskView(view) ? 'back' : '';
233
- };
277
+ const backFooterId = (view) =>
278
+ view.pane === 'npm' && view.npmView ? 'npmStop' : 'back';
234
279
 
235
- const backHintMark = (view) => {
236
- if (view.pane === 'npm' && view.npmView && view.npmRunning) return STOP_MARK;
237
- return BACK_ARROW;
238
- };
280
+ const backHintMark = () => BACK_ARROW;
239
281
 
240
282
  const buttonGap = (index) => (index ? BUTTON_GAP : BUTTON_LEAD);
241
283
 
242
- const hintBody = (mark) => {
243
- const gap = mark === STOP_MARK ? ' ' : '';
244
- return `${mark}${gap}${BACK_KEY}`;
245
- };
284
+ const hintBody = (mark) => `${mark}${BACK_KEY}`;
246
285
 
247
286
  const buttonLabel = (id, short) => (short ? actionLetter(id) : buttonWord(id));
248
287
 
@@ -266,38 +305,125 @@ const paintMarkedWord = (label, letter, color, mark) => {
266
305
  return out + paint(after, mark.fgRgb, mark.bgRgb, color);
267
306
  };
268
307
 
269
- const footerIds = (hidden, extra, backId) => {
308
+ const footerIds = (hidden, extra, backId, globals, tail, after) => {
270
309
  const ids = backId ? [backId] : [];
271
- for (const add of extra) {
272
- if (!ids.includes(add)) ids.push(add);
273
- }
274
- for (const id of ACTION_IDS) {
275
- if (id === 'quit' || hidden.includes(id) || ids.includes(id)) continue;
276
- ids.push(id);
310
+ const push = (id) => {
311
+ if (!ids.includes(id)) ids.push(id);
312
+ };
313
+ for (const add of extra) push(add);
314
+ for (const id of globals) push(id);
315
+ if (tail) {
316
+ for (const id of ACTION_IDS) {
317
+ if (id === 'quit' || hidden.includes(id) || ids.includes(id)) continue;
318
+ ids.push(id);
319
+ }
277
320
  }
321
+ for (const id of after) push(id);
278
322
  return ids;
279
323
  };
280
324
 
325
+ const buttonPiece = (id, index, short, backId, mark) => {
326
+ const back = id === backId;
327
+ const letter = back ? BACK_KEY : actionLetter(id);
328
+ const label = back ? hintBody(mark) : buttonLabel(id, short);
329
+ const gap = buttonGap(index);
330
+ return {
331
+ id,
332
+ piece: `${gap}${label}`,
333
+ gap,
334
+ letter,
335
+ label,
336
+ back,
337
+ mark: back ? mark : '',
338
+ };
339
+ };
340
+
341
+ const packGroup = (ids, short, start, backId, mark) => {
342
+ const parts = [];
343
+ for (let i = 0; i < ids.length; i++) {
344
+ parts.push(buttonPiece(ids[i], start + i, short, backId, mark));
345
+ }
346
+ return parts;
347
+ };
348
+
349
+ const groupWidth = (parts) => {
350
+ let width = 0;
351
+ for (const part of parts) width += visibleWidth(part.piece);
352
+ return width;
353
+ };
354
+
355
+ const placeGroup = (parts, hits, disabled, x) => {
356
+ const placed = [];
357
+ let cursor = x;
358
+ for (const part of parts) {
359
+ const w = visibleWidth(part.piece);
360
+ const off = disabled.includes(part.id);
361
+ if (!off) hits.push({ id: part.id, x0: cursor, x1: cursor + w });
362
+ placed.push({ ...part, disabled: off });
363
+ cursor += w;
364
+ }
365
+ return { placed, cursor };
366
+ };
367
+
368
+ const splitFooter = (ids, options, limit) => {
369
+ const { disabled = [], backId = '', mark = BACK_ARROW } = options;
370
+ const { globals = [], after = [] } = options;
371
+ const rightIds = [];
372
+ for (const id of [...globals, ...after]) {
373
+ if (ids.includes(id) && !rightIds.includes(id)) rightIds.push(id);
374
+ }
375
+ const leftIds = ids.filter((id) => !rightIds.includes(id));
376
+ const pack = (leftShort, rightShort) => {
377
+ const left = packGroup(leftIds, leftShort, 0, backId, mark);
378
+ const start = left.length ? 1 : 0;
379
+ const right = packGroup(rightIds, rightShort, start, backId, mark);
380
+ return { left, right };
381
+ };
382
+ const wide = (packed) =>
383
+ groupWidth(packed.left) + groupWidth(packed.right) > limit;
384
+ let packed = pack(false, false);
385
+ if (wide(packed)) packed = pack(false, true);
386
+ if (wide(packed)) packed = pack(true, true);
387
+ const left = packed.left.slice();
388
+ const right = packed.right.slice();
389
+ while (groupWidth(left) + groupWidth(right) > limit && left.length) {
390
+ left.pop();
391
+ }
392
+ while (groupWidth(left) + groupWidth(right) > limit && right.length) {
393
+ right.pop();
394
+ }
395
+ const hits = [];
396
+ const leftPlace = placeGroup(left, hits, disabled, 0);
397
+ const parts = leftPlace.placed;
398
+ let x = leftPlace.cursor;
399
+ const gap = Math.max(0, limit - x - groupWidth(right));
400
+ if (gap && right.length) {
401
+ parts.push({ spacer: true, piece: ' '.repeat(gap) });
402
+ x += gap;
403
+ }
404
+ const rightPlace = placeGroup(right, hits, disabled, x);
405
+ parts.push(...rightPlace.placed);
406
+ return { hits, parts };
407
+ };
408
+
281
409
  const layoutButtons = (width, options = {}) => {
282
410
  const { compact = false, hidden = [], extra = [], disabled = [] } = options;
283
- const { backId = '', mark = BACK_ARROW } = options;
284
- const ids = footerIds(hidden, extra, backId);
411
+ const { backId = '', mark = BACK_ARROW, globals = [] } = options;
412
+ const { tail = true, after = [] } = options;
413
+ const ids = footerIds(hidden, extra, backId, globals, tail, after);
285
414
  const limit = Math.max(0, width - BUTTON_EDGE);
415
+ if (globals.length) return splitFooter(ids, options, limit);
286
416
  const build = (short) => {
287
417
  const hits = [];
288
418
  const parts = [];
289
419
  let x = 0;
290
420
  for (const id of ids) {
291
- const back = id === backId;
292
- const letter = back ? BACK_KEY : actionLetter(id);
293
- const label = back ? hintBody(mark) : buttonLabel(id, short);
294
- const piece = `${buttonGap(parts.length)}${label}`;
295
- const w = visibleWidth(piece);
421
+ const part = buttonPiece(id, parts.length, short, backId, mark);
422
+ const w = visibleWidth(part.piece);
296
423
  if (x + w > limit && parts.length) break;
297
424
  const off = disabled.includes(id);
298
425
  if (!off) hits.push({ id, x0: x, x1: x + w });
299
- const hint = back ? mark : '';
300
- parts.push({ id, piece, letter, label, disabled: off, back, mark: hint });
426
+ parts.push({ ...part, disabled: off });
301
427
  x += w;
302
428
  }
303
429
  return { hits, parts };
@@ -315,7 +441,13 @@ const paintButtons = (layout, width, color) => {
315
441
  let used = 0;
316
442
  for (let i = 0; i < layout.parts.length; i++) {
317
443
  const part = layout.parts[i];
318
- out += paint(buttonGap(i), fgRgb, bgRgb, color);
444
+ if (part.spacer) {
445
+ out += paint(part.piece, fgRgb, bgRgb, color);
446
+ used += visibleWidth(part.piece);
447
+ continue;
448
+ }
449
+ const gap = part.gap ?? buttonGap(i);
450
+ out += paint(gap, fgRgb, bgRgb, color);
319
451
  if (part.back) out += paintBackHint(color, part.mark);
320
452
  else if (part.disabled) out += paint(part.label, fgRgb, bgRgb, color);
321
453
  else out += paintMarkedWord(part.label, part.letter, color, BUTTON_MARK);
@@ -339,13 +471,19 @@ const isWorktree = (view) =>
339
471
  countsKind(view.sourceKind, view.revShort) === 'worktree';
340
472
 
341
473
  const notesLine = (counts) => {
342
- const feedback = counts.feedback ?? 0;
343
- const tasks = counts.tasks ?? 0;
344
- const code = counts.code ?? 0;
345
- return `feedback ${feedback} tasks ${tasks} code ${code}`;
474
+ const done = counts.tasksDone ?? 0;
475
+ const total = counts.tasks ?? 0;
476
+ return `tasks ${done}/${total}`;
346
477
  };
347
478
 
348
479
  const statusLead = (view) => {
480
+ if (view.pane === 'repos') {
481
+ const tiles = view.repos && view.repos.tiles ? view.repos.tiles : [];
482
+ const count = tiles.length;
483
+ if (!count) return 'repos';
484
+ const at = (view.repos.cursor ?? 0) + 1;
485
+ return `${at}/${count}`;
486
+ }
349
487
  const counts = view.counts ?? {};
350
488
  const kind = countsKind(view.sourceKind, view.revShort);
351
489
  if (kind === 'worktree') return branchLabel(view.branch);
@@ -356,8 +494,13 @@ const statusLead = (view) => {
356
494
  return `${kind} ${label} ${counts.pr ?? 0}`;
357
495
  };
358
496
 
359
- const statusLeft = (view) =>
360
- ` ${joinStatus([statusLead(view), notesLine(view.counts ?? {})])}`;
497
+ const STATUS_QUIET = new Set(['npm', 'packages', 'branches']);
498
+
499
+ const statusLeft = (view) => {
500
+ if (view.pane === 'repos') return ` ${statusLead(view)}`;
501
+ const notes = STATUS_QUIET.has(view.pane) ? '' : notesLine(view.counts ?? {});
502
+ return ` ${joinStatus([statusLead(view), notes])}`;
503
+ };
361
504
 
362
505
  const statusDelta = (total) => {
363
506
  const stagedAdd = total.stagedAdded ?? 0;
@@ -372,6 +515,8 @@ const statusDelta = (total) => {
372
515
  return `${plus} ${minus}`;
373
516
  };
374
517
 
518
+ const DIFF_QUIET = new Set(['npm', 'packages', 'tasks', 'branches']);
519
+
375
520
  const statusStats = (view) => {
376
521
  const counts = view.counts ?? {};
377
522
  const staged = counts.staged ?? 0;
@@ -380,7 +525,10 @@ const statusStats = (view) => {
380
525
  total.remaining || total.staged || total.added || total.removed;
381
526
  const n = hasGit ? total.staged : staged;
382
527
  const m = hasGit ? total.remaining : staged + (counts.unstaged ?? 0);
383
- return joinStatus([view.npmLogs, statusDelta(total), `${n}/${m}`]);
528
+ const delta = DIFF_QUIET.has(view.pane) ? '' : statusDelta(total);
529
+ const ratio = STATUS_QUIET.has(view.pane) ? '' : `${n}/${m}`;
530
+ const packages = view.pane === 'packages' ? npmStatus(view.packages) : '';
531
+ return joinStatus([view.npmLogs, delta, ratio, packages]);
384
532
  };
385
533
 
386
534
  const statusLayout = (view, status, width) => {
@@ -491,7 +639,26 @@ const paintFooter = (view, layout, width, color) => {
491
639
 
492
640
  const footerHits = (view, layout) => (commitSaveHint(view) ? [] : layout.hits);
493
641
 
642
+ const paintFindStatus = (view, width, color) => {
643
+ const query = view.find.query ?? '';
644
+ const hits = fileMatches(view.files ?? [], query);
645
+ const miss = query.length > 0 && hits.length === 0;
646
+ const lead = ' /';
647
+ const caret = 1;
648
+ const room = Math.max(0, width - lead.length - caret);
649
+ const shown = truncateVisible(query, room);
650
+ const pad = Math.max(0, width - lead.length - visibleWidth(shown) - caret);
651
+ if (!color) return `${lead}${shown}${' '.repeat(caret + pad)}`;
652
+ const bgRgb = THEME.chromeBg;
653
+ const tone = miss ? THEME.errorFg : THEME.chromeFg;
654
+ let out = paint(lead, THEME.warnFg, bgRgb, color, true);
655
+ out += paint(shown, tone, bgRgb, color);
656
+ out += paint(' ', THEME.searchOnFg, THEME.searchOnBg, color);
657
+ return out + fill(pad, THEME.mutedFg, bgRgb, color);
658
+ };
659
+
494
660
  const paintStatusLine = (view, status, width, color) => {
661
+ if (view.find) return paintFindStatus(view, width, color);
495
662
  const confirm = CONFIRM[view.mode];
496
663
  if (confirm) {
497
664
  const prefix = confirmPrefix(confirm, view);
@@ -524,7 +691,7 @@ const statusChoiceHits = (view, y) => {
524
691
  };
525
692
 
526
693
  const statusBranchHit = (view, width, y) => {
527
- if (CONFIRM[view.mode]) return null;
694
+ if (view.find || CONFIRM[view.mode]) return null;
528
695
  const name = branchLabel(view.branch);
529
696
  if (!name || !isWorktree(view)) return null;
530
697
  const layout = statusLayout(view, view.status ?? '', width);
@@ -537,7 +704,7 @@ const withHidden = (ids, more) => [...ids, ...more];
537
704
 
538
705
  const hiddenActions = (view) => {
539
706
  const { pane } = view;
540
- if (pane === 'dashboard') return DASH_HIDDEN;
707
+ if (pane === 'dashboard' || pane === 'repos') return DASH_HIDDEN;
541
708
  if (pane === 'files') return FILES_HIDDEN;
542
709
  if (pane === 'unit') return withHidden(UNIT_DISABLED, ['feedback', 'tasks']);
543
710
  if (pane === 'branches') return BRANCHES_DISABLED;
@@ -552,9 +719,18 @@ const hiddenActions = (view) => {
552
719
  const dimFooterActions = (view) => {
553
720
  const pane = view.pane ?? 'diff';
554
721
  if (pane === 'files') {
555
- return isTasksEntry(selectedFile(view)) ? FILES_GIT_DISABLED : [];
722
+ const entry = selectedFile(view);
723
+ if (isTasksEntry(entry)) return FILES_GIT_DISABLED;
724
+ return withoutMoves([], entryHasDiff(entry));
556
725
  }
557
- if (pane === 'diff' || pane === 'unit') return diffGitDisabled(view.item);
726
+ if (pane === 'unit') {
727
+ const off = [...diffGitDisabled(view.item)];
728
+ const lines = view.unitLines;
729
+ if (!lines || !lines.length) return off;
730
+ const line = lines[view.unitLine ?? 0];
731
+ return withoutMoves(off, lineHasDiff(line));
732
+ }
733
+ if (pane === 'diff') return diffGitDisabled(view.item);
558
734
  if (pane === 'branches') return branchGitDisabled(selectedBranch(view));
559
735
  if (pane === 'commits') {
560
736
  return commitGitDisabled({
@@ -563,46 +739,46 @@ const dimFooterActions = (view) => {
563
739
  unstagedCurrent: !view.rev && !view.revShort,
564
740
  });
565
741
  }
742
+ if (pane === 'packages') {
743
+ const listed = view.packages && view.packages.modules;
744
+ const rows = listed && listed.packages ? listed.packages : [];
745
+ const cursor = view.packagesCursor ?? 0;
746
+ const onRow = rows.length > 0 && cursor < rows.length;
747
+ if (!onRow) return ['packageDrop', 'packageWanted', 'packageLatest'];
748
+ if (rows[cursor] && rows[cursor].transitive) {
749
+ return ['packageNew', 'packageDrop'];
750
+ }
751
+ return [];
752
+ }
566
753
  if (pane !== 'npm') return [];
567
754
  if (view.npmView) return view.npmRunning ? ['npmRerun'] : [];
568
755
  return view.npmLogs ? [] : ['npmLogs'];
569
756
  };
570
757
 
571
- const navExcept = (skip) => DASH_IDS.filter((id) => !skip.includes(id));
758
+ const GIT_FOOTER = ['add', 'unstage', 'revert', 'ignore'];
572
759
 
573
760
  const extraFooterActions = (view) => {
574
761
  const { pane } = view;
575
- if (pane === 'dashboard') return DASH_IDS;
762
+ if (pane === 'repos') return ['theme'];
763
+ if (pane === 'dashboard') return ['theme', 'pull', 'push'];
764
+ if (pane === 'unit') return ['add', 'unstage', 'revert', 'feedback', 'code'];
765
+ if (pane === 'files') return [...GIT_FOOTER, 'code'];
766
+ if (pane === 'diff') return [...GIT_FOOTER, 'feedback', 'code', 'layout'];
576
767
  if (pane === 'branches') {
577
- return [
578
- ...navExcept(['dashNpm']),
579
- 'newBranch',
580
- 'rebase',
581
- 'drop',
582
- 'pull',
583
- 'push',
584
- ];
768
+ return ['newBranch', 'rebase', 'drop', 'pull', 'push'];
585
769
  }
586
770
  if (pane === 'commits') {
587
- return [
588
- ...navExcept(['dashCommits']),
589
- 'commit',
590
- 'amend',
591
- 'apply',
592
- 'reword',
593
- 'fixup',
594
- 'drop',
595
- 'view',
596
- ];
771
+ return ['newCommit', 'apply', 'reword', 'drop', 'view', 'pull', 'push'];
597
772
  }
598
773
  if (pane === 'npm') {
599
- if (view.npmView) {
600
- return [...navExcept(['dashRun']), 'npmVerbose', 'npmRerun'];
601
- }
602
- return [...DASH_IDS, 'npmEdit', 'npmNew', 'npmDrop', 'npmLogs'];
774
+ if (view.npmView) return ['npmVerbose', 'npmRerun'];
775
+ return ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'];
776
+ }
777
+ if (pane === 'packages') {
778
+ return ['packageNew', 'packageDrop', 'packageWanted', 'packageLatest'];
603
779
  }
604
- if (pane === 'tasks' || isTaskView(view)) return [...DASH_IDS, 'drop'];
605
- return navExcept(['dashDiffs']);
780
+ if (pane === 'tasks' || isTaskView(view)) return ['drop', 'check'];
781
+ return GIT_FOOTER;
606
782
  };
607
783
 
608
784
  const footerLayout = (view, width) =>
@@ -612,6 +788,8 @@ const footerLayout = (view, width) =>
612
788
  disabled: dimFooterActions(view),
613
789
  backId: backFooterId(view),
614
790
  mark: backHintMark(view),
791
+ globals: view.pane === 'repos' ? [] : DASH_IDS,
792
+ tail: false,
615
793
  });
616
794
 
617
795
  module.exports = {