diffwatch 1.0.8 → 1.1.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.
package/AGENTS.md ADDED
@@ -0,0 +1,201 @@
1
+ # DiffWatch Agent Guidelines
2
+
3
+ This document provides coding standards and operational guidelines for agentic coding assistants working on the DiffWatch project.
4
+
5
+ ## Build, Lint, and Test Commands
6
+
7
+ ### Primary Commands
8
+ - **Build**: `npm run build` - Compiles TypeScript to JavaScript using `tsc`
9
+ - **Test**: `npm test` - Runs Jest test suite
10
+ - **Dev**: `npm run dev` - Runs the application in development mode with `ts-node`
11
+ - **Start**: `npm start` - Runs the compiled application from `dist/`
12
+
13
+ ### Testing Specific Files
14
+ - Run a single test file: `npm test -- tests/git.test.ts`
15
+ - Run tests matching a pattern: `npm test -- --testNamePattern="should return file status"`
16
+
17
+ ### Linting and Type Checking
18
+ - **Type Check**: `npx tsc --noEmit` - TypeScript type checking without compilation
19
+ - **No explicit linting configured** - TypeScript strict mode serves as the primary code quality gate
20
+
21
+ ## Code Style Guidelines
22
+
23
+ ### Language and Module System
24
+ - **Language**: TypeScript with strict mode enabled
25
+ - **Target**: ES6 JavaScript output
26
+ - **Module System**: CommonJS output (`"type": "commonjs"` in package.json)
27
+ - **Source**: ES6 modules with `import`/`export` syntax
28
+
29
+ ### Import/Export Style
30
+ ```typescript
31
+ // ES6 imports at the top of files
32
+ import { simpleGit, SimpleGit, StatusResult } from 'simple-git';
33
+ import fs from 'fs/promises';
34
+ import chalk from 'chalk';
35
+
36
+ // Named exports for utilities
37
+ export interface FileStatus {
38
+ path: string;
39
+ status: 'modified' | 'added' | 'deleted' | 'unstaged' | 'unknown' | 'unchanged';
40
+ mtime?: Date;
41
+ }
42
+
43
+ // Default export for main classes
44
+ export class GitHandler {
45
+ // ...
46
+ }
47
+ ```
48
+
49
+ ### Naming Conventions
50
+ - **Variables/Functions**: camelCase (`filePath`, `getStatus`, `updateFileList`)
51
+ - **Classes/Interfaces**: PascalCase (`GitHandler`, `FileStatus`)
52
+ - **Constants**: UPPER_CASE (not commonly used in this codebase)
53
+ - **Files**: kebab-case for directories (`diff-formatter.ts`), camelCase for utilities
54
+ - **Test Files**: Match source filename with `.test.ts` extension (`git.test.ts`)
55
+
56
+ ### Code Structure and Patterns
57
+ - **Async/Await**: Preferred over Promises for asynchronous operations
58
+ - **Error Handling**: Try-catch blocks with descriptive error messages
59
+ - **Type Safety**: Use interfaces for data structures, avoid `any` type
60
+ - **Class Design**: Private properties with public methods, constructor injection
61
+ - **Functional Programming**: Pure functions where possible, avoid side effects
62
+
63
+ ### Formatting and Style
64
+ - **Indentation**: 2 spaces (no tabs)
65
+ - **Semicolons**: Not used (TypeScript ASI)
66
+ - **Quotes**: Single quotes for strings (`'string'`)
67
+ - **Line Length**: No strict limit, break long lines naturally
68
+ - **Blank Lines**: Use sparingly between logical code blocks
69
+ - **Comments**: Minimal comments, rely on descriptive naming
70
+ - **Braces**: Same line for functions/classes, new line for control structures
71
+
72
+ ### Example Code Style
73
+ ```typescript
74
+ export class GitHandler {
75
+ private git: SimpleGit;
76
+
77
+ constructor(workingDir: string = process.cwd()) {
78
+ this.git = simpleGit(workingDir);
79
+ }
80
+
81
+ async getStatus(): Promise<FileStatus[]> {
82
+ const status: StatusResult = await this.git.status();
83
+ const uniqueFiles = new Map<string, FileStatus>();
84
+
85
+ status.modified.forEach(path => {
86
+ uniqueFiles.set(path, { path, status: 'modified' });
87
+ });
88
+
89
+ // Process and return sorted results
90
+ const fileArray = Array.from(uniqueFiles.values());
91
+ return fileArray.sort((a, b) => {
92
+ const timeDiff = (b.mtime || new Date(0)).getTime() - (a.mtime || new Date(0)).getTime();
93
+ return timeDiff !== 0 ? timeDiff : a.path.localeCompare(b.path);
94
+ });
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### Error Handling Patterns
100
+ ```typescript
101
+ async getFileContent(filePath: string): Promise<string> {
102
+ try {
103
+ return await fs.readFile(filePath, 'utf8');
104
+ } catch (error) {
105
+ return `Error reading file: ${error}`;
106
+ }
107
+ }
108
+
109
+ async isRepo(): Promise<boolean> {
110
+ try {
111
+ return await this.git.checkIsRepo();
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Testing Guidelines
119
+ - **Framework**: Jest with ts-jest for TypeScript support
120
+ - **Mocking**: Mock external dependencies (simple-git, fs/promises)
121
+ - **Test Structure**: `describe` blocks for classes, `it` blocks for methods
122
+ - **Assertions**: Use Jest matchers (`expect().toBe()`, `expect().toEqual()`)
123
+ - **Test Naming**: Descriptive sentences starting with "should"
124
+ - **Coverage**: Aim for critical path coverage, especially Git operations
125
+
126
+ ```typescript
127
+ describe('GitHandler', () => {
128
+ let gitHandler: GitHandler;
129
+ let mockGit: any;
130
+
131
+ beforeEach(() => {
132
+ mockGit = { checkIsRepo: jest.fn() };
133
+ (simpleGit as jest.Mock).mockReturnValue(mockGit);
134
+ gitHandler = new GitHandler();
135
+ });
136
+
137
+ it('should return true if directory is a repo', async () => {
138
+ mockGit.checkIsRepo.mockResolvedValue(true);
139
+ const result = await gitHandler.isRepo();
140
+ expect(result).toBe(true);
141
+ });
142
+ });
143
+ ```
144
+
145
+ ### File Organization
146
+ - **Source Code**: `src/` directory with subdirectories for utilities
147
+ - **Tests**: `tests/` directory mirroring source structure
148
+ - **Build Output**: `dist/` directory (gitignored)
149
+ - **Configuration**: Root level for `package.json`, `tsconfig.json`, `jest.config.js`
150
+
151
+ ### TypeScript Configuration
152
+ - **Strict Mode**: Enabled for type safety
153
+ - **Source Maps**: Enabled for debugging
154
+ - **Declaration Files**: Generated for library usage
155
+ - **Module Resolution**: Node.js style
156
+ - **ES Module Interop**: Enabled for compatibility
157
+
158
+ ### Dependencies
159
+ - **Runtime**: chalk (colors), simple-git (Git operations), neo-neo-blessed (TUI)
160
+ - **Dev**: Jest, ts-jest, ts-node, TypeScript
161
+ - **Keep dependencies minimal** - only add when necessary for functionality
162
+
163
+ ### Git and Version Control
164
+ - **Branching**: Feature branches for development
165
+ - **Commits**: Descriptive messages focusing on "why" rather than "what"
166
+ - **Tags**: Semantic versioning (current: 1.0.8)
167
+ - **Hooks**: No custom hooks configured
168
+
169
+ ### Security Considerations
170
+ - **Input Validation**: Validate file paths and user inputs
171
+ - **Error Messages**: Avoid exposing sensitive system information
172
+ - **Dependencies**: Keep updated, audit regularly
173
+ - **Secrets**: Never commit sensitive data
174
+
175
+ ### Performance Guidelines
176
+ - **Async Operations**: Use efficient patterns, avoid blocking operations
177
+ - **Memory Management**: Clean up resources, avoid memory leaks
178
+ - **File I/O**: Handle large files appropriately
179
+ - **Real-time Updates**: Debounce rapid operations (150ms in current code)
180
+
181
+ ### Code Review Checklist
182
+ - [ ] TypeScript compiles without errors
183
+ - [ ] Tests pass and cover new functionality
184
+ - [ ] Follows established naming and style conventions
185
+ - [ ] Error handling appropriate for use case
186
+ - [ ] No console.log statements in production code
187
+ - [ ] Dependencies justified and minimal
188
+ - [ ] Documentation updated if public API changes</content>
189
+ <parameter name="filePath">D:\SystemFolders\Downloads\Dev\node\diffwatch\AGENTS.md
190
+
191
+ <!-- GENERAL-RULES-RULES-START -->
192
+
193
+ ## You must always follow these rules:
194
+
195
+ - If you are unsure about any requirement, behavior, or implementation detail, ask clarifying questions **before** writing code.
196
+ - At every step, provide a **high-level explanation** of what changes were made and why.
197
+ - After implementing changes or new features, always provide a list of **suggestions or improvements**, even if they differ from the user's original request.
198
+ - If the user requests a change or feature that is an **anti-pattern** or violates well-established best practices, clearly explain the issue and ask for confirmation before proceeding.
199
+ - Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.
200
+
201
+ <!-- GENERAL-RULES-RULES-END -->
package/dist/index.js CHANGED
@@ -18,27 +18,38 @@ const chalk_1 = __importDefault(require("chalk"));
18
18
  const child_process_1 = require("child_process");
19
19
  const git_1 = require("./utils/git");
20
20
  const diff_formatter_1 = require("./utils/diff-formatter");
21
+ const fs_1 = require("fs");
22
+ const path_1 = require("path");
23
+ const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)((0, path_1.dirname)(__filename), '..', 'package.json'), 'utf-8'));
21
24
  function main() {
22
25
  return __awaiter(this, void 0, void 0, function* () {
23
26
  const args = process.argv.slice(2);
24
27
  let repoPath = process.cwd();
25
28
  const showHelp = () => {
26
- console.log(`
27
- Usage: diffwatch [path] [options]
28
-
29
- Arguments:
30
- path Path to the git repository (default: current directory)
31
-
32
- Options:
33
- -h, --help Show help information
29
+ console.log(`
30
+ Usage: diffwatch [path]
31
+
32
+ Arguments:
33
+ path Path to the git repository (default: current directory)
34
+
35
+ Options:
36
+ -h, --help Show help information
37
+ -v, --version Show version information
34
38
  `);
35
39
  process.exit(0);
36
40
  };
41
+ const showVersion = () => {
42
+ console.log(`diffwatch v${packageJson.version}`);
43
+ process.exit(0);
44
+ };
37
45
  const positionalArgs = [];
38
46
  for (let i = 0; i < args.length; i++) {
39
47
  if (args[i] === '-h' || args[i] === '--help') {
40
48
  showHelp();
41
49
  }
50
+ else if (args[i] === '-v' || args[i] === '--version') {
51
+ showVersion();
52
+ }
42
53
  else if (args[i].startsWith('-')) {
43
54
  // Ignore unknown flags or handle them if needed
44
55
  }
@@ -72,7 +83,7 @@ Options:
72
83
  });
73
84
  // Explicitly enable mouse tracking
74
85
  screen.program.enableMouse();
75
- const fileList = blessed.list({
86
+ let fileList = blessed.list({
76
87
  top: 0,
77
88
  left: 0,
78
89
  width: '30%',
@@ -93,6 +104,38 @@ Options:
93
104
  },
94
105
  border: { type: 'line' },
95
106
  });
107
+ const createFileList = () => {
108
+ const newList = blessed.list({
109
+ top: 0,
110
+ left: 0,
111
+ width: '30%',
112
+ height: '99%',
113
+ label: 'Files (0)',
114
+ keys: true,
115
+ vi: true,
116
+ mouse: true,
117
+ tags: true,
118
+ scrollbar: {
119
+ ch: ' ',
120
+ track: { bg: 'white' },
121
+ style: { bg: 'blue' },
122
+ },
123
+ style: {
124
+ selected: { fg: 'black', bg: 'white' },
125
+ border: { fg: 'white' },
126
+ },
127
+ border: { type: 'line' },
128
+ });
129
+ newList.on('select item', () => {
130
+ scheduleDiffUpdate();
131
+ });
132
+ newList.key(['up', 'down'], () => {
133
+ scheduleDiffUpdate();
134
+ });
135
+ newList.on('wheeldown', () => handleScroll('down'));
136
+ newList.on('wheelup', () => handleScroll('up'));
137
+ return newList;
138
+ };
96
139
  const diffView = blessed.scrollabletext({
97
140
  top: 0,
98
141
  left: '30%',
@@ -115,7 +158,7 @@ Options:
115
158
  });
116
159
  const searchBox = blessed.box({
117
160
  top: 'center',
118
- left: 'center',
161
+ left: '40%', // Center of right pane (30% + 70%/2 = 65%)
119
162
  width: '50%',
120
163
  height: 3,
121
164
  label: ' Search ',
@@ -136,11 +179,11 @@ Options:
136
179
  // Confirmation dialog for revert
137
180
  const confirmDialog = blessed.box({
138
181
  top: 'center',
139
- left: 'center',
182
+ left: '45%', // Center of right pane (30% + 70%/2 = 65%)
140
183
  width: '38%',
141
184
  label: ' Confirm Revert ',
142
185
  height: 3,
143
- content: 'Press ENTER key to confirm revert or ESC to cancel.',
186
+ content: 'Press SPACE key to confirm revert or ESC to cancel.',
144
187
  border: { type: 'line' },
145
188
  style: {
146
189
  fg: 'yellow',
@@ -149,22 +192,70 @@ Options:
149
192
  },
150
193
  hidden: true,
151
194
  });
195
+ const notificationBox = blessed.box({
196
+ top: 'center',
197
+ left: '45%', // Center of right pane (30% + 70%/2 = 65%)
198
+ width: '38%',
199
+ height: 3,
200
+ label: ' Notification ',
201
+ border: { type: 'line' },
202
+ style: {
203
+ fg: 'green',
204
+ bg: 'black',
205
+ border: { fg: 'green' }
206
+ },
207
+ hidden: true,
208
+ });
209
+ const showNotification = (message, color = 'green') => {
210
+ const previouslyFocused = screen.focused;
211
+ notificationBox.setContent(message);
212
+ notificationBox.style = {
213
+ fg: color,
214
+ bg: 'black',
215
+ border: { fg: color }
216
+ };
217
+ notificationBox.show();
218
+ notificationBox.setFront();
219
+ screen.render();
220
+ setTimeout(() => {
221
+ notificationBox.hide();
222
+ if (previouslyFocused && previouslyFocused !== notificationBox) {
223
+ previouslyFocused.focus();
224
+ }
225
+ else {
226
+ fileList.focus();
227
+ }
228
+ updateBorders();
229
+ screen.render();
230
+ }, 3000);
231
+ };
232
+ // Branch display in bottom right
233
+ const branchBox = blessed.box({
234
+ bottom: 0,
235
+ right: 0,
236
+ width: 'shrink',
237
+ height: 1,
238
+ align: 'left',
239
+ content: chalk_1.default.cyan('Branch: '),
240
+ style: {
241
+ fg: 'white',
242
+ },
243
+ });
152
244
  // Footer box to show shortcuts - aligned with the panes
153
245
  const footer = blessed.box({
154
246
  bottom: 0,
155
247
  left: 0,
156
248
  width: '100%',
157
249
  height: 1,
158
- align: 'center',
159
250
  content: chalk_1.default.green('←→') + ' Switch |' + chalk_1.default.green(' ⏎') + ' Open | ' + chalk_1.default.green('S') + ' Search | ' + chalk_1.default.green('R') + ' Revert | ' + chalk_1.default.green('Q') + ' Quit ',
160
251
  });
161
- // Adjust footer to align with the panes
162
- // Note: We'll adjust this after screen initialization
163
252
  screen.append(fileList);
164
253
  screen.append(diffView);
165
254
  screen.append(searchBox);
255
+ screen.append(notificationBox);
166
256
  screen.append(confirmDialog);
167
257
  screen.append(footer);
258
+ screen.append(branchBox);
168
259
  const updateBorders = () => {
169
260
  fileList.style.border.fg = screen.focused === fileList ? 'yellow' : 'white';
170
261
  diffView.style.border.fg = screen.focused === diffView ? 'yellow' : 'white';
@@ -174,6 +265,9 @@ Options:
174
265
  let lastSelectedPath = null;
175
266
  let diffUpdateTimeout = null;
176
267
  let currentSearchTerm = '';
268
+ let currentBranch = '';
269
+ let lastFilePaths = [];
270
+ let isUpdatingList = false;
177
271
  const scheduleDiffUpdate = () => {
178
272
  if (diffUpdateTimeout)
179
273
  clearTimeout(diffUpdateTimeout);
@@ -182,17 +276,18 @@ Options:
182
276
  }), 150); // 150ms debounce
183
277
  };
184
278
  const handleScroll = (direction) => {
185
- if (screen.focused === fileList) {
279
+ const focused = screen.focused;
280
+ if (focused === fileList) {
186
281
  if (direction === 'up') {
187
- fileList.up(1);
282
+ focused.up(1);
188
283
  }
189
284
  else {
190
- fileList.down(1);
285
+ focused.down(1);
191
286
  }
192
287
  scheduleDiffUpdate();
193
288
  screen.render();
194
289
  }
195
- else if (screen.focused === diffView) {
290
+ else if (focused === diffView) {
196
291
  const scrollAmount = direction === 'up' ? -2 : 2;
197
292
  diffView.scroll(scrollAmount);
198
293
  screen.render();
@@ -206,9 +301,9 @@ Options:
206
301
  // Attach custom scroll handlers to widgets (captures wheel even if hovering specific widget)
207
302
  // We use widget-level listeners now that screen.mouse is true.
208
303
  // We attach to both to ensure the event is caught regardless of where the mouse is.
304
+ // We use widget-level listeners now that screen.mouse is true.
305
+ // We attach to both to ensure the event is caught regardless of where the mouse is.
209
306
  // The handleScroll function will then decide WHAT to scroll based on focus.
210
- fileList.on('wheeldown', () => handleScroll('down'));
211
- fileList.on('wheelup', () => handleScroll('up'));
212
307
  diffView.on('wheeldown', () => handleScroll('down'));
213
308
  diffView.on('wheelup', () => handleScroll('up'));
214
309
  // Also listen on screen for events that might miss the widgets (margins, borders)
@@ -230,9 +325,25 @@ Options:
230
325
  console.error(`Failed to open ${filePath}: ${error}`);
231
326
  }
232
327
  };
328
+ const updateBranch = () => __awaiter(this, void 0, void 0, function* () {
329
+ const branch = yield gitHandler.getBranch();
330
+ if (branch !== currentBranch) {
331
+ currentBranch = branch;
332
+ branchBox.setContent(chalk_1.default.cyan('Branch: ') + chalk_1.default.yellow(branch));
333
+ screen.render();
334
+ }
335
+ });
233
336
  const updateDiff = () => __awaiter(this, void 0, void 0, function* () {
234
337
  const selectedIndex = fileList.selected;
235
338
  const selectedFile = currentFiles[selectedIndex];
339
+ if (!selectedFile) {
340
+ // No valid file selected, clear diff view
341
+ diffView.setContent('Select a file to view diff.');
342
+ diffView.setLabel(' Diff () ');
343
+ lastSelectedPath = null;
344
+ screen.render();
345
+ return;
346
+ }
236
347
  if (selectedFile) {
237
348
  let content = '';
238
349
  let label = ` Diff (${selectedFile.path}) `;
@@ -284,9 +395,10 @@ Options:
284
395
  });
285
396
  const updateFileList = () => __awaiter(this, void 0, void 0, function* () {
286
397
  var _a;
287
- // Preserve selected file path and scroll positions
398
+ if (isUpdatingList)
399
+ return;
400
+ isUpdatingList = true;
288
401
  const selectedPath = (_a = currentFiles[fileList.selected]) === null || _a === void 0 ? void 0 : _a.path;
289
- const diffScroll = diffView.scrollTop;
290
402
  let files;
291
403
  if (currentSearchTerm) {
292
404
  files = yield gitHandler.searchFiles(currentSearchTerm);
@@ -295,7 +407,7 @@ Options:
295
407
  files = yield gitHandler.getStatus();
296
408
  }
297
409
  currentFiles = files;
298
- const items = files.map(f => {
410
+ const items = files.map((f) => {
299
411
  let color = '{white-fg}';
300
412
  if (f.status === 'added')
301
413
  color = '{green-fg}';
@@ -307,61 +419,62 @@ Options:
307
419
  color = '{white-fg}';
308
420
  else if (f.status === 'unchanged')
309
421
  color = '{grey-fg}';
310
- return `${color}${f.path}{/}`;
422
+ let statusSymbol = '';
423
+ if (f.status === 'added')
424
+ statusSymbol = 'A ';
425
+ else if (f.status === 'deleted')
426
+ statusSymbol = 'D ';
427
+ else if (f.status === 'modified')
428
+ statusSymbol = 'M ';
429
+ else if (f.status === 'unstaged')
430
+ statusSymbol = '? ';
431
+ else if (f.status === 'unchanged')
432
+ statusSymbol = ' ';
433
+ return `${color}${statusSymbol}${f.path}{/}`;
311
434
  });
312
- fileList.setItems(items);
313
435
  const labelTitle = currentSearchTerm ? `Files (${files.length}) - Searching: "${currentSearchTerm}"` : `Files (${files.length})`;
314
- fileList.setLabel(labelTitle);
436
+ const newFilePaths = files.map(f => f.path);
437
+ const selectedFileStillExists = selectedPath ? newFilePaths.includes(selectedPath) : false;
438
+ const listChanged = lastFilePaths.length !== newFilePaths.length ||
439
+ lastFilePaths.some(path => !newFilePaths.includes(path)) ||
440
+ newFilePaths.some(path => !lastFilePaths.includes(path));
315
441
  if (items.length > 0) {
316
- // Restore selection by path if possible
317
442
  const newSelectedIndex = selectedPath ? currentFiles.findIndex(f => f.path === selectedPath) : -1;
318
- fileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
319
- // Cancel any pending diff update and update immediately
443
+ const oldFileList = fileList;
444
+ const newFileList = createFileList();
445
+ newFileList.setLabel(labelTitle);
446
+ newFileList.setItems(items);
447
+ newFileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
448
+ if (!selectedFileStillExists || listChanged) {
449
+ lastSelectedPath = null;
450
+ }
451
+ lastFilePaths = newFilePaths;
320
452
  if (diffUpdateTimeout) {
321
453
  clearTimeout(diffUpdateTimeout);
322
454
  diffUpdateTimeout = null;
323
455
  }
324
456
  yield updateDiff();
457
+ oldFileList.destroy();
458
+ screen.remove(oldFileList);
459
+ fileList = newFileList;
460
+ screen.append(fileList);
325
461
  }
326
462
  else {
327
- // Clear the file list when there are no files
328
- fileList.clearItems();
463
+ const oldFileList = fileList;
464
+ const newFileList = createFileList();
465
+ newFileList.setLabel(labelTitle);
466
+ newFileList.setItems([]);
329
467
  diffView.setContent(currentSearchTerm ? `No files match "${currentSearchTerm}".` : 'No changes detected.');
330
468
  diffView.setLabel(' Diff () ');
469
+ lastSelectedPath = null;
470
+ lastFilePaths = [];
471
+ oldFileList.destroy();
472
+ screen.remove(oldFileList);
473
+ fileList = newFileList;
474
+ screen.append(fileList);
331
475
  }
332
- // Restore scroll positions if reasonably possible (reset if list changed drastically)
333
- // Actually, if we filter, the scroll position might be invalid.
334
- // Ideally we keep it 0 if it was 0 or just let the select() call handle scrolling to the item.
335
- // The previous implementation blindly restored scrollTop.
336
- // If the list shrunk, select() should have brought it into view.
337
- // We only explicitly restore if items.length > 0
338
- // But setting scroll to previous value might be wrong if the list is now shorter.
339
- // Safe to only restore diffView scroll as it depends on content, fileList is handled by select.
340
- diffView.scrollTop = diffScroll;
341
476
  screen.render();
342
- });
343
- fileList.on('select item', () => {
344
- scheduleDiffUpdate();
345
- });
346
- fileList.key(['up', 'down'], () => {
347
- scheduleDiffUpdate();
348
- });
349
- screen.key(['escape', 'q', 'C-c'], () => {
350
- if (!searchBox.hidden) {
351
- searchBox.hide();
352
- screen.render();
353
- fileList.focus();
354
- }
355
- else if (!confirmDialog.hidden) {
356
- // Close the confirmation dialog if it's open
357
- confirmDialog.hide();
358
- fileList.focus();
359
- screen.render();
360
- }
361
- else {
362
- screen.destroy();
363
- process.exit(0);
364
- }
477
+ isUpdatingList = false;
365
478
  });
366
479
  screen.key(['s'], () => {
367
480
  searchBox.show();
@@ -392,12 +505,16 @@ Options:
392
505
  updateBorders();
393
506
  });
394
507
  screen.key(['left'], () => {
395
- fileList.focus();
396
- updateBorders();
508
+ if (screen.focused !== fileList) {
509
+ fileList.focus();
510
+ updateBorders();
511
+ }
397
512
  });
398
513
  screen.key(['right'], () => {
399
- diffView.focus();
400
- updateBorders();
514
+ if (screen.focused !== diffView) {
515
+ diffView.focus();
516
+ updateBorders();
517
+ }
401
518
  });
402
519
  screen.key(['enter'], () => {
403
520
  const selectedIndex = fileList.selected;
@@ -411,44 +528,74 @@ Options:
411
528
  const selectedIndex = fileList.selected;
412
529
  const selectedFile = currentFiles[selectedIndex];
413
530
  if (selectedFile) {
414
- confirmDialog.setContent(`Press SPACE key to confirm revert or ESC to cancel.`);
415
531
  confirmDialog.show();
532
+ confirmDialog.setFront();
416
533
  confirmDialog.focus();
417
534
  screen.render();
418
535
  }
419
536
  }));
420
- // Handle confirmation dialog response with SPACE key
537
+ // Handle dialog keys
421
538
  confirmDialog.key(['space'], () => __awaiter(this, void 0, void 0, function* () {
422
- confirmDialog.hide();
423
539
  const selectedIndex = fileList.selected;
424
540
  const selectedFile = currentFiles[selectedIndex];
425
541
  if (selectedFile) {
426
542
  try {
427
543
  yield gitHandler.revertFile(selectedFile.path);
428
- console.log(chalk_1.default.green(`File ${selectedFile.path} reverted successfully.`));
429
- // Refresh the file list after reverting
544
+ showNotification(`File ${selectedFile.path} reverted successfully.`, 'green');
430
545
  yield updateFileList();
431
546
  }
432
547
  catch (error) {
433
548
  const errorMessage = error instanceof Error ? error.message : String(error);
434
- console.error(chalk_1.default.red(`Error reverting file: ${errorMessage}`));
549
+ showNotification(`Error reverting file: ${errorMessage}`, 'red');
435
550
  }
436
551
  }
552
+ confirmDialog.hide();
437
553
  fileList.focus();
438
554
  screen.render();
439
555
  }));
440
- // Handle cancellation with ESC key
441
- confirmDialog.key(['escape', 'q'], () => {
556
+ confirmDialog.key(['escape'], () => {
442
557
  confirmDialog.hide();
443
558
  fileList.focus();
444
559
  screen.render();
445
560
  });
446
- setInterval(() => __awaiter(this, void 0, void 0, function* () {
447
- yield updateFileList();
448
- }), 5000);
561
+ yield updateBranch();
449
562
  yield updateFileList();
450
563
  fileList.focus();
451
564
  updateBorders();
565
+ // Set up periodic updates to detect new git changes
566
+ const updateInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
567
+ const currentlyFocused = screen.focused;
568
+ const wasFileListFocused = currentlyFocused === fileList;
569
+ yield updateBranch();
570
+ yield updateFileList();
571
+ // Restore focus to the same pane it was on before the update
572
+ if (wasFileListFocused) {
573
+ fileList.focus();
574
+ }
575
+ else if (currentlyFocused === diffView) {
576
+ diffView.focus();
577
+ }
578
+ updateBorders();
579
+ }), 2000); // Check for changes every 2 seconds
580
+ // Clean up interval on exit
581
+ screen.key(['escape', 'q', 'C-c'], () => {
582
+ // Don't handle ESC if confirmation dialog has focus or is visible
583
+ if (screen.focused === confirmDialog || !confirmDialog.hidden) {
584
+ return false;
585
+ }
586
+ if (!searchBox.hidden) {
587
+ searchBox.hide();
588
+ screen.render();
589
+ fileList.focus();
590
+ return false;
591
+ }
592
+ else {
593
+ // Clear interval only when actually exiting
594
+ clearInterval(updateInterval);
595
+ screen.destroy();
596
+ process.exit(0);
597
+ }
598
+ });
452
599
  });
453
600
  }
454
601
  main().catch(err => {