Skip to content

cli: output polish batch from the friction walk (#161)#179

Merged
mbertschler merged 2 commits into
claude/issue-157-run-rows-standing-statesfrom
claude/issue-161-output-polish
Jul 24, 2026
Merged

cli: output polish batch from the friction walk (#161)#179
mbertschler merged 2 commits into
claude/issue-157-run-rows-standing-statesfrom
claude/issue-161-output-polish

Conversation

@mbertschler

Copy link
Copy Markdown
Owner

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 refused run rows are what the F11d line
rendering now cooperates with. Base retargets to main when #157 merges.

Changes

  • F7 — already_correct=N in sync summaries. A no-op-because-in-sync is
    now distinguishable from a no-op-because-empty (transferred=0 alone was
    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, so it can't be counted from the
    disposition list — added store.CountPresentFilesInVolume).
  • F8 — empty-volume warning. 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 so the library stays free of stdout.
  • F11a — name the volume in the index summary line (sync already named
    the pair).
  • F11b — no rclone.conf updated on a kopia-only sync (kopia never
    touches rclone); the whole rclone preamble is skipped for such a batch.
  • F11c — strip kopia's ANSI codes before folding its stderr tail into an
    error, so escape sequences don't leak into the structured log / runs.error.
  • F11d — suppress the counter line when a run never reached a terminal
    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 a
    status and id).
  • F18 — label peer 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 / runs --changes filters, TUI folding of
    consecutive no-op rows (f toggles), and the runs help no longer claims
    to list only index runs. (See caveat below.)
  • F24 — catch-up note. After a burst of new files following a long quiet
    gap, the index summary adds catch-up: N new files after D days of quiet.
  • Restore arrow — the restore summary flips to destination → volume,
    matching the byte-flow direction.

Caveat worth a look

runs.file_count conflates transferred with already-correct for bucket
and 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 --changes and the TUI fold reliably collapse peer-sync
no-ops (whose file_count is the transferred count) but deliberately keep
bucket/index rows visible rather than risk hiding a run that moved content.
--failed is exact.

Testing

  • go build ./..., go vet ./... — clean.
  • go test ./... — all packages pass.
  • golangci-lint run ./... — 0 issues (rebuilt with the module's go1.26.1
    toolchain; the preinstalled binary refused the newer target version).
  • New unit tests: the runs filter predicates, peer-direction arrows,
    catch-up heuristic, index volume-naming + empty warning, stripANSI, and
    the TUI destinationCell/runIsNoOp helpers. rclone-guarded CLI tests
    cover already_correct and the restore arrow (skip where rclone is absent).

Closes #161

🤖 Generated with Claude Code

https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7


Generated by Claude Code

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
Copilot AI review requested due to automatic review settings July 24, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 index UX 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 runs CLI/TUI readability via peer direction arrows, --failed/--changes filters, 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.

Comment thread cmd/squirrel/runs.go Outdated
…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
mbertschler merged commit 6a403b3 into claude/issue-157-run-rows-standing-states Jul 24, 2026
3 checks passed
@mbertschler
mbertschler deleted the claude/issue-161-output-polish branch July 24, 2026 21:30
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.

3 participants