Skip to content

fix(tui): stop default-branch owner shimmer in prune/sync#430

Merged
avihut merged 4 commits intomasterfrom
fix/main-branch-owner-infinitely-loads-on-prune-and-sync
May 1, 2026
Merged

fix(tui): stop default-branch owner shimmer in prune/sync#430
avihut merged 4 commits intomasterfrom
fix/main-branch-owner-infinitely-loads-on-prune-and-sync

Conversation

@avihut
Copy link
Copy Markdown
Owner

@avihut avihut commented May 1, 2026

Summary

  • Fix: The main branch's owner cell shimmered indefinitely in daft prune and daft sync. Root cause: resolve_owner_with_fallbacks returns None for the default branch by design, but the loading-state machinery never marked OWNER as received because prune/sync's streaming_fields never includes it (and with default flags no streaming collector even runs, so WorktreeInfoCollectionDone never fires).
  • Mechanism: New seeded_fields: FieldSet plumbed through TableConfigTuiState::newLiveTableConfigLiveTable::new, where each row's received_patches is now initialized to seeded_fields instead of FieldSet::EMPTY. This pre-marks bits whose authoritative value is known before TUI start (synchronously seeded, or guaranteed not to arrive via the stream).
  • Per call site: prune.rs / sync.rs pass !streaming_fields; list_live.rs / clone.rs pass FieldSet::EMPTY.
  • Side-effect (intentional): prune's gone-branch stub rows previously shimmered indefinitely on cells the streaming collector doesn't refresh (LAST_COMMIT, BRANCH_AGE, etc.). They now render as final/empty — net improvement, but a behavior delta worth noting.
  • UX polish: Skeleton shimmer brightness range narrowed from indices 234–253 (~11%–85% luminance) to 239–252 (~30%–80%), softening the pulse on dark-themed terminals.
  • Lockfile: Refreshed mise.lock header URL.

Test plan

  • mise run fmt clean
  • mise run clippy zero warnings
  • mise run test:unit (1495 passing) — includes new regression tests in live_table.rs::tests:
    • seeded_fields_marks_cells_received_at_construction (the fix's red-then-green TDD test)
    • seeded_fields_empty_preserves_existing_loading_behavior (paired negative guard)
  • Manual: run daft prune and daft sync in a sandbox repo with the default branch present — confirm the owner cell on the main row renders blank (no shimmer) for the duration of the operation
  • Manual: visually confirm the new skeleton brightness range feels softer in a dark terminal

🤖 Generated with Claude Code

avihut and others added 4 commits May 1, 2026 11:29
The owner cell on the default branch row in `daft prune` and `daft sync`
shimmered for the entire run. The synchronous seed
(`collect_worktree_info`) deliberately returns `None` for the default
branch's owner (`resolve_owner_with_fallbacks` short-circuits when
`branch == base`), so `info.owner` is empty by design. The streaming
collector in prune/sync, however, only requests heavy fields (size /
mtime / *_lines) — never `OWNER` — so no `Owner` patch ever arrives to
mark the bit as received. With `vals.owner` empty and the OWNER bit
unset in `received_patches`, the render path always took the
`is_cell_loading` branch and animated the shimmer indefinitely.

Plumb a new `seeded_fields: FieldSet` through `TableConfig` /
`TuiState::new` / `LiveTableConfig` and use it to pre-seed each row's
`received_patches` in `LiveTable::new`. Prune and sync pass
`!streaming_fields` (everything the seed authoritatively populated
that the stream won't update). List passes `EMPTY` (the streaming
collector requests `ALL`). Clone also passes `EMPTY` (seeded with
empty stubs).

`daft list` was unaffected because its streaming collector requests
`FieldSet::ALL`, which fires a `P::Owner(None)` patch for the default
branch that flips the bit.

Regression test: `seeded_fields_marks_cells_received_at_construction`
in `live_table::tests` (paired with
`seeded_fields_empty_preserves_existing_loading_behavior`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bitmask represents "fields not arriving via the streaming
collector" rather than "fields the synchronous seed populated".
It works because the render path keys shimmer off `vals.X.is_empty()`
rather than the bit alone — empty cells with the bit set render as
"final empty" rather than shimmering — but the comment should match
reality.

Also acknowledges in the live_table.rs `push_row` doc that the
`FieldSet::EMPTY` default for dynamically-discovered branches is a
conservative choice, not provably-correct for all callers: cells that
no patch ever lands on (e.g. gone branches surfaced after fetch in
prune) will shimmer indefinitely. That's a pre-existing condition;
fix is out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-generated header comment in mise.lock was pointing at the old
mise.jdx.dev domain. Refresh to mise.en.dev to match the current
upstream URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The loading skeleton previously breathed across xterm grayscale indices
234-253 (~11%-85% luminance), a 74-point swing that read as harsh on
dark-themed terminals. Narrow to indices 239-252 (~30%-80%, ~50-point
swing) for a softer pulse that stays clearly visible without strobing.

Triangle-wave shape and 16-frame breath period are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avihut avihut added this to the Public Launch milestone May 1, 2026
@avihut avihut added the fix Bug fix label May 1, 2026
@avihut avihut self-assigned this May 1, 2026
@avihut avihut merged commit 85efe84 into master May 1, 2026
9 checks passed
@avihut avihut deleted the fix/main-branch-owner-infinitely-loads-on-prune-and-sync branch May 1, 2026 09:15
This was referenced May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant