feat(tui): surface live sync progress and failures
Stream repository start and completion events into the Bubble Tea update loop so long runs show elapsed time, active repositories, a progress bar, live outcome counts, and the latest completed result. Keep an actionable summary after completion, list failures before warnings, focus the first failed repository automatically, and add the f shortcut for cycling through failed, skipped, protected, or divergent repositories.
This commit is contained in:
@@ -18,7 +18,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const version = "0.3.0"
|
||||
const version = "0.3.1"
|
||||
|
||||
var defaultSkippedDirs = map[string]bool{
|
||||
".cache": true, ".claude": true, ".codex": true, ".git": true, ".next": true, ".pnpm-store": true,
|
||||
@@ -64,6 +64,12 @@ type syncResult struct {
|
||||
Branches []branchSyncResult `json:"branches,omitempty"`
|
||||
}
|
||||
|
||||
type syncProgressEvent struct {
|
||||
Path string
|
||||
Started bool
|
||||
Result syncResult
|
||||
}
|
||||
|
||||
func main() {
|
||||
code := run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)
|
||||
os.Exit(code)
|
||||
@@ -421,6 +427,10 @@ func syncAll(root string, repos []repo, jobs int, timeout time.Duration, stdout
|
||||
}
|
||||
|
||||
func syncSelected(root string, repos, workspaceRepos []repo, jobs int, timeout time.Duration, stdout io.Writer, quiet, strict bool) []syncResult {
|
||||
return syncSelectedWithProgress(root, repos, workspaceRepos, jobs, timeout, stdout, quiet, strict, nil)
|
||||
}
|
||||
|
||||
func syncSelectedWithProgress(root string, repos, workspaceRepos []repo, jobs int, timeout time.Duration, stdout io.Writer, quiet, strict bool, progress func(syncProgressEvent)) []syncResult {
|
||||
type item struct {
|
||||
index int
|
||||
repo repo
|
||||
@@ -433,6 +443,12 @@ func syncSelected(root string, repos, workspaceRepos []repo, jobs int, timeout t
|
||||
for _, r := range workspaceRepos {
|
||||
allPaths = append(allPaths, r.Path)
|
||||
}
|
||||
record := func(index int, result syncResult) {
|
||||
results[index] = result
|
||||
if progress != nil {
|
||||
progress(syncProgressEvent{Path: result.Path, Result: result})
|
||||
}
|
||||
}
|
||||
|
||||
for range min(jobs, len(repos)) {
|
||||
wg.Add(1)
|
||||
@@ -441,19 +457,22 @@ func syncSelected(root string, repos, workspaceRepos []repo, jobs int, timeout t
|
||||
for next := range work {
|
||||
r := next.repo
|
||||
if !r.Eligible {
|
||||
results[next.index] = syncResult{Path: r.RelativePath, Skipped: true, Message: r.BlockReason}
|
||||
record(next.index, syncResult{Path: r.RelativePath, Skipped: true, Message: r.BlockReason})
|
||||
continue
|
||||
}
|
||||
// Close the scan/apply race: inspect again immediately before mutation.
|
||||
fresh := inspect(root, r.Path, allPaths)
|
||||
if !fresh.Eligible {
|
||||
results[next.index] = syncResult{Path: r.RelativePath, Skipped: true, Message: "pre-sync recheck: " + fresh.BlockReason}
|
||||
record(next.index, syncResult{Path: r.RelativePath, Skipped: true, Message: "pre-sync recheck: " + fresh.BlockReason})
|
||||
continue
|
||||
}
|
||||
if strict && len(fresh.Dirty) > 0 {
|
||||
results[next.index] = syncResult{Path: r.RelativePath, Skipped: true, Message: fmt.Sprintf("pre-sync recheck: dirty worktree (%d changes)", len(fresh.Dirty))}
|
||||
record(next.index, syncResult{Path: r.RelativePath, Skipped: true, Message: fmt.Sprintf("pre-sync recheck: dirty worktree (%d changes)", len(fresh.Dirty))})
|
||||
continue
|
||||
}
|
||||
if progress != nil {
|
||||
progress(syncProgressEvent{Path: r.RelativePath, Started: true})
|
||||
}
|
||||
if !quiet {
|
||||
outputMu.Lock()
|
||||
fmt.Fprintf(stdout, "\nSTART %s (%s)\n", r.RelativePath, r.Branch)
|
||||
@@ -478,7 +497,7 @@ func syncSelected(root string, repos, workspaceRepos []repo, jobs int, timeout t
|
||||
if err != nil && message == "" {
|
||||
message = err.Error()
|
||||
}
|
||||
results[next.index] = syncResult{Path: r.RelativePath, Success: err == nil, Message: message, Duration: duration, Branches: report.Branches}
|
||||
record(next.index, syncResult{Path: r.RelativePath, Success: err == nil, Message: message, Duration: duration, Branches: report.Branches})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user