Skip to content

Perform an initial fetch when starting the app - #4

Merged
romaintb merged 2 commits into
mainfrom
enhance/automatic_initial_fetch
Aug 18, 2025
Merged

Perform an initial fetch when starting the app#4
romaintb merged 2 commits into
mainfrom
enhance/automatic_initial_fetch

Conversation

@romaintb

@romaintb romaintb commented Aug 18, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a modal “Please wait” syncing dialog with spinner and status messages.
    • Automatic background sync on first launch when no local data is found.
  • Improvements

    • Manual sync (r) now runs in the background, keeping the UI responsive.
    • Prevents multiple syncs from running at the same time.
    • Automatically refreshes local data after syncing completes.
    • Displays latest sync status and clear error messages when issues occur.

@romaintb romaintb self-assigned this Aug 18, 2025
@romaintb romaintb added the enhancement New feature or request label Aug 18, 2025
@coderabbitai

coderabbitai Bot commented Aug 18, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds background sync task tracking to App, spawns sync in background on startup and on 'r' key, tracks JoinHandle, handles task completion in the renderer to update status/errors and reload data, and overlays a modal SyncingDialog during loading/syncing. Introduces and exports the SyncingDialog component and adjusts dialog exports.

Changes

Cohort / File(s) Summary
App state: background sync handle
src/ui/app.rs
Add pub field sync_task: Option<JoinHandle<anyhow::Resultcrate::sync::SyncStatus>>; import JoinHandle; initialize to None in App::new.
Dialogs: new syncing dialog and exports
src/ui/components/dialogs/syncing_dialog.rs, src/ui/components/dialogs/mod.rs, src/ui/components/mod.rs
Add SyncingDialog component rendering a centered modal with spinner/status; re-export SyncingDialog from dialogs and from components; reorganize TaskCreationDialog export.
Events: trigger background sync on 'r'
src/ui/events.rs
Replace synchronous force_clear_and_sync with guarded background spawn: set app.syncing, clone service, store JoinHandle in app.sync_task; prevent concurrent syncs.
Renderer: startup sync, task completion, overlay
src/ui/renderer.rs
After loading, if no local data, start background force_sync and store JoinHandle; in loop, detect finished task, join and update last_sync_status or error_message, reload local data, clear syncing; render SyncingDialog when app.loading or app.syncing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhance/automatic_initial_fetch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@romaintb
romaintb merged commit 4ea7ce1 into main Aug 18, 2025
3 of 5 checks passed
@romaintb
romaintb deleted the enhance/automatic_initial_fetch branch August 18, 2025 17:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
src/ui/app.rs (1)

36-37: Tighten the JoinHandle type and plan for graceful shutdown

  • Prefer the already-imported SyncStatus over the fully-qualified crate::sync::SyncStatus for consistency and readability.
  • Optional: introduce a type alias to reduce verbosity.
  • Optional: abort any in-flight sync on shutdown to avoid a background task lingering after the UI exits.

Apply this minimal consistency diff:

-    pub sync_task: Option<JoinHandle<anyhow::Result<crate::sync::SyncStatus>>>,
+    pub sync_task: Option<JoinHandle<anyhow::Result<SyncStatus>>>,

Optional alias and Drop (outside the selected lines):

// near the top of this file, after imports
type SyncTaskHandle = JoinHandle<anyhow::Result<SyncStatus>>;

// field could then be:
// pub sync_task: Option<SyncTaskHandle>,

// ensure background task doesn't outlive the app
impl Drop for App {
    fn drop(&mut self) {
        if let Some(handle) = &self.sync_task {
            handle.abort();
        }
    }
}
src/ui/components/dialogs/syncing_dialog.rs (2)

25-31: Clarify quit instructions in the dialog copy

Users can also quit with Ctrl+C per event handling. Suggest updating the message for consistency.

-            Line::from(Span::raw("Press q to quit")),
+            Line::from(Span::raw("Press q or Ctrl+C to quit")),

