diffwatch 1.0.6 → 1.0.9

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,6 +18,9 @@ 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);
@@ -31,14 +34,22 @@ Arguments:
31
34
 
32
35
  Options:
33
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,21 +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
- content: chalk_1.default.cyan(' enter') + ': Open file | ' + chalk_1.default.cyan('s') + ': Search | ' + chalk_1.default.cyan('r') + ': Revert file | ' + chalk_1.default.cyan('tab') + ': Switch panes | ' + chalk_1.default.cyan('q/esc') + ': Quit ',
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 ',
159
251
  });
160
- // Adjust footer to align with the panes
161
- // Note: We'll adjust this after screen initialization
162
252
  screen.append(fileList);
163
253
  screen.append(diffView);
164
254
  screen.append(searchBox);
255
+ screen.append(notificationBox);
165
256
  screen.append(confirmDialog);
166
257
  screen.append(footer);
258
+ screen.append(branchBox);
167
259
  const updateBorders = () => {
168
260
  fileList.style.border.fg = screen.focused === fileList ? 'yellow' : 'white';
169
261
  diffView.style.border.fg = screen.focused === diffView ? 'yellow' : 'white';
@@ -173,6 +265,9 @@ Options:
173
265
  let lastSelectedPath = null;
174
266
  let diffUpdateTimeout = null;
175
267
  let currentSearchTerm = '';
268
+ let currentBranch = '';
269
+ let lastFilePaths = [];
270
+ let isUpdatingList = false;
176
271
  const scheduleDiffUpdate = () => {
177
272
  if (diffUpdateTimeout)
178
273
  clearTimeout(diffUpdateTimeout);
@@ -181,17 +276,18 @@ Options:
181
276
  }), 150); // 150ms debounce
182
277
  };
