gamr 0.1.1__tar.gz

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 (74) hide show
  1. gamr-0.1.1/.bandit +3 -0
  2. gamr-0.1.1/.gitignore +10 -0
  3. gamr-0.1.1/.pre-commit-config.yaml +31 -0
  4. gamr-0.1.1/.python-version +1 -0
  5. gamr-0.1.1/CHANGELOG.md +18 -0
  6. gamr-0.1.1/LICENSE +21 -0
  7. gamr-0.1.1/Makefile +145 -0
  8. gamr-0.1.1/PKG-INFO +12 -0
  9. gamr-0.1.1/README.md +99 -0
  10. gamr-0.1.1/docs/UI_DESIGN.md +151 -0
  11. gamr-0.1.1/docs/adrs/001-use-textual-framework.md +28 -0
  12. gamr-0.1.1/docs/adrs/002-pure-python-git-dulwich.md +33 -0
  13. gamr-0.1.1/docs/adrs/003-watchdog-polling-fallback.md +28 -0
  14. gamr-0.1.1/docs/adrs/004-datatable-tree-semantics.md +32 -0
  15. gamr-0.1.1/docs/adrs/005-thread-workers-for-git-ops.md +37 -0
  16. gamr-0.1.1/docs/adrs/006-git-provider-abc.md +24 -0
  17. gamr-0.1.1/docs/adrs/007-rapidfuzz-fuzzy-matching.md +27 -0
  18. gamr-0.1.1/docs/adrs/008-three-view-modes.md +31 -0
  19. gamr-0.1.1/docs/adrs/009-deferred-expensive-computations.md +28 -0
  20. gamr-0.1.1/docs/adrs/010-types-in-models.md +28 -0
  21. gamr-0.1.1/docs/adrs/011-gradient-colors.md +31 -0
  22. gamr-0.1.1/docs/adrs/012-lsd-icons-config.md +29 -0
  23. gamr-0.1.1/docs/adrs/013-spaced-paths.md +21 -0
  24. gamr-0.1.1/docs/adrs/014-preserve-state-on-watch.md +28 -0
  25. gamr-0.1.1/docs/adrs/015-persistent-state.md +36 -0
  26. gamr-0.1.1/docs/adrs/016-folder-navigation.md +32 -0
  27. gamr-0.1.1/docs/adrs/017-three-diff-modes.md +33 -0
  28. gamr-0.1.1/docs/adrs/018-diff-overview-bar.md +37 -0
  29. gamr-0.1.1/docs/adrs/019-incremental-table-updates.md +53 -0
  30. gamr-0.1.1/docs/adrs/020-widgets-presentational.md +39 -0
  31. gamr-0.1.1/docs/adrs/021-gutter-diff-mode.md +25 -0
  32. gamr-0.1.1/docs/adrs/022-preview-stability.md +35 -0
  33. gamr-0.1.1/docs/adrs/023-copy-file-reference.md +34 -0
  34. gamr-0.1.1/docs/adrs/README.md +27 -0
  35. gamr-0.1.1/docs/design-twisty-click-target.md +79 -0
  36. gamr-0.1.1/pyproject.toml +50 -0
  37. gamr-0.1.1/scripts/chooser.py +96 -0
  38. gamr-0.1.1/scripts/lint.sh +14 -0
  39. gamr-0.1.1/scripts/release.sh +83 -0
  40. gamr-0.1.1/src/gamr/__init__.py +3 -0
  41. gamr-0.1.1/src/gamr/app.py +498 -0
  42. gamr-0.1.1/src/gamr/commands.py +52 -0
  43. gamr-0.1.1/src/gamr/config.py +33 -0
  44. gamr-0.1.1/src/gamr/gamr.tcss +13 -0
  45. gamr-0.1.1/src/gamr/models.py +59 -0
  46. gamr-0.1.1/src/gamr/py.typed +0 -0
  47. gamr-0.1.1/src/gamr/services/__init__.py +3 -0
  48. gamr-0.1.1/src/gamr/services/diff_parser.py +76 -0
  49. gamr-0.1.1/src/gamr/services/file_index.py +74 -0
  50. gamr-0.1.1/src/gamr/services/file_scanner.py +209 -0
  51. gamr-0.1.1/src/gamr/services/filter.py +92 -0
  52. gamr-0.1.1/src/gamr/services/git_provider.py +220 -0
  53. gamr-0.1.1/src/gamr/services/icons.py +72 -0
  54. gamr-0.1.1/src/gamr/state.py +213 -0
  55. gamr-0.1.1/src/gamr/widgets/__init__.py +3 -0
  56. gamr-0.1.1/src/gamr/widgets/file_tree_table.py +657 -0
  57. gamr-0.1.1/src/gamr/widgets/filter_bar.py +104 -0
  58. gamr-0.1.1/src/gamr/widgets/preview_pane.py +678 -0
  59. gamr-0.1.1/src/gamr/widgets/split.py +100 -0
  60. gamr-0.1.1/src/gamr/widgets/tree_data.py +124 -0
  61. gamr-0.1.1/tests/__init__.py +0 -0
  62. gamr-0.1.1/tests/test_app.py +136 -0
  63. gamr-0.1.1/tests/test_diff_overview.py +147 -0
  64. gamr-0.1.1/tests/test_file_index.py +83 -0
  65. gamr-0.1.1/tests/test_file_scanner.py +95 -0
  66. gamr-0.1.1/tests/test_file_tree_table.py +104 -0
  67. gamr-0.1.1/tests/test_filter_bar.py +62 -0
  68. gamr-0.1.1/tests/test_git_provider.py +122 -0
  69. gamr-0.1.1/tests/test_gutter_markers.py +137 -0
  70. gamr-0.1.1/tests/test_icons.py +77 -0
  71. gamr-0.1.1/tests/test_preview_pane.py +71 -0
  72. gamr-0.1.1/tests/test_state.py +48 -0
  73. gamr-0.1.1/tests/test_tree_data.py +106 -0
  74. gamr-0.1.1/uv.lock +1821 -0