43-69: Clamp percentages to avoid underflow for invalid inputs

If percent_x or percent_y > 100, (100 - percent) underflows (u16). It’s safe with current callers (50, 25) but trivial to harden.

-    fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
-        let popup_layout = Layout::default()
+    fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
+        let px = percent_x.min(100);
+        let py = percent_y.min(100);
+        let popup_layout = Layout::default()
             .direction(Direction::Vertical)
             .constraints(
                 [
-                    Constraint::Percentage((100 - percent_y) / 2),
-                    Constraint::Percentage(percent_y),
-                    Constraint::Percentage((100 - percent_y) / 2),
+                    Constraint::Percentage((100 - py) / 2),
+                    Constraint::Percentage(py),
+                    Constraint::Percentage((100 - py) / 2),
                 ]
                 .as_ref(),
             )
             .split(r);
 
-        let horizontal = Layout::default()
+        let horizontal = Layout::default()
             .direction(Direction::Horizontal)
             .constraints(
                 [
-                    Constraint::Percentage((100 - percent_x) / 2),
-                    Constraint::Percentage(percent_x),
-                    Constraint::Percentage((100 - percent_x) / 2),
+                    Constraint::Percentage((100 - px) / 2),
+                    Constraint::Percentage(px),
+                    Constraint::Percentage((100 - px) / 2),
                 ]
                 .as_ref(),
             )
             .split(popup_layout[1]);
 
         horizontal[1]
     }
src/ui/renderer.rs (3)

35-37: Confirm loading UX is surfaced during local load

You render the SyncingDialog when app.loading || app.syncing. Please confirm App::load_local_data sets app.loading true/false internally; otherwise users may not see the “Loading local data” overlay during the initial load.


89-109: Simplify JoinHandle handling and reload only on success

Two tweaks:

  • Avoid borrowing Option then mutating it; compute finished first, then take(). This sidesteps subtle borrow patterns.
  • Only reload local data on a successful sync. Errors already surface via error_message; skipping a reload avoids unnecessary I/O after a failed sync.

Apply this diff:

-        if let Some(handle_ref) = app.sync_task.as_ref() {
-            if handle_ref.is_finished() {
-                if let Some(handle) = app.sync_task.take() {
-                    match handle.await {
-                        Ok(Ok(status)) => {
-                            app.last_sync_status = status;
-                            app.load_local_data(sync_service).await;
-                        }
-                        Ok(Err(e)) => {
-                            app.error_message = Some(format!("Sync failed: {e}"));
-                        }
-                        Err(join_err) => {
-                            app.error_message = Some(format!("Sync task error: {join_err}"));
-                        }
-                    }
-                    app.syncing = false;
-                }
-            }
-        }
+        if app
+            .sync_task
+            .as_ref()
+            .map(|h| h.is_finished())
+            .unwrap_or(false)
+        {
+            if let Some(handle) = app.sync_task.take() {
+                match handle.await {
+                    Ok(Ok(status)) => {
+                        let should_reload = matches!(status, crate::sync::SyncStatus::Success { .. });
+                        app.last_sync_status = status;
+                        if should_reload {
+                            app.load_local_data(sync_service).await;
+                        }
+                    }
+                    Ok(Err(e)) => {
+                        app.error_message = Some(format!("Sync failed: {e}"));
+                    }
+                    Err(join_err) => {
+                        app.error_message = Some(format!("Sync task error: {join_err}"));
+                    }
+                }
+                app.syncing = false;
+            }
+        }

If you prefer not to use a fully-qualified path in matches!, add this import at the top of the file:

use crate::sync::SyncStatus;

38-49: Guard against double-spawning sync tasks in renderer.rs

Add the same app.sync_task.is_none() check you have in events.rs to the initial-sync branch in src/ui/renderer.rs, so a rapid manual trigger can’t stomp on the initial JoinHandle.

Locations to update:

  • src/ui/renderer.rs, in the Ok(false) arm of the match sync_service.has_local_data().await

Proposed diff:

--- a/src/ui/renderer.rs
+++ b/src/ui/renderer.rs
@@ 38,9 +38,13 @@ impl Renderer {
     // If local DB is empty, start an initial sync in the background
     match sync_service.has_local_data().await {
         Ok(false) => {
-            app.syncing = true;
-            let svc = sync_service.clone();
-            app.sync_task = Some(tokio::spawn(async move { svc.force_sync().await }));
+            // Avoid overwriting an existing sync task
+            if app.sync_task.is_none() {
+                app.syncing = true;
+                let svc = sync_service.clone();
+                app.sync_task = Some(tokio::spawn(async move { svc.force_sync().await }));
+            }
         }
         Ok(true) => {}
         Err(e) => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b50d84 and f33aad3.

📒 Files selected for processing (6)
  • src/ui/app.rs (3 hunks)
  • src/ui/components/dialogs/mod.rs (1 hunks)
  • src/ui/components/dialogs/syncing_dialog.rs (1 hunks)
  • src/ui/components/mod.rs (1 hunks)
  • src/ui/events.rs (1 hunks)
  • src/ui/renderer.rs (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/ui/components/dialogs/syncing_dialog.rs (1)
src/ui/app.rs (2)
  • new (60-97)
  • default (52-54)
src/ui/app.rs (3)
src/sync.rs (6)
  • sync (157-177)
  • SyncService (25-324)
  • SyncStats (328-333)
  • perform_sync (180-235)
  • SyncService (11-15)
  • SyncStatus (18-23)
src/main.rs (1)
  • sync (3-3)
src/lib.rs (1)
  • sync (3-3)
src/ui/events.rs (1)
src/sync.rs (1)
  • SyncService (25-324)
src/ui/renderer.rs (5)
src/ui/components/dialogs/syncing_dialog.rs (1)
  • render (16-41)
src/ui/components/dialogs/error_dialog.rs (1)
  • render (18-34)
src/ui/components/status_bar.rs (1)
  • render (17-45)
src/sync.rs (5)
  • SyncService (25-324)
  • SyncService (11-15)
  • SyncStats (328-333)
  • perform_sync (180-235)
  • SyncStatus (18-23)
src/storage.rs (1)
  • LocalStorage (160-688)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test (beta)
  • GitHub Check: Security Audit
🔇 Additional comments (9)
src/ui/app.rs (2)

7-7: JoinHandle import — looks good

Imports the Tokio JoinHandle needed for background sync orchestration.


84-84: Initialization to None — OK

sync_task starts empty and will be set when a background sync is spawned.

src/ui/components/dialogs/mod.rs (2)

7-7: New syncing_dialog module wiring — OK

Module is declared alongside the existing dialogs.


14-14: Re-export SyncingDialog — OK

Public re-export enables ergonomic use at crate::ui::components::SyncingDialog.

src/ui/events.rs (1)

238-243: Background sync behavior verified

All checks pass:

  • In src/ui/renderer.rs, after handle_ref.is_finished(), the code calls app.sync_task.take(), awaits the handle, updates app.last_sync_status and reloads data on success, handles both service errors and panics by setting app.error_message, and finally sets app.syncing = false.
  • SyncService in src/sync.rs is #[derive(Clone)].

No further changes needed.

src/ui/components/mod.rs (1)

10-12: Public re-exports updated — OK

SyncingDialog is now available to consumers; grouping remains consistent with other dialog exports.

src/ui/components/dialogs/syncing_dialog.rs (1)

15-41: Rendering logic and modal overlay — OK

Centered layout, Clear overlay, and simple status messaging are implemented cleanly.

src/ui/renderer.rs (2)

15-16: Importing SyncingDialog into the renderer is correct

Good integration of the new modal component into the renderer’s import list.


128-131: Syncing/Loading overlay is correctly integrated

The modal overlay condition matches the new state flags and will provide clear feedback during initial load and background sync.

This was referenced Aug 31, 2025
This was referenced Sep 9, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Sep 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant