jarq 0.7.4

An interactive jq-like JSON query tool with a TUI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# Code Review: jarq - Interactive JSON Query Tool

**Review Date:** 2026-01-28
**Reviewer:** Claude Opus 4.5
**Codebase Version:** 0.7.3

---

## Executive Summary

The jarq codebase is generally well-structured with clean separation of concerns. The core filter evaluation system is well-tested with extensive unit tests covering parsing and evaluation. The architecture demonstrates good understanding of performance considerations (virtualized rendering, async loading, pipe caching).

However, there are several areas that could benefit from improvement in terms of testability, maintainability, and potential bugs. This review identifies 20+ issues across 7 categories with severity and complexity ratings.

### Rating Legend

**Severity:**
- šŸ”“ **Critical** - Could cause data loss, crashes, or security issues
- 🟠 **High** - Significant impact on reliability or maintainability
- 🟔 **Medium** - Moderate impact, should be addressed
- 🟢 **Low** - Minor issues, nice-to-have improvements

**Complexity:**
- ⬛ **High** - Requires significant refactoring or architectural changes
- ⬜ **Medium** - Moderate effort, localized changes
- ā—½ **Low** - Simple fix, minimal changes required

---

## 1. Potential Bugs & Issues

### 1.1 Race Condition in Worker Cancellation

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ā—½ Low | `worker.rs:79-82` |

```rust
// Skip if newer request waiting (implicit cancellation)
if !request_rx.is_empty() {
    continue;
}
```

**Issue:** TOCTOU (Time-of-check to time-of-use) race condition. Between checking `is_empty()` and receiving the next request, another request could be enqueued. While not catastrophic, this can lead to unnecessary skipping of valid requests.

**Impact:** Occasionally, a filter evaluation might be skipped when it shouldn't be, causing stale results to display briefly.

**Recommendation:** Accept the race as benign (document it), or use a more sophisticated request tracking mechanism with sequence numbers that are checked after evaluation completes.

---

### 1.2 Silent Clipboard Failures āœ… RESOLVED

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ā—½ Low | `app/mod.rs:324-344` |

~~**Issue:** No user feedback when clipboard operations fail.~~

**Resolution:** Added status message system:
- `App` now has `status_message` field with auto-expiring messages (2 seconds)
- `copy_filter_to_clipboard` shows success message with copied command
- Clipboard errors show error message
- Status message appears above the filter input line (white text)

**Bonus:** Extracted all UI colors into `src/theme.rs` module:
- Centralized color definitions for JSON syntax, filter input, status bar, help popup, etc.
- Uses `THEME` static for easy access throughout UI code
- Foundation for future theme customization

---

### 1.3 Unconstrained Cache Growth

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟠 High | ⬜ Medium | `worker.rs:115-120` |

```rust
if let Ok(ref values) = result {
    cache = Some(PipeCache {
        filter_text: req.filter_text.clone(),
        values: Arc::new(values.clone()),
        slurp_mode: req.slurp_mode,
    });
}
```

**Issue:** If the filter produces very large intermediate results, these are cached indefinitely. The cache clones the evaluation result (`Arc::new(values.clone())`), effectively doubling memory usage for that intermediate result.

**Impact:** For filters like `.[] | .` on a large array, the cache stores all intermediate values. Combined with the 3-5x memory overhead of `simd_json::OwnedValue`, a 128MB file could result in 500MB+ cached, plus the original result sent to the app.

**Why this is inherent:** The result needs to exist in two places - the cache (for future suffix evaluation) and the app (for display). Since filter evaluation produces new owned `Vec<Value>` data, sharing via `Arc` alone doesn't help: we'd need `Arc` at evaluation time, but then suffix evaluation still produces new owned results that face the same problem.

