fix(staged): resolve project session state from backend instead of content heuristics#726
Merged
Merged
Conversation
Project notes used a content-based heuristic (empty title + content = running) to determine active state, causing two bugs: 1. Replying to a completed note didn't show the spinner due to a race condition in the isKnownNoteSession gate 2. Cancelled sessions left empty stubs that still appeared as "generating" This unifies project notes with the branch timeline approach by: - Adding sessionStatus and completionReason fields to ProjectNote, resolved at query time via the same resolve_session_status() used by timeline items - Extracting a shared isSessionActive() helper used by ProjectSection, BranchCard, and BranchCardSessionManager - Simplifying the ProjectSection event handler to always refresh from the backend (removing the racy isKnownNoteSession check) - Filtering out empty stubs from cancelled/errored sessions in the backend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…e helpers Project note deletion didn't cancel running sessions or clean up session records, leaving orphaned sessions running to completion. Branch notes handled this correctly via a cancel → delete → cleanup flow. This fix: - Adds delete_session handling to delete_project_note (Tauri command + web server), mirroring the existing delete_note pattern - Extracts a shared deleteSessionLinkedItem() frontend helper that encapsulates cancel → delete → registry cleanup, used by both ProjectSection and BranchCard (note, review, pending commit deletion) - Fixes project note sorting to use isSessionActive(sessionStatus) instead of the stale content-based heuristic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…backend
The backend filter removed project notes with empty content whose session
was cancelled or errored. This caused notes to vanish entirely from the
UI — the frontend already handles these via the "failed-note" display
type ("Session finished — no note created"), so the backend filter was
redundant and overly aggressive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…failures buildProjectHashtagItems used Promise.all for branch timeline fetches, so a single failing branch caused the entire operation to reject silently (the $effect had no .catch handler), leaving hashtagItems as [] and the # dropdown permanently empty. This fix: - Switches to Promise.allSettled so individual branch timeline failures are skipped rather than aborting all hashtag item loading - Adds .catch() to the $effect to log errors instead of swallowing them - Adds a stale flag to prevent race conditions when dependencies change rapidly (matching the pattern used in NewSessionModal) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…events Session completion/error/cancelled events emitted by emit_status() had project_id and branch_id hardcoded to None, causing ProjectSection's listener to silently drop them (it filters on projectId === project.id). This left the spinner stuck until the user opened and closed the session dialog, which triggered an unconditional loadProjectNotes(). Fix by piping branch_id and project_id through SessionConfig and PipelineConfig from callers who already have the context, so emit_status can include them in terminal events without a DB lookup. This makes the data flow explicit from session creation to completion. Changes: - Add branch_id/project_id fields to SessionConfig and PipelineConfig - All ~12 callers of start_session/start_pipeline_session now populate these fields from the branch/project context they already hold - emit_status() accepts and forwards the piped IDs instead of hardcoding None - Pipeline→AI handoff copies IDs from PipelineConfig to SessionConfig - Pipeline terminal paths use piped branch_id for queue draining instead of a DB lookup via get_branch_id_for_session - recover_orphaned_sessions (app-quit recovery) falls back to DB lookup via new Store::get_project_id_for_session() since it has no caller context - cancel_session command also looks up IDs from DB for its inline event Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…euristic The comment on the project note stub creation still referenced the old !title && !content frontend check, which was replaced by backend-resolved sessionStatus in 0fbd80a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
delete_project_note used separate get + delete calls, creating a window where the note could be deleted by another caller between reading its session_id and performing the delete. Fix by using DELETE ... RETURNING session_id to atomically delete the note and retrieve its session_id in a single query. Both the Tauri command and web server handler now use this simplified path consistently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Some callers (e.g. BranchCard timeline items) may pass undefined for sessionStatus. The function already returns false for undefined, but the type signature didn't reflect this, masking potential missing-data bugs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…events ProjectSection's session-status-changed handler called loadProjectNotes() on every event (including running), reloading all notes when only one changed. This is wasteful since a session maps 1:1 to a single note. Fix by: - Adding get_project_note_by_session command (Tauri + web server) that returns a single note with resolved session status - Skipping backend fetch on 'running' events (the stub is already loaded by startProjectSession) - On terminal events (completed/error/cancelled), fetching only the affected note by session ID and patching it into the local array Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…timeline invalidation The # hashtag reference dropdown in project prompts showed stale data after sessions created commits, because nothing triggered a rebuild of hashtagItems. Two contributing factors: 1. The hashtag $effect only depended on project.id, branches, and reposById — none of which change when a session creates a commit 2. The timeline cache wasn't invalidated by project session completion, so even a forced re-run would return stale data Fix by adding a hashtagVersion counter as a reactive dependency in the hashtag $effect. The counter is bumped in two places: - On project session terminal events (completed/error/cancelled), after invalidating all branch timeline caches - On timeline-invalidated custom events, catching branch-level session completions that BranchCard already fires Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
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
sessionStatusandcompletionReasonfields populated at query time from the sessions table, replacing the fragile frontend heuristic that inferred "generating" from empty title+content.branch_id/project_idon all session events:SessionConfigandPipelineConfignow carry ownership context so terminal events (completed,error,cancelled) includeproject_id/branch_id— eliminating the DB lookup race whereemit_statuspreviously sentnullfor these fields.deleteSessionLinkedItem()on the frontend and madedelete_project_notereturn+delete the linked session atomically (usingDELETE ... RETURNING) to eliminate TOCTOU races.buildProjectHashtagItemsnow usesPromise.allSettledso a single branch timeline failure doesn't break the entire hashtag list.isSessionActivehelper consolidates active-status checks, stale comment updated,isSessionActiveacceptsundefined.Test plan
crates-testpassingstaged-cipassing🤖 Generated with Claude Code