183
278
  const handleScroll = (direction) => {
184
- if (screen.focused === fileList) {
279
+ const focused = screen.focused;
280
+ if (focused === fileList) {
185
281
  if (direction === 'up') {
186
- fileList.up(1);
282
+ focused.up(1);
187
283
  }
188
284
  else {
189
- fileList.down(1);
285
+ focused.down(1);
190
286
  }
191
287
  scheduleDiffUpdate();
192
288
  screen.render();
193
289
  }
194
- else if (screen.focused === diffView) {
290
+ else if (focused === diffView) {
195
291
  const scrollAmount = direction === 'up' ? -2 : 2;
196
292
  diffView.scroll(scrollAmount);
197
293
  screen.render();
@@ -205,9 +301,9 @@ Options:
205
301
  // Attach custom scroll handlers to widgets (captures wheel even if hovering specific widget)
206
302
  // We use widget-level listeners now that screen.mouse is true.
207
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.
208
306
  // The handleScroll function will then decide WHAT to scroll based on focus.
209
- fileList.on('wheeldown', () => handleScroll('down'));
210
- fileList.on('wheelup', () => handleScroll('up'));
211
307
  diffView.on('wheeldown', () => handleScroll('down'));
212
308
  diffView.on('wheelup', () => handleScroll('up'));
213
309
  // Also listen on screen for events that might miss the widgets (margins, borders)
@@ -229,9 +325,25 @@ Options:
229
325
  console.error(`Failed to open ${filePath}: ${error}`);
230
326
  }
231
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
+ });
232
336
  const updateDiff = () => __awaiter(this, void 0, void 0, function* () {
233
337
  const selectedIndex = fileList.selected;
234
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
+ }
235
347
  if (selectedFile) {
236
348
  let content = '';
237
349
  let label = ` Diff (${selectedFile.path}) `;
@@ -283,9 +395,10 @@ Options:
283
395
  });
284
396
  const updateFileList = () => __awaiter(this, void 0, void 0, function* () {
285
397
  var _a;
286
- // Preserve selected file path and scroll positions
398
+ if (isUpdatingList)
399
+ return;
400
+ isUpdatingList = true;
287
401
  const selectedPath = (_a = currentFiles[fileList.selected]) === null || _a === void 0 ? void 0 : _a.path;
288
- const diffScroll = diffView.scrollTop;
289
402
  let files;
290
403
  if (currentSearchTerm) {
291
404
  files = yield gitHandler.searchFiles(currentSearchTerm);
@@ -294,7 +407,7 @@ Options:
294
407
  files = yield gitHandler.getStatus();
295
408
  }
296
409
  currentFiles = files;
297
- const items = files.map(f => {
410
+ const items = files.map((f) => {
298
411
  let color = '{white-fg}';
299
412
  if (f.status === 'added')
300
413
  color = '{green-fg}';
@@ -308,59 +421,49 @@ Options:
308
421
  color = '{grey-fg}';
309
422
  return `${color}${f.path}{/}`;
310
423
  });
311
- fileList.setItems(items);
312
424
  const labelTitle = currentSearchTerm ? `Files (${files.length}) - Searching: "${currentSearchTerm}"` : `Files (${files.length})`;
313
- fileList.setLabel(labelTitle);
425
+ const newFilePaths = files.map(f => f.path);
426
+ const selectedFileStillExists = selectedPath ? newFilePaths.includes(selectedPath) : false;
427
+ const listChanged = lastFilePaths.length !== newFilePaths.length ||
428
+ lastFilePaths.some(path => !newFilePaths.includes(path)) ||
429
+ newFilePaths.some(path => !lastFilePaths.includes(path));
314
430
  if (items.length > 0) {
315
- // Restore selection by path if possible
316
431
  const newSelectedIndex = selectedPath ? currentFiles.findIndex(f => f.path === selectedPath) : -1;
317
- fileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
318
- // Cancel any pending diff update and update immediately
432
+ const oldFileList = fileList;
433
+ const newFileList = createFileList();
434
+ newFileList.setLabel(labelTitle);
435
+ newFileList.setItems(items);
436
+ newFileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
437
+ if (!selectedFileStillExists || listChanged) {
438
+ lastSelectedPath = null;
439
+ }
440
+ lastFilePaths = newFilePaths;
319
441
  if (diffUpdateTimeout) {
320
442
  clearTimeout(diffUpdateTimeout);
321
443
  diffUpdateTimeout = null;
322
444
  }
323
445
  yield updateDiff();
446
+ oldFileList.destroy();
447
+ screen.remove(oldFileList);
448
+ fileList = newFileList;
449
+ screen.append(fileList);
324
450
  }
325
451
  else {
326
- // Clear the file list when there are no files
327
- fileList.clearItems();
452
+ const oldFileList = fileList;
453
+ const newFileList = createFileList();
454
+ newFileList.setLabel(labelTitle);
455
+ newFileList.setItems([]);
328
456
  diffView.setContent(currentSearchTerm ? `No files match "${currentSearchTerm}".` : 'No changes detected.');
329
457
  diffView.setLabel(' Diff () ');
458
+ lastSelectedPath = null;
459
+ lastFilePaths = [];
460
+ oldFileList.destroy();
461
+ screen.remove(oldFileList);
462
+ fileList = newFileList;
463
+ screen.append(fileList);
330
464
  }
331
- // Restore scroll positions if reasonably possible (reset if list changed drastically)
332
- // Actually, if we filter, the scroll position might be invalid.
333
- // Ideally we keep it 0 if it was 0 or just let the select() call handle scrolling to the item.
334
- // The previous implementation blindly restored scrollTop.
335
- // If the list shrunk, select() should have brought it into view.
336
- // We only explicitly restore if items.length > 0
337
- // But setting scroll to previous value might be wrong if the list is now shorter.
338
- // Safe to only restore diffView scroll as it depends on content, fileList is handled by select.
339
- diffView.scrollTop = diffScroll;
340
465
  screen.render();
341
- });
342
- fileList.on('select item', () => {
343
- scheduleDiffUpdate();
344
- });
345
- fileList.key(['up', 'down'], () => {
346
- scheduleDiffUpdate();
347
- });
348
- screen.key(['escape', 'q', 'C-c'], () => {
349
- if (!searchBox.hidden) {
350
- searchBox.hide();
351
- screen.render();
352
- fileList.focus();
353
- }
354
- else if (!confirmDialog.hidden) {
355
- // Close the confirmation dialog if it's open
356
- confirmDialog.hide();
357
- fileList.focus();
358
- screen.render();
359
- }
360
- else {
361
- screen.destroy();
362
- process.exit(0);
363
- }
466
+ isUpdatingList = false;
364
467
  });
365
468
  screen.key(['s'], () => {
366
469
  searchBox.show();
@@ -391,12 +494,16 @@ Options:
391
494
  updateBorders();
392
495
  });
393
496
  screen.key(['left'], () => {
394
- fileList.focus();
395
- updateBorders();
497
+ if (screen.focused !== fileList) {
498
+ fileList.focus();
499
+ updateBorders();
500
+ }
396
501
  });
397
502
  screen.key(['right'], () => {
398
- diffView.focus();
399
- updateBorders();
503
+ if (screen.focused !== diffView) {
504
+ diffView.focus();
505
+ updateBorders();
506
+ }
400
507
  });
401
508
  screen.key(['enter'], () => {
402
509
  const selectedIndex = fileList.selected;
@@ -410,44 +517,74 @@ Options:
410
517
  const selectedIndex = fileList.selected;
411
518
  const selectedFile = currentFiles[selectedIndex];
412
519
  if (selectedFile) {
413
- confirmDialog.setContent(`Press SPACE key to confirm revert or ESC to cancel.`);
414
520
  confirmDialog.show();
521
+ confirmDialog.setFront();
415
522
  confirmDialog.focus();
416
523
  screen.render();
417
524
  }
418
525
  }));
419
- // Handle confirmation dialog response with SPACE key
526
+ // Handle dialog keys
420
527
  confirmDialog.key(['space'], () => __awaiter(this, void 0, void 0, function* () {
421
- confirmDialog.hide();
422
528
  const selectedIndex = fileList.selected;
423
529
  const selectedFile = currentFiles[selectedIndex];
424
530
  if (selectedFile) {
425
531
  try {
426
532
  yield gitHandler.revertFile(selectedFile.path);
427
- console.log(chalk_1.default.green(`File ${selectedFile.path} reverted successfully.`));
428
- // Refresh the file list after reverting
533
+ showNotification(`File ${selectedFile.path} reverted successfully.`, 'green');
429
534
  yield updateFileList();
430
535
  }
431
536
  catch (error) {
432
537
  const errorMessage = error instanceof Error ? error.message : String(error);
433
- console.error(chalk_1.default.red(`Error reverting file: ${errorMessage}`));
538
+ showNotification(`Error reverting file: ${errorMessage}`, 'red');
434
539
  }
435
540
  }
541
+ confirmDialog.hide();
436
542
  fileList.focus();
437
543
  screen.render();
438
544
  }));
439
- // Handle cancellation with ESC key
440
- confirmDialog.key(['escape', 'q'], () => {
545
+ confirmDialog.key(['escape'], () => {
441
546
  confirmDialog.hide();
442
547
  fileList.focus();
443
548
  screen.render();
444
549
  });
445
- setInterval(() => __awaiter(this, void 0, void 0, function* () {
446
- yield updateFileList();
447
- }), 5000);
550
+ yield updateBranch();
448
551
  yield updateFileList();
449
552
  fileList.focus();
450
553
  updateBorders();
554
+ // Set up periodic updates to detect new git changes
555
+ const updateInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
556
+ const currentlyFocused = screen.focused;
557
+ const wasFileListFocused = currentlyFocused === fileList;
558
+ yield updateBranch();
559
+ yield updateFileList();
560
+ // Restore focus to the same pane it was on before the update
561
+ if (wasFileListFocused) {
562
+ fileList.focus();
563
+ }
564
+ else if (currentlyFocused === diffView) {
565
+ diffView.focus();
566
+ }
567
+ updateBorders();
568
+ }), 2000); // Check for changes every 2 seconds
569
+ // Clean up interval on exit
570
+ screen.key(['escape', 'q', 'C-c'], () => {
571
+ // Don't handle ESC if confirmation dialog has focus or is visible
572
+ if (screen.focused === confirmDialog || !confirmDialog.hidden) {
573
+ return false;
574
+ }
575
+ if (!searchBox.hidden) {
576
+ searchBox.hide();
577
+ screen.render();
578
+ fileList.focus();
579
+ return false;
580
+ }
581
+ else {
582
+ // Clear interval only when actually exiting
583
+ clearInterval(updateInterval);
584
+ screen.destroy();
585
+ process.exit(0);
586
+ }
587
+ });
451
588
  });
452
589
  }
453
590
  main().catch(err => {