Skip to content

refactor: reactive refresh architecture for hunk staging (#283)#311

Merged
esmuellert merged 3 commits intomainfrom
dev
Mar 7, 2026
Merged

refactor: reactive refresh architecture for hunk staging (#283)#311
esmuellert merged 3 commits intomainfrom
dev

Conversation

@esmuellert
Copy link
Owner

Summary

Replaces the manual refresh wiring in stage/unstage/discard hunk operations with a reactive architecture where the .git watcher is the single source of truth.

Root Cause

The staging system had no clear ownership for "who refreshes what after a git operation." A single stage_hunk triggered up to 3 concurrent view.update() calls that raced against each other, causing stale highlights, flickering, and missing refreshes.

Architecture Change

.git watcher detects change
  → sync_mutable_buffers()  [fetches :0, writes only if changed]
    → TextChanged / trigger → auto_refresh recomputes diff → highlights update
  → explorer.refresh()  [rebuilds tree, on_file_select only on group/revision change]

Keymaps just do the git operation + notify. Zero refresh logic.

Changes

Core

  • New sync_mutable_buffers() in auto_refresh.lua: fetches :0 content from git, compares with buffer, writes only if changed. Handles readonly/nowrite virtual buffers.
  • .git watcher debounced callback calls sync_mutable_buffers alongside explorer.refresh
  • Enable auto_refresh for all buffers (removed virtual buffer guards)

Simplifications

  • Keymaps: stage/unstage/discard just do git operation + notify (removed explorer.refresh(), view.update(), stale captures, last-hunk branches)
  • View guards: stage only from unstaged view, unstage only from staged view (fixes Hunks staging #283)
  • Refresh re-selection: only calls on_file_select when group or comparison base changes (HEAD→:0)
  • No cursor jump on watcher-triggered refresh (no_jump flag)
  • lifecycle.clear_highlights() in inline_view for namespace cleanup on toggle

Tests

  • 10 new E2E keymap-driven tests (hunk_operations_spec.lua)
  • Cleaned stale_buffer_spec.lua: removed 4 overlapping tests, kept 3 welcome page tests

Net

~100 lines added, ~260 lines removed. Single refresh path, no more racing.

Fixes

  • Fixes Hunks staging #283 (staging already-staged hunk)
  • Fixes stale highlights after staging in inline mode
  • Fixes cursor jumping to first hunk after staging
  • Fixes flickering on staged/unstaged views

esmuellert and others added 3 commits March 5, 2026 23:10
…line

When toggling from side-by-side to inline mode, ns_highlight extmarks
from the side-by-side renderer remained on the buffer. inline_view.update()
only cleared ns_inline via inline.clear(), leaving orphaned highlights
visible after staging/unstaging hunks.

Use lifecycle.clear_highlights() which clears all four namespaces
(ns_highlight, ns_filler, ns_conflict, ns_inline), matching what
side_by_side.update() already does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace manual view.update/explorer.refresh calls in stage_hunk,
unstage_hunk, and discard_hunk with a single reactive data flow:

  .git watcher → sync_mutable_buffers → auto_refresh → diff recompute

Changes:
- New sync_mutable_buffers() in auto_refresh.lua: fetches :0 content
  from git, compares with buffer, writes only if changed, triggers
  diff recomputation. Handles readonly/nowrite virtual buffers.
- .git watcher debounced callback calls sync_mutable_buffers alongside
  explorer.refresh (tree rebuild)
- Enable auto_refresh for all buffers (remove virtual buffer guards)
- Keymaps: stage/unstage/discard just do git operation + notify
- Refresh re-selection: only calls on_file_select when group or
  comparison base (HEAD vs :0) changes
- Guard check: detects HEAD→:0 revision switch for partially staged files
- inline_view: lifecycle.clear_highlights for namespace cleanup on toggle
- Removed 4 overlapping tests, added 10 strict E2E keymap-driven tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Pass no_jump flag from refresh re-selection to on_file_select so
  cursor position is preserved during watcher-triggered updates
- Stage hunk only allowed from unstaged view (modified_revision == nil)
- Unstage hunk only allowed from staged view (modified_revision == ':0')
- Prevents staging already-staged hunks (fixes #283)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esmuellert esmuellert enabled auto-merge March 7, 2026 02:58
@esmuellert esmuellert merged commit 2b19141 into main Mar 7, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hunks staging

1 participant