gamr-0.1.1/.bandit ADDED
@@ -0,0 +1,3 @@
1
+ skips:
2
+ - B101
3
+ - B110
gamr-0.1.1/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .coverage
9
+ htmlcov/
10
+ .gamrstate
@@ -0,0 +1,31 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-added-large-files
10
+ - id: check-merge-conflict
11
+ - id: detect-private-key
12
+
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.15.1
15
+ hooks:
16
+ - id: ruff
17
+ args: [--fix]
18
+ - id: ruff-format
19
+
20
+ - repo: https://github.com/PyCQA/bandit
21
+ rev: 1.8.3
22
+ hooks:
23
+ - id: bandit
24
+ args: [-r, -c, .bandit]
25
+ files: ^src/
26
+
27
+ - repo: https://github.com/shellcheck-py/shellcheck-py
28
+ rev: v0.10.0.1
29
+ hooks:
30
+ - id: shellcheck
31
+ args: [-x, -e, SC2016]
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ 0.1.1 (2025-06-10)
4
+
5
+ Initial public release.
6
+
7
+ - Live file watching with automatic tree updates (watchdog + polling fallback)
8
+ - Pure Python git integration via Dulwich — no git binary required
9
+ - File status indicators (Modified, Added, Deleted, Untracked, Staged)
10
+ - Three diff modes: full file, gutter markers, unified diff
11
+ - Three view modes: tree, flat filenames, flat relative paths
12
+ - Fuzzy filename search with RapidFuzz
13
+ - Git status filtering toggles
14
+ - Syntax-highlighted preview with Monokai theme
15
+ - Column sorting, resizable split pane, state persistence
16
+ - Background blame loading (author + last modified)
17
+ - .gitignore support
18
+ - Graceful degradation on non-git directories
gamr-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edouard Poor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gamr-0.1.1/Makefile ADDED
@@ -0,0 +1,145 @@
1
+ # Makefile for Gamr — Git-aware Agent Monitor & Review
2
+ #
3
+ # Usage:
4
+ # make Show all available targets
5
+ # make <target> Run a specific target
6
+
7
+ .PHONY: help
8
+ help: ## Show this help message
9
+ @echo "Available targets:"
10
+ @echo ""
11
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
12
+ awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
13
+ @echo ""
14
+ @echo "Common workflows:"
15
+ @echo " make install test # Set up and run tests"
16
+ @echo " make lint format # Check and fix code"
17
+ @echo " make security test # Full validation"
18
+
19
+ # ============================================================================
20
+ # Setup and Installation
21
+ # ============================================================================
22
+
23
+ .PHONY: install
24
+ install: ## Install dependencies with uv
25
+ uv sync
26
+
27
+ .PHONY: install-hooks
28
+ install-hooks: ## Install pre-commit git hooks
29
+ uv run pre-commit install
30
+
31
+ # ============================================================================
32
+ # Code Quality
33
+ # ============================================================================
34
+
35
+ .PHONY: lint
36
+ lint: ## Run linting checks (ruff check + format check)
37
+ ./scripts/lint.sh
38
+
39
+ .PHONY: format
40
+ format: ## Auto-fix formatting and linting issues
41
+ ./scripts/lint.sh --fix
42
+
43
+ .PHONY: pre-commit
44
+ pre-commit: ## Run all pre-commit hooks manually
45
+ uv run pre-commit run --all-files
46
+
47
+ .PHONY: security
48
+ security: ## Run security scans (bandit + pip-audit)
49
+ uv run bandit -r src/ -c .bandit
50
+ uv run pip-audit
51
+
52
+ # ============================================================================
53
+ # Testing
54
+ # ============================================================================
55
+
56
+ .PHONY: test
57
+ test: ## Run all tests
58
+ uv run pytest tests/ -q
59
+
60
+ .PHONY: test-verbose
61
+ test-verbose: ## Run tests with verbose output
62
+ uv run pytest tests/ -v
63
+
64
+ .PHONY: coverage
65
+ coverage: ## Run tests with coverage report
66
+ uv run pytest tests/ -q --cov=gamr --cov-report=term-missing
67
+
68
+ # ============================================================================
69
+ # Dependencies
70
+ # ============================================================================
71
+
72
+ .PHONY: update-deps
73
+ update-deps: ## Update dependencies (7-day lag for supply chain protection)
74
+ @echo "╔══════════════════════════════════════════════════════════════╗"
75
+ @echo "║ Updating dependencies with 7-day publication lag ║"
76
+ @echo "║ (packages published less than 7 days ago are excluded) ║"
77
+ @echo "╚══════════════════════════════════════════════════════════════╝"
78
+ @echo ""
79
+ uv lock --upgrade --exclude-newer "$$(date -v-7d +%Y-%m-%dT00:00:00Z)"
80
+ uv sync
81
+ @echo ""
82
+ @echo "Running security audit..."
83
+ uv run pip-audit
84
+ @echo ""
85
+ @echo "✅ Dependencies updated. Run 'make test' to verify."
86
+
87
+ # ============================================================================
88
+ # Build
89
+ # ============================================================================
90
+
91
+ .PHONY: build
92
+ build: ## Build wheel and sdist
93
+ uv build
94
+
95
+ # ============================================================================
96
+ # Run
97
+ # ============================================================================
98
+
99
+ .PHONY: run
100
+ run: ## Run the app on the current directory
101
+ uv run gamr .
102
+
103
+ # ============================================================================
104
+ # Release
105
+ # ============================================================================
106
+
107
+ .PHONY: release
108
+ release: ## Release (interactive — asks for bump type)
109
+ @CHOICE=$$(uv run scripts/chooser.py --title "Release type?" patch minor major 3>&1 1>&2) || exit 1; \
110
+ ./scripts/release.sh $$CHOICE
111
+
112
+ .PHONY: release-patch
113
+ release-patch: ## Release a patch version (bug fixes)
114
+ ./scripts/release.sh patch
115
+
116
+ .PHONY: release-minor
117
+ release-minor: ## Release a minor version (new features)
118
+ ./scripts/release.sh minor
119
+
120
+ .PHONY: release-major
121
+ release-major: ## Release a major version (breaking changes)
122
+ ./scripts/release.sh major
123
+
124
+ # ============================================================================
125
+ # Cleanup
126
+ # ============================================================================
127
+
128
+ .PHONY: clean
129
+ clean: ## Remove build artifacts and caches
130
+ rm -rf dist/ .pytest_cache .ruff_cache .coverage
131
+
132
+ # ============================================================================
133
+ # Validation (Full Quality Check)
134
+ # ============================================================================
135
+
136
+ .PHONY: validate
137
+ validate: lint security test ## Run lint, security, and tests
138
+ @echo ""
139
+ @echo "✅ Validation complete!"
140
+
141
+ # ============================================================================
142
+ # Default Target
143
+ # ============================================================================
144
+
145
+ .DEFAULT_GOAL := help
gamr-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: gamr
3
+ Version: 0.1.1
4
+ Summary: Git-aware Agent Monitor & Review — a TUI for reviewing AI agent work
5
+ Author: Edouard Poor
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: dulwich>=0.22.7
10
+ Requires-Dist: rapidfuzz>=3.14.0
11
+ Requires-Dist: textual>=1.0.0
12
+ Requires-Dist: watchdog>=6.0.0
gamr-0.1.1/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Gamr
2
+
3
+ **G**it-aware **A**gent **M**onitor & **R**eview — a TUI for reviewing AI agent work with live watching, fuzzy search, and diff preview.
4
+
5
+ ## Features
6
+
7
+ - **Live file watching** — tree updates automatically when files change (watchdog + polling fallback)
8
+ - **Git integration** — pure Python via Dulwich, no git binary required
9
+ - File status indicators (Modified, Added, Deleted, Untracked, Staged)
10
+ - Three diff modes: full file diff, gutter markers, unified diff (`d`/`D` to cycle)
11
+ - Blame data (last author, last modified) loaded in background
12
+ - Respects `.gitignore` rules
13
+ - **Three view modes** — tree, flat filenames, flat relative paths (`v` to cycle)
14
+ - **Column sorting** — click any column header to sort asc/desc/none
15
+ - **Fuzzy search** — fzf-like filename filtering with RapidFuzz
16
+ - **Git status filtering** — toggle buttons to show only modified, added, etc.
17
+ - **Syntax-highlighted preview** — Monokai theme, scrollable, line numbers
18
+ - **Copy file references** — double-click or drag lines to copy `path:line` to clipboard
19
+ - **Full file diff** — syntax highlighting with inline +/- markers and colored backgrounds
20
+ - **Diff overview bar** — 1-column change map (line or braille style) in full diff mode
21
+ - **Gradient colors** — size and modification time columns colored by relative magnitude
22
+ - **File icons** — loads from `~/.config/lsd/icons.yaml` if present
23
+ - **Resizable split pane** — drag the divider between tree and preview
24
+ - **Desktop-style navigation** — ←/→ to collapse/expand folders, ← on file collapses parent
25
+ - **State persistence** — view mode, columns, collapsed dirs, selection, filters saved between sessions
26
+ - **Graceful degradation** — works on non-git directories (hides git UI)
27
+
28
+ ## Install
29
+
30
+ Requires Python 3.11+.
31
+
32
+ ```sh
33
+ uv sync
34
+ uv run gamr [path]
35
+ ```
36
+
37
+ ## Keybindings
38
+
39
+ | Key | Action |
40
+ | -------- | -------------------------------------------------- |
41
+ | `↑`/`↓` | Navigate files |
42
+ | `→` | Expand directory |
43
+ | `←` | Collapse directory (or parent if on file) |
44
+ | `space` | Toggle expand/collapse |
45
+ | `v` | Cycle view mode (tree → flat name → flat path) |
46
+ | `d` | Cycle diff mode (full → gutter → unified) |
47
+ | `D` | Cycle diff mode reverse |
48
+ | `m` | Toggle Modified filter |
49
+ | `f` | Toggle follow mode (auto-select last changed file) |
50
+ | `ctrl+f` | Focus search input |
51
+ | `b` | Toggle blame columns (last author, git time) |
52
+ | `1`–`6` | Toggle individual columns |
53
+ | `tab` | Switch focus between tree and preview |
54
+ | `ctrl+p` | Command palette (spaced paths, gradient toggle) |
55
+ | `q` | Quit (saves state) |
56
+
57
+ Column headers are clickable to sort (ascending → descending → none).
58
+
59
+ ## Architecture
60
+
61
+ ```
62
+ src/gamr/
63
+ ├── app.py # Textual app, keybindings, orchestration
64
+ ├── commands.py # Command palette provider
65
+ ├── models.py # FileEntry, GitStatus, DiffMode, etc.
66
+ ├── state.py # Persistent state management
67
+ ├── gamr.tcss # Stylesheet
68
+ ├── services/
69
+ │ ├── diff_parser.py # Unified diff parsing into DiffData
70
+ │ ├── file_scanner.py # Watchdog + polling fallback + git state watcher
71
+ │ ├── file_index.py # Merges scanner + git into FileEntry list
72
+ │ ├── git_provider.py # GitProvider ABC + Dulwich implementation
73
+ │ ├── filter.py # Fuzzy and status filtering
74
+ │ └── icons.py # lsd icons.yaml loader
75
+ └── widgets/
76
+ ├── file_tree_table.py # DataTable with tree semantics
77
+ ├── tree_data.py # Tree building and sorting logic
78
+ ├── filter_bar.py # Git status toggles + search input
79
+ ├── preview_pane.py # Syntax highlighting + diff view
80
+ └── split.py # Resizable horizontal split
81
+ ```
82
+
83
+ ## Dependencies
84
+
85
+ - [Textual](https://textual.textualize.io/) — TUI framework
86
+ - [Dulwich](https://dulwich.io/) — pure Python git (no compiled deps)
87
+ - [watchdog](https://github.com/gorakhargosh/watchdog) — filesystem events
88
+ - [RapidFuzz](https://github.com/rapidfuzz/RapidFuzz) — fuzzy string matching
89
+
90
+ ## Tests
91
+
92
+ ```sh
93
+ uv run pytest
94
+ ```
95
+
96
+ ## Documentation
97
+
98
+ - [UI Design Rules](docs/UI_DESIGN.md) — all interaction behaviors and edge cases
99
+ - [Architecture Decision Records](docs/adrs/README.md) — 23 ADRs covering all design choices
@@ -0,0 +1,151 @@
1
+ # UI Design Rules
2
+
3
+ ## Navigation
4
+
5
+ - **↑/↓** — Move cursor between rows in the file tree
6
+ - **→** on a collapsed directory — Expand it
7
+ - **→** on a file — No action
8
+ - **←** on an expanded directory — Collapse it; cursor stays on the directory; preview clears or shows nothing (dirs have no content)
9
+ - **←** on a file or collapsed directory — Collapse the parent folder, cursor moves to parent; preview clears (now on a directory)
10
+ - **←** on a file at root level (no parent to collapse) — No action; preview stays on current file
11
+ - **←** on a collapsed directory at root level — No action; preview unchanged
12
+
13
+ ### Edge cases for ← (collapse/navigate-to-parent)
14
+
15
+ | Current selection | Action | Cursor after | Preview after |
16
+ | ------------------------------------------ | ----------------------------- | ----------------------- | --------------------------------- |
17
+ | Expanded directory | Collapse it | Stays on that directory | Clears (directory has no preview) |
18
+ | File inside `src/` | Collapse `src/` | Moves to `src/` row | Clears (now on directory) |
19
+ | Collapsed directory inside `src/` | Collapse `src/` | Moves to `src/` row | Clears |
20
+ | File at root level (no parent dir in tree) | Nothing | Unchanged | Unchanged |
21
+ | Root-level directory (already collapsed) | Nothing | Unchanged | Unchanged |
22
+ | File while in flat view mode | No action (no tree hierarchy) | Unchanged | Unchanged |
23
+
24
+ **Preview pane rule:** When cursor lands on a directory (after ← collapses parent), the preview pane shows nothing meaningful — directories don't have file content. The preview should retain its last content but dim or show "Select a file to preview" if we want to be explicit. Current behavior: preview simply doesn't update (the `NodeHighlighted` message carries `entry=None` for directory nodes, which is filtered out before rendering).
25
+ - **Space** on a directory — Toggle expand/collapse
26
+ - **Tab** — Switch focus between the file tree and preview pane
27
+ - After expand/collapse, the cursor remains on the same directory row
28
+
29
+ ## View Modes
30
+
31
+ - **v** — Cycle through: tree → flat name → flat path
32
+ - **Tree** — Hierarchical with expand/collapse, dirs with ▶/▼ twisties
33
+ - **Flat name** — Leaf filenames only, no paths, no hierarchy
34
+ - **Flat path** — Relative paths (e.g., `src / worker / foo.py`)
35
+ - Spaced paths (`/` → ` / `) is the default; toggled via command palette
36
+ - When sorting is activated, tree mode auto-switches to flat path (sorting requires a flat list)
37
+ - When sort is removed, the view restores to tree mode if that's where it was
38
+
39
+ ## Column Sorting
40
+
41
+ - Click any column header — Cycle: ascending (▲) → descending (▼) → no sort
42
+ - Sorting only applies in flat view modes
43
+ - Sort indicators shown as suffix on the column header text
44
+
45
+ ## Filtering
46
+
47
+ - **m** — Toggle the Modified git status filter
48
+ - **ctrl+f** — Focus the search input
49
+ - Filter buttons (M, A, D, ?, S) toggle git status filters
50
+ - Search input provides fzf-like fuzzy filename matching (RapidFuzz)
51
+ - Filters compose: status filter applied first, then fuzzy search
52
+ - Filter state (selected filter IDs + search query) persisted between sessions
53
+
54
+ ## Follow Mode
55
+
56
+ - **f** — Toggle follow mode on/off (notification shown)
57
+ - When ON and the file watcher detects a change:
58
+ - The tree cursor automatically jumps to the last changed file
59
+ - Parent folders are expanded to reveal it
60
+ - The preview pane renders the file
61
+ - If the file is git-modified, the preview scrolls to the first diff hunk
62
+ - When OFF, file watcher changes preserve the current cursor position
63
+ - Follow mode does not persist between sessions (always starts OFF)
64
+
65
+ ## Preview Pane
66
+
67
+ - Automatically shows the highlighted file's content
68
+ - **d** — Cycle diff mode forward: full diff → gutter → unified diff
69
+ - **D** (shift) — Cycle diff mode backward
70
+ - Diff modes only apply to files with git changes; clean files show gutter mode as plain file
71
+ - Full diff — Syntax-highlighted full file with `+`/`-` markers and colored backgrounds (green `#002200` for added, red `#300000` for removed)
72
+ - Gutter — Syntax-highlighted file with a change column after line numbers: orange `●` for changed lines, green `+` for added, red `_` where deletions follow. When file has no changes, renders as plain file (no gutter column, no overview bar).
73
+ - Unified diff — Standard coloured unified diff output
74
+ - Preview and full diff share identical rendering (same line numbers, same syntax highlighting)
75
+ - Monokai background (`#272822`) fills the entire pane
76
+ - Header bar (pinned, doesn't scroll) shows filename on left, diff mode on right
77
+ - When switching diff modes, scroll position is preserved by source line number mapping
78
+ - Preview doesn't change when new files appear in the tree (stable during file watcher updates)
79
+ - When the previewed file changes on disk, scroll position is preserved by source line mapping
80
+ - The 10-second timestamp refresh does not reset preview scroll
81
+ - **Double-click** a line → copies `path:line` to clipboard, flashes highlight
82
+ - **Drag** across lines → live highlight, copies `path:start-end` on release
83
+ - Invalid lines in unified diff (headers, removed) are not selectable
84
+
85
+ ## Columns
86
+
87
+ - **1–6** — Toggle individual columns
88
+ - **b** — Toggle blame columns (author + git time) and trigger background load
89
+ - Available columns: Name, Status (St), Lines (+/-), Size, Modified, Author, Git Time
90
+ - Size and Modified columns use a 256-color gradient by relative magnitude
91
+ - Gradient toggled via command palette
92
+ - Blame columns show "..." while loading, then populate progressively
93
+
94
+ ## Gradient Colors
95
+
96
+ - Applied to Size and Modified columns
97
+ - Color ramp: `[15, 51, 45, 39, 33, 27, 57, 93, 129, 165, 201, 200, 199, 198, 197, 196]`
98
+ - White/cyan (low) → blue/purple (mid) → magenta/red (high)
99
+ - Ranges computed per the currently visible/filtered file set
100
+
101
+ ## File Icons
102
+
103
+ - Loaded from `~/.config/lsd/icons.yaml` if present
104
+ - Resolution order: exact filename → file extension → filetype fallback (file/dir)
105
+ - No PyYAML dependency required (custom parser)
106
+
107
+ ## Git Integration
108
+
109
+ - Graceful degradation for non-git directories: hides status/lines columns and filter buttons
110
+ - `.gitignore` rules enforced via Dulwich's `IgnoreFilterManager` (handles nesting, negation, `**` globs)
111
+ - Git status: M (modified), A (added), D (deleted), ? (untracked), SM/SA/SD (staged variants)
112
+ - Diff computed against HEAD
113
+
114
+ ## Live File Watching
115
+
116
+ - File tree updates automatically when files change on disk
117
+ - Watchdog for native filesystem events; polling fallback if watchdog fails
118
+ - On change: rebuild file index, re-apply filters, sync table incrementally (only changed rows update)
119
+ - Column headers preserved during data refreshes (no flicker)
120
+ - Parent folders of changed files are auto-expanded to ensure visibility
121
+ - Preview updates in-place (no scroll reset) when the selected file's content changes
122
+ - Relative timestamps refresh every 10 seconds via `update_cell()` (no table rebuild)
123
+ - `.gamrstate` excluded from watching (prevents feedback loop)
124
+
125
+ ## State Persistence
126
+
127
+ - Saved to `~/.config/gamr/state.json` on quit
128
+ - Restored on next launch if target directory matches
129
+ - Persisted state: view mode, diff mode, column toggles, spaced paths, gradient, collapsed directories, split position, selected file, selected filter IDs, search query
130
+
131
+ ## Resizable Split
132
+
133
+ - Draggable 1-character divider between tree and preview panes
134
+ - Divider is subtle (`$surface-lighten-1`, one shade lighter on hover)
135
+ - Split fraction persisted between sessions
136
+ - Uses `fr` units for pane sizing to avoid rounding gaps
137
+
138
+ ## Diff Overview Bar
139
+
140
+ - 1-column bar docked to the right of the preview pane
141
+ - Only visible in full diff mode; hidden in unified diff and plain preview
142
+ - Shows file-level change distribution: green (added), red (removed), yellow (mixed), dim (unchanged)
143
+ - Two styles toggled via command palette (`ctrl+p` → "braille"):
144
+ - **Line mode** (default): `┃`/`│` characters
145
+ - **Braille mode**: Unicode braille dots packing 4 source lines per terminal row
146
+
147
+ ## Command Palette (ctrl+p)
148
+
149
+ - Toggle spaced paths
150
+ - Toggle gradient colors
151
+ - Toggle diff overview style (line/braille)
@@ -0,0 +1,28 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-06-08
4
+ deciders: edouard
5
+ ---
6
+
7
+ # ADR-001: Use Textual as TUI Framework
8
+
9
+ ## Context and Problem Statement
10
+
11
+ Which Python TUI framework should we use to build an interactive, responsive file browser with split panes, data tables, and async workers?
12
+
13
+ ## Considered Options
14
+
15
+ 1. Textual — modern, CSS-like styling, async-native, rich widget library
16
+ 2. urwid — mature but lower-level, manual layout
17
+ 3. blessed/curses — very low-level, no built-in widgets
18
+
19
+ ## Decision Outcome
20
+
21
+ Chosen option: **Textual** because it provides DataTable, Tree, reactive attributes, CSS styling, built-in Workers API for background tasks, and a pilot testing framework. Its async event loop integrates cleanly with our concurrency needs.
22
+
23
+ ### Consequences
24
+
25
+ - Good, because DataTable gives us proper column alignment and headers
26
+ - Good, because Workers API handles thread↔UI bridging elegantly
27
+ - Good, because CSS separation keeps styling maintainable
28
+ - Neutral, because Textual is relatively new and API may evolve
@@ -0,0 +1,33 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-06-08
4
+ deciders: edouard
5
+ ---
6
+
7
+ # ADR-002: Pure Python Git via Dulwich
8
+
9
+ ## Context and Problem Statement
10
+
11
+ How should we interact with git repositories? We need status, diff, blame, and gitignore support without requiring the git binary.
12
+
13
+ ## Considered Options
14
+
15
+ 1. Dulwich — pure Python, no compiled deps, no git binary needed
16
+ 2. pygit2 — libgit2 bindings, fast but requires native compilation
17
+ 3. GitPython — wraps the git CLI, requires git installed
18
+ 4. Shell out to git — simple but fragile, requires git binary
19
+
20
+ ## Decision Outcome
21
+
22
+ Chosen option: **Dulwich** because it's pure Python (works anywhere Python does), has no compiled dependencies, provides porcelain + low-level APIs for status/diff/log, and includes `IgnoreFilterManager` for proper .gitignore handling.
23
+
24
+ ### Consequences
25
+
26
+ - Good, because zero native deps — easy install on any platform
27
+ - Good, because `IgnoreFilterManager` handles nested .gitignore, negation, and global excludes
28
+ - Bad, because some operations (blame) are slower than native git
29
+ - Mitigation: expensive operations run in background thread workers
30
+
31
+ ## Design Note
32
+
33
+ The `GitProvider` ABC allows swapping Dulwich for another backend (e.g., pygit2 for performance) without changing the rest of the app.
@@ -0,0 +1,28 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-06-08
4
+ deciders: edouard
5
+ ---
6
+
7
+ # ADR-003: Watchdog with Polling Fallback for File Watching
8
+
9
+ ## Context and Problem Statement
10
+
11
+ How do we detect filesystem changes in real-time to keep the file tree up to date?
12
+
13
+ ## Considered Options
14
+
15
+ 1. watchdog — cross-platform, uses FSEvents on macOS, inotify on Linux
16
+ 2. OS-native only (PyObjC/fsevents) — macOS-specific
17
+ 3. Pure polling — simple but high latency and CPU cost
18
+ 4. watchdog + polling fallback — best of both
19
+
20
+ ## Decision Outcome
21
+
22
+ Chosen option: **watchdog with polling fallback** because watchdog provides efficient native filesystem events on all platforms, and polling ensures the app still works if watchdog fails (e.g., network filesystems, Docker volumes).
23
+
24
+ ### Consequences
25
+
26
+ - Good, because native events are near-instant on macOS (FSEvents)
27
+ - Good, because polling fallback ensures robustness on edge-case filesystems
28
+ - Neutral, watchdog runs its own thread — requires queue-based bridging to Textual's event loop
@@ -0,0 +1,32 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-06-08
4
+ deciders: edouard
5
+ ---
6
+
7
+ # ADR-004: DataTable with Tree Semantics (Not Tree Widget)
8
+
9
+ ## Context and Problem Statement
10
+
11
+ How do we display a file tree alongside metadata columns with proper alignment?
12
+
13
+ ## Considered Options
14
+
15
+ 1. Textual's Tree widget with render_label() override — single text column, manual padding
16
+ 2. DataTable with tree-rendered first column — real columns, proper alignment
17
+ 3. Side-by-side Tree + DataTable with scroll sync — complex sync logic
18
+
19
+ ## Decision Outcome
20
+
21
+ Chosen option: **DataTable with tree semantics in column 1** because it gives us real, properly-aligned columns with headers (clickable for sorting), while the first column renders indentation and expand/collapse icons to simulate tree behavior.
22
+
23
+ ### Consequences
24
+
25
+ - Good, because columns are independently sized, have headers, support sorting
26
+ - Good, because DataTable cursor_type="row" gives us keyboard navigation for free
27
+ - Bad, because we reimplement expand/collapse logic (space key handler)
28
+ - Neutral, tree state is internal; the DataTable is rebuilt on expand/collapse
29
+
30
+ ## Supersedes
31
+
32
+ Initial implementation used Tree widget with render_label() appending padded metadata — but columns didn't align when filenames varied in length.
@@ -0,0 +1,37 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-06-08
4
+ deciders: edouard
5
+ ---
6
+
7
+ # ADR-005: Thread Workers for Blocking Git Operations
8
+
9
+ ## Context and Problem Statement
10
+
11
+ Textual runs on a single asyncio event loop. Dulwich operations (status, diff, blame) are synchronous and can block for seconds on large repos. How do we prevent UI freezes?
12
+
13
+ ## Decision Outcome
14
+
15
+ Use Textual's `@work(thread=True)` decorator to run blocking operations in thread workers.
16
+
17
+ ### Concurrency Architecture
18
+
19
+ | Operation | Worker Type | Strategy |
20
+ |-----------|------------|----------|
21
+ | Git status | Thread, exclusive | Debounced after file changes |
22
+ | Diff stats (+/- lines) | Thread, exclusive | Deferred after initial load |
23
+ | Git blame/log | Thread, group | Progressive per-file updates |
24
+ | File watcher | Thread, long-running | Polls queue every 0.5s |
25
+ | Fuzzy filter | Inline (sync) | Fast enough for <10k files |
26
+
27
+ ### Key Constraints
28
+
29
+ - **watchdog → UI**: Events arrive on watchdog's thread; bridged via `call_from_thread()` or `post_message()` (thread-safe)
30
+ - **Dulwich → UI**: Results pushed via `call_from_thread()`; check `worker.is_cancelled` before updating
31
+ - **Exclusive workers**: Cancel previous work when user changes selection/filter (prevents stale writes)
32
+
33
+ ### Consequences
34
+
35
+ - Good, because UI stays responsive during expensive git operations
36
+ - Good, because Textual manages worker lifecycle (auto-cleanup on widget removal)
37
+ - Neutral, blame columns show "..." placeholder while loading