Skip to content

Commit e3cf51e

Browse files
committed
Improve sidebar refresh progress bar and reduce session scan overhead
Replace broken GeometryReader+onAppear indeterminate loader with TimelineView+Canvas that reliably animates back and forth. Remove intermediate session publishing during refresh (was firing N partial UI updates every 25 sessions), now publishes once at the end.
1 parent dfe1f11 commit e3cf51e

2 files changed

Lines changed: 24 additions & 61 deletions

File tree

macos/Sources/Features/Worktrunk/WorktrunkSidebarView.swift

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -838,38 +838,24 @@ private struct RepoPickerPopover: View {
838838
}
839839

840840
private struct SidebarRefreshProgressBar: View {
841-
@State private var position: CGFloat = 0
842-
843-
private let barWidthRatio: CGFloat = 0.25
844-
845841
var body: some View {
846-
GeometryReader { geometry in
847-
ZStack(alignment: .leading) {
848-
Rectangle()
849-
.fill(Color.accentColor.opacity(0.3))
850-
851-
Rectangle()
852-
.fill(Color.accentColor)
853-
.frame(
854-
width: geometry.size.width * barWidthRatio,
855-
height: geometry.size.height
856-
)
857-
.offset(x: position * (geometry.size.width * (1 - barWidthRatio)))
842+
TimelineView(.animation) { timeline in
843+
let t = timeline.date.timeIntervalSinceReferenceDate
844+
let phase = (1 + sin(t * .pi)) / 2
845+
Canvas { context, size in
846+
context.fill(
847+
Path(CGRect(origin: .zero, size: size)),
848+
with: .color(.accentColor.opacity(0.15))
849+
)
850+
let barWidth = size.width * 0.25
851+
let x = phase * (size.width - barWidth)
852+
context.fill(
853+
Path(CGRect(x: x, y: 0, width: barWidth, height: size.height)),
854+
with: .color(.accentColor)
855+
)
858856
}
859857
}
860858
.frame(height: 3)
861-
.clipped()
862859
.allowsHitTesting(false)
863-
.onAppear {
864-
withAnimation(
865-
.easeInOut(duration: 1.2)
866-
.repeatForever(autoreverses: true)
867-
) {
868-
position = 1
869-
}
870-
}
871-
.onDisappear {
872-
position = 0
873-
}
874860
}
875861
}

macos/Sources/Features/Worktrunk/WorktrunkStore.swift

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,11 +1377,7 @@ final class WorktrunkStore: ObservableObject {
13771377

13781378
func refreshSessions() async {
13791379
var allSessions: [String: [AISession]] = [:]
1380-
var updateCount = 0
13811380
var dirtyWorktreePaths = Set<String>()
1382-
let publishCheckBatch = 25
1383-
let publishIntervalSeconds: TimeInterval = 0.2
1384-
var lastPublish = Date.distantPast
13851381
let validWorktreePaths = sidebarWorktreePaths
13861382
var index = sessionIndex.snapshot()
13871383
var seenClaudePaths = Set<String>()
@@ -1401,36 +1397,17 @@ final class WorktrunkStore: ObservableObject {
14011397
}
14021398
}
14031399

1404-
func prepareForPublish() {
1400+
func sortDirtySessions() {
14051401
guard !dirtyWorktreePaths.isEmpty else { return }
14061402
for path in dirtyWorktreePaths {
14071403
allSessions[path]?.sort { $0.timestamp > $1.timestamp }
14081404
}
14091405
dirtyWorktreePaths.removeAll(keepingCapacity: true)
14101406
}
14111407

1412-
func publishSnapshotIfNeeded(force: Bool = false) async {
1413-
let now = Date()
1414-
if !force, now.timeIntervalSince(lastPublish) < publishIntervalSeconds {
1415-
return
1416-
}
1417-
lastPublish = now
1418-
prepareForPublish()
1419-
let snapshot = allSessions
1420-
await MainActor.run {
1421-
if sessionsByWorktreePath != snapshot {
1422-
sessionsByWorktreePath = snapshot
1423-
}
1424-
}
1425-
}
1426-
1427-
func noteSession(_ session: AISession, worktreePath: String) async {
1408+
func noteSession(_ session: AISession, worktreePath: String) {
14281409
allSessions[worktreePath, default: []].append(session)
14291410
dirtyWorktreePaths.insert(worktreePath)
1430-
updateCount += 1
1431-
if updateCount % publishCheckBatch == 0 {
1432-
await publishSnapshotIfNeeded()
1433-
}
14341411
}
14351412

14361413
// Scan Claude sessions with periodic UI updates
@@ -1475,7 +1452,7 @@ final class WorktrunkStore: ObservableObject {
14751452
sourcePath: sessionFile.path,
14761453
messageCount: cached.messageCount
14771454
)
1478-
await noteSession(session, worktreePath: worktreePath)
1455+
noteSession(session, worktreePath: worktreePath)
14791456
if cached.worktreePath != worktreePath {
14801457
var updated = cached
14811458
updated.worktreePath = worktreePath
@@ -1496,7 +1473,7 @@ final class WorktrunkStore: ObservableObject {
14961473
validWorktreePaths: validWorktreePaths
14971474
) {
14981475
session.worktreePath = worktreePath
1499-
await noteSession(session, worktreePath: worktreePath)
1476+
noteSession(session, worktreePath: worktreePath)
15001477
index.claude[sessionFile.path] = SessionIndexEntry(
15011478
sessionId: session.id,
15021479
cwd: session.cwd,
@@ -1557,7 +1534,7 @@ final class WorktrunkStore: ObservableObject {
15571534
sourcePath: fileURL.path,
15581535
messageCount: cached.messageCount
15591536
)
1560-
await noteSession(session, worktreePath: worktreePath)
1537+
noteSession(session, worktreePath: worktreePath)
15611538
if cached.worktreePath != worktreePath {
15621539
var updated = cached
15631540
updated.worktreePath = worktreePath
@@ -1574,7 +1551,7 @@ final class WorktrunkStore: ObservableObject {
15741551
validWorktreePaths: validWorktreePaths
15751552
) {
15761553
session.worktreePath = worktreePath
1577-
await noteSession(session, worktreePath: worktreePath)
1554+
noteSession(session, worktreePath: worktreePath)
15781555
index.codex[fileURL.path] = SessionIndexEntry(
15791556
sessionId: session.id,
15801557
cwd: session.cwd,
@@ -1678,7 +1655,7 @@ final class WorktrunkStore: ObservableObject {
16781655
sourcePath: infoFile.path,
16791656
messageCount: cached.messageCount
16801657
)
1681-
await noteSession(session, worktreePath: worktreePath)
1658+
noteSession(session, worktreePath: worktreePath)
16821659
}
16831660
continue
16841661
}
@@ -1733,7 +1710,7 @@ final class WorktrunkStore: ObservableObject {
17331710
sourcePath: infoFile.path,
17341711
messageCount: messageCount
17351712
)
1736-
await noteSession(session, worktreePath: worktreePath)
1713+
noteSession(session, worktreePath: worktreePath)
17371714
}
17381715
}
17391716
}
@@ -1743,8 +1720,8 @@ final class WorktrunkStore: ObservableObject {
17431720

17441721
index.opencode = index.opencode.filter { seenOpenCodePaths.contains($0.key) }
17451722

1746-
// Final sort and update
1747-
prepareForPublish()
1723+
// Final sort and single publish
1724+
sortDirtySessions()
17481725

17491726
sessionCache.saveToDisk()
17501727
sessionIndex.update(index)

0 commit comments

Comments
 (0)