**Recommendation:** Memory-budget caching - accept the clone but skip caching when estimated size exceeds a threshold:
```rust
const CACHE_SIZE_LIMIT: usize = 50_000; // ~100MB assuming 2KB avg per Value

if let Ok(ref values) = result {
    if values.len() <= CACHE_SIZE_LIMIT {
        cache = Some(PipeCache { ... });
    } else {
        cache = None; // Too large to cache safely
    }
}
```
This preserves caching for the common case (small-to-medium results where it's most useful) while preventing OOM on pathological cases. Document that pipe caching has memory overhead proportional to intermediate result size.

---

### 1.4 Potential Integer Underflow in Navigation

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | `text_buffer.rs:157-158` |

```rust
if (prev_char == '|' || (prev_char == ' ' && i > 1 && chars[i - 2] == '|'))
```

**Issue:** The `i > 1` check prevents underflow, but the logic is fragile and easy to break during maintenance. The compound condition is hard to reason about.

**Recommendation:** Refactor to use `chars.get(i.wrapping_sub(2))` with explicit `Option` handling, or extract to a helper function with clearer semantics.

---

### 1.5 Incomplete String Escape Handling in Breakpoints āœ… PARTIALLY RESOLVED

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬜ Medium | `filter/mod.rs`, `text_buffer.rs` |

~~**Issue:** The string detection logic doesn't handle:~~
~~- Escaped backslashes before quotes (`"\\"` followed by `"`)~~
~~- Unicode escapes that might contain quote-like sequences~~

~~**Impact:** Error position highlighting could be incorrect for filters with complex string literals.~~

**Resolution (Error Highlighting):** Implemented proper AST span tracking using `nom_locate`:
- Parser now tracks source positions for each AST node via `Span { start, end }`
- Eval errors set position from `filter.span.start` instead of heuristic scanning
- Worker adjusts error positions when using pipe cache (suffix evaluation)
- Removed unused `find_eval_error_position` function

**Remaining Issue (Navigation):** The `text_buffer.rs:compute_navigation_positions()` function still uses the same heuristic character scanning for Alt+Left/Right word jumping. While less critical (navigation just needs "good enough" stopping points vs. precise error positions), it could be improved to use AST spans for more accurate navigation boundaries.

**Future Enhancement:** Consider using AST spans for navigation positions:
- Would require parsing on text changes (or caching AST)
- Challenge: incomplete/invalid filters won't parse, breaking navigation mid-typing
- Possible hybrid: use AST when available, fall back to heuristics for invalid input

---

### 1.6 Inconsistent `has()` Behavior for Arrays

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | `filter/eval.rs:258-268` |

```rust
Value::Array(arr) => {
    if let Ok(idx) = key.parse::<usize>() {
        Ok(vec![Value::Static(StaticNode::Bool(idx < arr.len()))])
    } else {
        Ok(vec![Value::Static(StaticNode::Bool(false))])
    }
}
```

**Issue:** `has("-1")` returns `false` because negative numbers fail `parse::<usize>()`. In jq, `has(-1)` on an array checks if the last element exists (negative indexing).

**Recommendation:** Parse as `i64` first, then handle negative indices like the `Index` filter does.

---

## 2. Code Structure & Maintainability Issues

### 2.1 God Struct: `App`

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬛ High | `app/mod.rs:40-68` |

The `App` struct has 16 fields spanning multiple concerns:

```rust
pub struct App {
    // Input/Loading (4 fields)
    input_values: Option<Arc<Vec<Value>>>,
    loading_state: LoadingState,
    loader: Option<Loader>,

    // Filter editing (4 fields)
    filter_buffer: TextBuffer,
    filter_error: Option<FilterError>,
    pending_filter_update: Option<Instant>,
    partial_eval_mode: bool,

    // Output (2 fields)
    output_values: Vec<Value>,
    total_lines: usize,

    // Mode flags (2 fields)
    editing_filter: bool,
    eval_paused: bool,

    // Worker (1 field)
    worker: Worker,

    // Grouped state (4 fields)
    help: HelpState,
    eval: EvalTracker,
    viewport: Viewport,
    display: DisplayOptions,
}
```

**Issue:** Single struct orchestrating loading, editing, evaluation, display, and help. Changes to any subsystem risk affecting others.

**Recommendation:** Consider a more modular architecture:
- `InputManager` - handles loading and input values
- `FilterEditor` - manages filter text, validation, partial eval
- `EvalCoordinator` - worker communication, caching, debouncing
- `App` becomes a thin coordinator

---

### 2.2 Duplicated `type_name` Functions āœ… RESOLVED

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | Multiple files |

~~Three separate `type_name` implementations existed:~~
- ~~`filter/eval.rs:225-234`~~
- ~~`filter/builtins/mod.rs:14-23`~~
- ~~`error.rs:58-68` (inline in `Display` impl)~~

**Resolution:** Centralized to `src/utils.rs`:
- Created `utils.rs` with single `pub fn type_name()`
- All usages now import directly from `crate::utils::type_name`

---

### 2.3 Large Monolithic `eval.rs` File

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬛ High | `filter/eval.rs` (1724 lines) |

The evaluation logic file contains:
- Main `eval` function (220+ line match statement)
- All function implementations (`eval_map`, `eval_select`, etc.)
- Comparison logic
- 900+ lines of tests

**Recommendation:** Split into focused modules:
```
filter/
  eval/
    mod.rs          - Main eval dispatch
    accessor.rs     - Field, Index, Slice, Iterate
    functions.rs    - Map, Select, SortBy, etc.
    comparisons.rs  - Compare, BoolOp
    construction.rs - Array, Object construction
```

---

### 2.4 Inconsistent Error Handling Patterns āœ… RESOLVED

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬜ Medium | Multiple files |

~~Different error patterns across the codebase:~~
- ~~`loader.rs` uses `Result<_, String>`~~
- ~~`filter/` uses `Result<_, FilterError>` with proper typed errors~~
- `main.rs` uses `anyhow::Result` (appropriate for binary entry point)

**Resolution:** Standardized on `thiserror`-derived errors:
- Converted `FilterError`, `ParseError`, `EvalError` to use `#[derive(thiserror::Error)]`
- Added `LoadError` enum with `Io`, `ParseLine`, `EmptyInput`, `NoValidJson` variants
- Updated `loader.rs` to use `LoadError` instead of `String`
- Updated `LoadingState::Failed` to hold `LoadError`

---

## 3. Testability Issues

### 3.1 Untestable TUI Code

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟠 High | ⬜ Medium | `main.rs:163-203` |

```rust
fn run_tui_loop(app: &mut App) -> Result<()> {
    // Direct terminal I/O - impossible to unit test
    let mut terminal = Terminal::new(CrosstermBackend::new(stdout))?;
    loop {
        terminal.draw(|frame| ui::render(frame, app))?;
        if event::poll(POLL_TIMEOUT)? {
            match event::read()? {
                Event::Key(key) => match app.handle_key(key) { ... }
            }
        }
        app.tick();
    }
}
```

**Issue:** Core application loop directly interacts with terminal I/O, making it impossible to unit test state transitions.

**Recommendation:** Extract a pure `update` function:
```rust
enum AppEvent {
    Key(KeyEvent),
    Tick,
    Resize(u16, u16),
}

fn update(app: &mut App, event: AppEvent) -> ControlFlow<()> {
    // Pure logic, no I/O - fully testable
}
```

---

### 3.2 No Tests for `App` Logic

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟠 High | ⬜ Medium | `app/mod.rs` |

The core `App` struct has no unit tests. Untested behaviors include:
- Debounce timing logic (`pending_filter_update`)
- Eval cancellation (`cancel_eval`)
- Mode transitions (navigation ↔ filter editing)
- Partial eval mode toggling
- Slurp mode interactions

**Recommendation:** Add focused unit tests:
```rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_debounce_schedules_update() {
        let mut app = App::new(vec![json!({"a": 1})]);
        app.filter_buffer.insert('x');
        app.mark_filter_dirty();
        assert!(app.pending_filter_update.is_some());
    }

    #[test]
    fn test_cancel_clears_pending() {
        // ...
    }
}
```

---

### 3.3 UI Rendering Not Tested

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬜ Medium | `ui.rs` |

Only basic virtualized rendering has tests. Untested:
- Main `render` function
- `render_filter_input` with error highlighting
- `render_help_popup` scrolling
- Status bar content

**Recommendation:** Use `ratatui`'s `TestBackend`:
```rust
#[test]
fn test_filter_error_rendering() {
    let backend = TestBackend::new(80, 24);
    let mut terminal = Terminal::new(backend).unwrap();
    let mut app = App::new(vec![json!(1)]);
    app.filter_error = Some(FilterError::Parse(...));

    terminal.draw(|f| render(f, &mut app)).unwrap();

    let buffer = terminal.backend().buffer();
    assert!(buffer_contains(buffer, "Error:"));
}
```

---

### 3.4 Worker Thread Not Unit Tested

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ⬜ Medium | `worker.rs` |

The worker's pipe caching logic (`PipeCache::get_suffix`) is tested only indirectly through integration tests.

**Recommendation:** Extract and test the caching logic in isolation:
```rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_pipe_cache_suffix_detection() {
        let cache = PipeCache {
            filter_text: ".[] | .name".to_string(),
            values: Arc::new(vec![]),
            slurp_mode: false,
        };

        assert_eq!(cache.get_suffix(".[] | .name | length", false), Some("length"));
        assert_eq!(cache.get_suffix(".[] | .other", false), None);
    }
}
```

---

## 4. Performance Concerns

### 4.1 Excessive Cloning in Slurp Mode

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟠 High | ⬜ Medium | `worker.rs:142-148` |

```rust
fn prepare_inputs(req: &EvalRequest) -> Arc<Vec<Value>> {
    if req.slurp_mode {
        Arc::new(vec![Value::Array(Box::new((*req.inputs).clone()))])  // Full deep clone
    } else {
        Arc::clone(&req.inputs)
    }
}
```

**Issue:** When slurp mode is enabled, the entire input is deep-cloned. For large files, this doubles memory usage during evaluation.

**Impact:** A 500MB JSON file in memory becomes 1GB+ during slurp mode evaluation.

**Recommendation:** Consider lazy wrapping or COW (copy-on-write) semantics. At minimum, document the memory implications of slurp mode.

---

### 4.2 O(n²) Algorithm in `filter_breakpoints`

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | `filter/mod.rs:108` |

```rust
if !breakpoints.contains(&i) {  // O(n) search on every iteration
    breakpoints.push(i);
}
```

**Issue:** Linear search on every character for long filters. A filter with 1000 characters could result in ~500K comparisons.

**Recommendation:** Use a `HashSet` for deduplication:
```rust
let mut breakpoints: HashSet<usize> = HashSet::new();
breakpoints.insert(0);
// ... collect breakpoints ...
let mut result: Vec<_> = breakpoints.into_iter().collect();
result.sort();
```

---

### 4.3 Repeated Navigation Position Computation

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ⬜ Medium | `text_buffer.rs:198-234` |

```rust
pub fn jump_word_back(&mut self) {
    let positions = self.navigation_positions();  // Recomputed every jump
    // ...
}
```

**Issue:** `navigation_positions()` rescans the entire text on every cursor move, even when text hasn't changed.

**Recommendation:** Cache navigation positions and invalidate on text modification:
```rust
struct TextBuffer {
    text: String,
    cursor: usize,
    nav_cache: Option<Vec<usize>>,  // Invalidated on insert/delete
}
```

---

## 5. Missing Features / Code Gaps

### 5.1 No Graceful Worker Thread Shutdown

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟔 Medium | ā—½ Low | `worker.rs` |

```rust
pub struct Worker {
    request_tx: Sender<EvalRequest>,
    result_rx: Receiver<EvalResult>,
    _handle: JoinHandle<()>,  // Never joined
}
```

**Issue:** The worker thread is orphaned on app exit. While not problematic for short-lived CLI usage, it's poor hygiene and could cause issues if jarq were used as a library.

**Recommendation:** Implement graceful shutdown:
```rust
impl Drop for Worker {
    fn drop(&mut self) {
        // Dropping the sender signals the worker to exit
        // (recv() will return Err when all senders are dropped)
    }
}
```

Or use an explicit shutdown channel for immediate termination.

---

### 5.2 No Loader Thread Cancellation

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ⬜ Medium | `loader.rs` |

If a user quits while loading a large file, the loader thread continues reading until completion.

**Recommendation:** Add a cancellation mechanism using an `AtomicBool` or channel.

---

### 5.3 No Configuration File Support

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ⬛ High | N/A |

Users cannot customize:
- Default display options (wrap, raw, compact)
- Keybindings
- Color scheme
- Debounce duration

**Recommendation:** Consider adding `~/.config/jarq/config.toml` support in a future version.

---

## 6. Code Style & Cleanup

### 6.1 Dead Code

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | `main.rs:157-161` |

```rust
#[allow(dead_code)]
fn run_interactive(values: Vec<simd_json::OwnedValue>) -> Result<()> {
    let mut app = App::new(values);
    run_tui_loop(&mut app)
}
```

**Issue:** Function is explicitly marked dead but retained. Per CLAUDE.md guidelines, unused code should be removed.

**Recommendation:** Remove the function entirely.

---

### 6.2 Magic Numbers

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | Multiple files |

Undocumented constants:
- `DEBOUNCE_DURATION: 250ms` (documented but not configurable)
- `PARALLEL_THRESHOLD: 10_000` (appears in `filter/mod.rs:28` and `filter/eval.rs:11`)
- `POLL_TIMEOUT: 50ms`

**Recommendation:** Centralize constants in a `config.rs` module with documentation explaining the rationale for each value.

---

### 6.3 Inconsistent Use of `#[allow(...)]`

| Severity | Complexity | Location |
|----------|------------|----------|
| 🟢 Low | ā—½ Low | Multiple files |

Some files use `#[allow(clippy::too_many_arguments)]` without attempting to refactor. Example: `ui.rs:597`.

**Recommendation:** Either refactor the function signatures (e.g., use a config struct) or add comments explaining why the many arguments are necessary.

---

## 7. Positive Observations

### 7.1 Excellent Parser Test Coverage
The `filter/parser/mod.rs` contains 100+ test cases covering:
- All syntax variations
- Edge cases (empty strings, unicode, escapes)
- Error conditions
- Operator precedence

### 7.2 Clean AST Design
The `Filter` enum is well-structured and exhaustive:
```rust
pub enum Filter {
    Identity,
    Field(String),
    Index(i64),
    Slice(Option<i64>, Option<i64>),
    // ... all variants clearly named
}
```

### 7.3 Efficient Virtualized Rendering
The line counting and viewport rendering in `ui.rs` correctly handles large files by only rendering visible lines. The separation of `count_total_lines` and `render_viewport` is elegant.

### 7.4 Proper Panic Hook Handling
`main.rs:170-175` correctly installs a panic hook to restore terminal state:
```rust
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
    let _ = disable_raw_mode();
    let _ = io::stdout().execute(LeaveAlternateScreen);
    original_hook(panic_info);
}));
```

### 7.5 Unicode-Aware Text Buffer
The `TextBuffer` correctly handles multi-byte characters by tracking cursor position in characters, not bytes.

### 7.6 Comprehensive Builtin Implementation
All common jq builtins are implemented with correct semantics:
- `length`, `keys`, `values`, `type`
- `sort`, `reverse`, `unique`, `flatten`
- `first`, `last`, `min`, `max`, `add`
- `to_entries`, `from_entries`, `with_entries`

---

## Summary Table

| # | Issue | Severity | Complexity | Category |
|---|-------|----------|------------|----------|
| 1.1 | Worker race condition | 🟔 Medium | ā—½ Low | Bug |
| 1.2 | āœ… ~~Silent clipboard failures~~ | 🟔 Medium | ā—½ Low | Bug |
| 1.3 | Unconstrained cache growth | 🟠 High | ⬜ Medium | Bug |
| 1.4 | Integer underflow risk | 🟢 Low | ā—½ Low | Bug |
| 1.5 | āœ… ~~Incomplete string escape handling~~ (nav pending) | 🟔 Medium | ⬜ Medium | Bug |
| 1.6 | Inconsistent `has()` for arrays | 🟢 Low | ā—½ Low | Bug |
| 2.1 | God struct `App` | 🟔 Medium | ⬛ High | Structure |
| 2.2 | āœ… ~~Duplicated `type_name`~~ | 🟢 Low | ā—½ Low | Structure |
| 2.3 | Large monolithic `eval.rs` | 🟔 Medium | ⬛ High | Structure |
| 2.4 | āœ… ~~Inconsistent error handling~~ | 🟔 Medium | ⬜ Medium | Structure |
| 3.1 | Untestable TUI code | 🟠 High | ⬜ Medium | Testability |
| 3.2 | No `App` unit tests | 🟠 High | ⬜ Medium | Testability |
| 3.3 | UI rendering not tested | 🟔 Medium | ⬜ Medium | Testability |
| 3.4 | Worker not unit tested | 🟔 Medium | ⬜ Medium | Testability |
| 4.1 | Excessive cloning in slurp | 🟠 High | ⬜ Medium | Performance |
| 4.2 | O(n²) in filter_breakpoints | 🟢 Low | ā—½ Low | Performance |
| 4.3 | Repeated nav computation | 🟢 Low | ⬜ Medium | Performance |
| 5.1 | No worker shutdown | 🟔 Medium | ā—½ Low | Missing |
| 5.2 | No loader cancellation | 🟢 Low | ⬜ Medium | Missing |
| 6.1 | Dead code | 🟢 Low | ā—½ Low | Cleanup |
| 6.2 | Magic numbers | 🟢 Low | ā—½ Low | Cleanup |

---

## Recommended Priority Order

### Immediate (High Impact, Low Effort)
1. Remove dead code (`main.rs:157-161`)
2. āœ… ~~Add clipboard failure feedback~~
3. āœ… ~~Centralize `type_name` function~~
4. Fix O(n²) in `filter_breakpoints`

### Short-Term (High Impact)
1. Add `App` unit tests
2. Add cache size limits to worker
3. Implement worker graceful shutdown
4. Fix slurp mode memory duplication

### Medium-Term (Architectural)
1. Extract testable TUI update function
2. Split `eval.rs` into modules
3. āœ… ~~Standardize error handling~~
4. Add UI rendering tests

### Long-Term (Nice to Have)
1. Refactor `App` struct
2. Add configuration file support
3. Cache navigation positions
4. Add loader cancellation

---

*End of Code Review*