Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions apps/staged/src-tauri/src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,10 @@ fn build_branch_timeline(store: &Arc<Store>, branch_id: &str) -> Result<BranchTi
// Local branch: fetch commits from the local worktree
let worktree_path = Path::new(&wd.path);
if worktree_path.exists() {
let git_commits = match git::get_commits_since_base(worktree_path, &branch.base_branch)
{
Ok(commits) => commits,
Err(e) => {
log::warn!("Failed to get commits since base for branch {branch_id}: {e:?}");
vec![]
}
};
let git_commits = git::get_commits_since_base(worktree_path, &branch.base_branch)
.map_err(|e| {
format!("Failed to get commits since base for branch {branch_id}: {e:?}")
})?;

// For each git commit, look up our metadata (session linkage)
for gc in git_commits {
Expand Down
4 changes: 4 additions & 0 deletions apps/staged/src/lib/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ const inFlightTimelines = new Map<string, Promise<BranchTimeline>>();

export function invalidateBranchTimeline(branchId: string): void {
timelineCache.delete(branchId);
window.dispatchEvent(
new CustomEvent('timeline-invalidated', { detail: { branchIds: [branchId] } })
);
}

interface GetBranchTimelineOptions {
Expand Down Expand Up @@ -373,6 +376,7 @@ export function invalidateProjectBranchTimelines(branchIds: string[]): void {
for (const id of branchIds) {
timelineCache.delete(id);
}
window.dispatchEvent(new CustomEvent('timeline-invalidated', { detail: { branchIds } }));
}

// =============================================================================
Expand Down
12 changes: 12 additions & 0 deletions apps/staged/src/lib/features/branches/BranchCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,18 @@
void loadTimeline();
});

// Re-fetch timeline when the cache is invalidated (e.g. after project-setup-progress)
$effect(() => {
const handler = (e: Event) => {
const { branchIds } = (e as CustomEvent<{ branchIds: string[] }>).detail;
if (branchIds.includes(branch.id) && (branch.worktreePath || isRemote)) {
void loadTimeline();
Comment on lines +520 to +521
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate invalidation reload on remote workspace readiness

The new timeline-invalidated listener reloads whenever isRemote is true, even if the remote workspace is not running. In this repo, invalidateProjectBranchTimelines(...) is fired during project setup refreshes, so stopped/starting remote branch cards will now call loadTimeline() and hit get_branch_timeline while the workspace is unavailable, producing avoidable backend errors and noisy/flickering UI error state. This should use the same readiness guard as the main timeline-load effect (remote status must be running) before triggering a reload.

Useful? React with 👍 / 👎.

}
};
window.addEventListener('timeline-invalidated', handler);
return () => window.removeEventListener('timeline-invalidated', handler);
});

let revalidationVersion = 0;

async function loadTimeline() {
Expand Down
14 changes: 12 additions & 2 deletions apps/staged/src/lib/features/projects/ProjectHome.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,18 @@
]);
setProjects(projectsList);
projects = projectsList;
branchesByProject = new Map(branchesByProject).set(projectId, branches);
commands.invalidateProjectBranchTimelines(branches.map((b) => b.id));
// Merge branches carefully: don't let a stale async response overwrite
// worktreePath with null when we already have it set.
const existingBranches = branchesByProject.get(projectId) || [];
const mergedBranches = branches.map((newBranch) => {
const existing = existingBranches.find((b) => b.id === newBranch.id);
if (existing?.worktreePath && !newBranch.worktreePath) {
return { ...newBranch, worktreePath: existing.worktreePath };
}
return newBranch;
});
branchesByProject = new Map(branchesByProject).set(projectId, mergedBranches);
commands.invalidateProjectBranchTimelines(mergedBranches.map((b) => b.id));
workspaceLifecycle.enqueueInitialSetup(projectId, branches);
replaceProjectRepos(projectId, repos);
void repoBadgeStore.ensureForRepos(
Expand Down