cli: output polish batch from the friction walk (#161)#179
Merged
mbertschler merged 2 commits intoJul 24, 2026
Merged
Conversation
Small, independently-landable output fixes surfaced by the reference-setup
walk. No behavioural change to what gets synced or stored — reporting only.
F7 — sync summaries report already_correct so an in-sync no-op is
distinguishable from an empty one (transferred=0 alone is ambiguous). For
rclone bucket pushes and restores it is rclone's already-matching count;
for peer syncs the handler derives it as present-total minus the paths the
sync acted on (the Merkle walk never sends identical folders to /plan).
F8 — a first index of an empty tree warns ("no files found — new volume,
empty directory, or wrong mount?"); a wrong mount otherwise looks identical
to a healthy no-op. index.Report.SawFiles() carries the decision.
F11a — the index summary names the volume, so back-to-back runs are legible
instead of three anonymous count lines.
F11b — a kopia-only sync skips the rclone preamble; "rclone.conf updated"
no longer prints when kopia never touches rclone.
F11c — kopia's ANSI escape sequences are stripped before its stderr tail is
folded into an error, so they don't leak into the structured log.
F11d — the per-pair summary line is suppressed when a run never reached a
terminal state (an error before its runs row was allocated), instead of
printing "status= … run=0". Refused rows minted by #157 still print.
F18 — peer-sync rows label direction (→ outbound / ← inbound) in `runs` and
the TUI. Only the initiator records runs.shallow, so a set flag marks the
pushing side; no schema change.
F19 — `runs --failed` and `runs --changes` filters; TUI folds consecutive
no-op rows behind a marker (f toggles); the `runs` help no longer claims to
list only index runs.
F24 — after a burst of new files following a long quiet gap, the index
summary adds a catch-up note ("404 new files after 21 days"), so the moment
squirrel earns the most trust isn't rendered as routine noise.
Restore arrow — the restore summary flips to destination → volume, matching
the byte-flow direction instead of the sync-shaped volume → destination.
Stacked on #157 (PR #174).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
There was a problem hiding this comment.
Pull request overview
This PR polishes CLI/TUI output to address several “friction walk” reporting nits (F7/F8/F11/F18/F19/F24), without changing what data is synced, indexed, or stored.
Changes:
- Add clearer run/sync summaries (e.g.,
already_correct=N, restore arrow direction, skip rclone preamble for kopia-only batches) and strip kopia ANSI codes from surfaced errors. - Improve
indexUX by naming the volume in the summary, warning on first index of an empty tree, and adding an optional catch-up note after long quiet gaps. - Enhance
runsCLI/TUI readability via peer direction arrows,--failed/--changesfilters, and TUI folding of consecutive no-op rows.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tui/runs.go | Adds fold markers for consecutive no-op rows and peer direction arrows in the TUI runs table. |
| tui/runs_polish_test.go | Unit tests for TUI destination arrow rendering and no-op detection. |
| sync/sync.go | Adds AlreadyCorrect to sync reports and populates it for rclone-based runs. |
| sync/node.go | Derives AlreadyCorrect for peer syncs using a store count. |
| sync/kopia.go | Strips ANSI escape sequences from kopia stderr before folding into errors. |
| sync/kopia_test.go | Tests ANSI stripping behavior. |
| store/files.go | Adds CountPresentFilesInVolume used for peer-sync “already correct” reporting. |
| index/index.go | Adds Report.SawFiles() helper to detect empty-tree walks without printing. |
| cmd/squirrel/sync.go | Skips rclone preamble for kopia-only batches; adds already_correct to summary; suppresses counter line for run-less preflight errors. |
| cmd/squirrel/sync_test.go | CLI coverage for already_correct and kopia-only “no rclone.conf updated” behavior. |
| cmd/squirrel/runs.go | Adds --failed/--changes filtering and peer direction arrows in the CLI runs listing. |
| cmd/squirrel/restore.go | Flips the restore summary arrow to match byte-flow direction. |
| cmd/squirrel/restore_test.go | Tests restore arrow direction rendering. |
| cmd/squirrel/polish_test.go | Adds unit tests for runs filters, peer direction arrows, catch-up heuristic, and index advisories. |
| cmd/squirrel/index.go | Names volumes in index summaries; prints empty-first-index warning and catch-up note. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…179) `gatherRuns` fetched an unbounded run slice under --failed/--changes and counted conflicts across the whole thing, feeding every peer-sync id into one IN (...) query — on a large history that overflows SQLite's bound-parameter cap ("too many SQL variables"), and --failed never needed the counts at all. - Conflict counts are now loaded only where needed: --failed consults none for filtering (an attention-worthy run is kept unconditionally); --changes counts only the ambiguous rows (a clean 0-file success); and the CONFLICTS-column counts are loaded for just the displayed rows, after filtering and truncation, not the unbounded fetch. - CountFilesFirstSeenByRunWithPathPrefix batches the id set (chunks of 400, each query binding chunk+2 params) so no caller can overflow the cap regardless of history size. Added a store test that passes 5000+ ids. filterRuns stays a pure predicate (unit-tested); the load strategy lives in gatherRuns/conflictCountsForFilter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
mbertschler
merged commit Jul 24, 2026
6a403b3
into
claude/issue-157-run-rows-standing-states
3 checks passed
This was referenced Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The friction-log walk's S3/S4 output nits, implemented as one landable batch.
These are reporting-only changes — no change to what gets synced, indexed,
or stored; the no-loss/append-only invariants are untouched.
Stacked on #157 (PR #174) — its
refusedrun rows are what the F11d linerendering now cooperates with. Base retargets to
mainwhen #157 merges.Changes
already_correct=Nin sync summaries. A no-op-because-in-sync isnow distinguishable from a no-op-because-empty (
transferred=0alone wasambiguous). For rclone bucket pushes and restores it is rclone's
already-matching count; for peer syncs the handler derives it as
present-total minus the paths the sync acted on (the Merkle walk never
sends identical folders to
/plan, so it can't be counted from thedisposition list — added
store.CountPresentFilesInVolume).(
no files found — new volume, empty directory, or wrong mount?); a wrongmount otherwise looks identical to a healthy no-op.
index.Report.SawFiles()carries the decision so the library stays free of stdout.
indexsummary line (sync already namedthe pair).
rclone.conf updatedon a kopia-only sync (kopia nevertouches rclone); the whole rclone preamble is skipped for such a batch.
error, so escape sequences don't leak into the structured log /
runs.error.state (a preflight error before its runs row existed) rather than printing
status= … run=0. Refused rows minted by runs: every failure and refusal becomes a run row or a standing state #157 still print (they have astatus and id).
→outbound /←inbound) inrunsandthe TUI. Only the initiator records
runs.shallow, so a set flag marks thepushing side — no schema change.
runs --failed/runs --changesfilters, TUI folding ofconsecutive no-op rows (
ftoggles), and therunshelp no longer claimsto list only index runs. (See caveat below.)
gap, the index summary adds
catch-up: N new files after D days of quiet.destination → volume,matching the byte-flow direction.
Caveat worth a look
runs.file_countconflates transferred with already-correct for bucketand index runs (it's files considered, not moved), and adding a real "changed"
column would be a schema migration (out of scope while #157 owns v25 / #158
owns v26). So
--changesand the TUI fold reliably collapse peer-syncno-ops (whose
file_countis the transferred count) but deliberately keepbucket/index rows visible rather than risk hiding a run that moved content.
--failedis exact.Testing
go build ./...,go vet ./...— clean.go test ./...— all packages pass.golangci-lint run ./...— 0 issues (rebuilt with the module's go1.26.1toolchain; the preinstalled binary refused the newer target version).
runsfilter predicates, peer-direction arrows,catch-up heuristic,
indexvolume-naming + empty warning,stripANSI, andthe TUI
destinationCell/runIsNoOphelpers. rclone-guarded CLI testscover
already_correctand the restore arrow (skip where rclone is absent).Closes #161
🤖 Generated with Claude Code
https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
Generated by Claude Code