Skip to content

Into Alpha#5

Open
richy1623 wants to merge 83 commits into
alphafrom
tokio
Open

Into Alpha#5
richy1623 wants to merge 83 commits into
alphafrom
tokio

Conversation

@richy1623
Copy link
Copy Markdown
Owner

@richy1623 richy1623 commented Mar 9, 2025

Summary by CodeRabbit

  • New Features

    • Rebranded app to "Kronos" with new task prompt and task list GUIs (always-on-top windows), task editing/adding, fuzzy search, task prompt delay, and database-backed task tracking with synchronization support.
    • Added database configuration via environment variable.
  • Documentation

    • Updated README and added setup guide and coverage instructions; recommended editor extensions and spell-check settings.
  • Chores

    • Added CI workflow for testing and coverage and added code coverage configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2025

Walkthrough

Adds Rust project scaffolding: CI, tooling, migrations, DB schema, models, settings, sync/state managers, GUI widgets and binary runners, tests, docs, plus an env var and .gitignore updates. The main app coordinates async state and spawns eframe GUI windows.

Changes

Cohort / File(s) Change Summary
Environment & CI
.env, .github/workflows/rust.yaml, codecov.yml, .gitignore
Added DATABASE_URL, new Rust CI workflow (tests + coverage), Codecov config, and updated ignore rules (Rust, DB, tests, coverage, VSCode).
Editor config
.vscode/extensions.json, .vscode/settings.json
Added recommended VS Code extensions and cSpell custom words.
Project manifest & tooling
Cargo.toml, diesel.toml
New/updated Cargo manifest with dependencies and dev-dependencies; Diesel CLI config for schema printing and migrations dir.
Docs & README
README.md, docs/SetupGuide.md
README rewritten (project rename, coverage badge, test instructions); added Diesel setup guide.
Migrations
migrations/.../up.sql, migrations/.../down.sql
New migrations to create task and task_performed tables and indexes; one down.sql contains a syntax error (DROP TABLE ID EXISTS).
Schema & Models
src/schema.rs, src/model.rs, src/model/*.rs
Diesel schema added; models for Task, TaskPerformed, and LatestTask with DB CRUD, search, sync-related methods and tests.
Settings & Persistence
src/settings.rs, tests/res/test_read_settings.json
New Settings struct, file persistence, defaults, tests and a test settings JSON.
Sync & State Managers
src/sync_manager.rs, src/task_prompt_manager.rs
New SyncManager (syncs unsynced tasks to server) and TaskPromptManager (async state machine with channels), with tests and enums.
Core app logic & binaries
src/lib.rs, src/main.rs, src/bin/*.rs
Library exports, DB connection + migrations applicator; async main orchestrates TaskPromptManager and spawns GUI windows; two binary runners for GUIs.
Task domain logic
src/task_list.rs, src/task_prompt.rs
TaskList and TaskPrompt structs managing performed tasks, time tracking, updates, and related tests.
UI widgets
src/widget.rs, src/widget/*.rs
New egui widgets: task select, task prompt widget, and task list widget implementing UI and interaction logic.

Sequence Diagram(s)

sequenceDiagram
    participant Main as async main
    participant TPM as TaskPromptManager
    participant Spawn as Spawner (sync)
    participant TPW as TaskPromptWindow
    participant TLW as TaskListWindow

    Main->>TPM: init() and subscribe()
    TPM-->>Main: broadcast(current_state)
    alt received AwaitingPrompt
        Main->>Spawn: request spawn TaskPromptWindow & TaskListWindow
        Spawn->>TPW: start eframe run (TaskPromptWidget)
        Spawn->>TLW: start eframe run (TaskListWidget)
        TPW->>TPM: send state changes (UiOpen / Closed)
        TLW->>TPM: send state changes (PendingPrompt / UiOpen)
    else other state
        Main->>TPM: continue listening
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

I’m a rabbit among the code trees,
Hopping through modules with tiny pleas.
GUIs sprout leaves and syncs take flight,
Migrations march on through day and night.
A whisker-twitch of tests—now everything’s bright! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title “Into Alpha” is too generic and does not convey the substantive changes introduced by this pull request, such as the addition of GUI applications, database migrations, and a comprehensive refactor of task management modules. It does not help reviewers or future readers understand the primary purpose or scope of the update. Consequently, its relevance to the actual content of the changeset is inconclusive. Please update the pull request title to a concise phrase that highlights the main change, for example “Add GUI task runners, Diesel ORM migrations, and task management modules.” This will help reviewers understand the core purpose of the PR at a glance.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tokio

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0509c9f and fe94cf4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • Cargo.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@richy1623 richy1623 marked this pull request as ready for review March 9, 2025 08:29
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 23

🧹 Nitpick comments (33)
migrations/2024-10-16-222233_create_table_tasks/down.sql (1)

1-3: Enhance SQL Down Migration for Robustness

The down migration currently drops the task table. To prevent potential errors if the table does not exist during rollback, consider using:

-DROP TABLE task;
+DROP TABLE IF EXISTS task;
docs/SetupGuide.md (1)

1-24: Enhance Documentation: Environment Variable Configuration

The setup guide is clear on Diesel usage and commands. It would be beneficial to include a brief note reminding users to set the DATABASE_URL in the corresponding .env file (for example, DATABASE_URL=database.db) to ensure the Diesel ORM connects correctly.

codecov.yml (1)

1-10: Nitpick: Check YAML Newline Characters

A static analysis hint flagged a newline character issue. Please verify that the file uses LF (\n) newlines (especially if edited in an environment that might use CRLF) to ensure YAML lint passes consistently.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 1-1: wrong new line character: expected \n

(new-lines)

migrations/2024-10-16-222233_create_table_tasks/up.sql (1)

15-16: Fix typo in comment line.

There's an extra dash at the beginning of the comment line, making it -- -- instead of just --.

--- -- Create an index on is_synced_to_server for faster queries for syncing
+-- Create an index on is_synced_to_server for faster queries for syncing
.github/workflows/rust.yaml (1)

83-85: Fix command continuation indentation.

The command continuation in line 84 appears to be incorrectly indented. In YAML, continuation lines should be properly aligned with the first content line.

        run:
          cargo tarpaulin --no-fail-fast --target-dir "target/tarpaulin" --exclude-files "target/*" --skip-clean --out
-          xml
+            xml
src/bin/task_prompt_runner.rs (3)

5-5: Remove inappropriate comment.

The comment "it's an example" appears to be from template code and should be removed in production code.

-// it's an example

21-28: Simplify nested thread-safety wrappers and add error handling.

The code has complex nested Arc/Mutex/RwLock structures which could be simplified. Also, the to_str().unwrap() call lacks proper error handling.

     let task_prompt_widget = TaskPromptWidget::new(TaskPrompt::new(
         Arc::new(Mutex::new(kronos::establish_connection(
-            settings.get_database_file_path().to_str().unwrap(),
+            settings.get_database_file_path().to_str().unwrap_or_else(|| {
+                eprintln!("Failed to convert database path to string");
+                std::process::exit(1);
+            }),
         ))),
-        Arc::new(RwLock::new(LatestTaskManager::new(Arc::new(Mutex::new(
-            settings,
-        ))))),
+        Arc::new(RwLock::new(LatestTaskManager::new(Arc::new(Mutex::new(settings))))),
     ));

Consider extracting some of this initialization to separate functions to improve readability.


43-47: Remove template comments in production code.

These educational comments are helpful during development but should be removed or converted to actual implementation in production code.

-        // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
-        // Restore app state using cc.storage (requires the "persistence" feature).
-        // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
-        // for e.g. egui::PaintCallback.

If you plan to implement these features later, consider replacing them with TODOs with specific implementation details.

src/widget/task_prompt_widget.rs (1)

29-33: Consider error handling for update_task.

If self.task_prompt.update_task() fails (e.g., DB error), the UI might close prematurely without informing the user. Consider returning or logging the error before closing the viewport.

src/main.rs (2)

58-139: Reuse or share an existing tokio Runtime if possible.

Creating a new single-threaded runtime for each UI state change can be heavy. Consider using a shared Tokio runtime to avoid repeated overhead.


165-179: Fake window approach is safe to remove or refine.

If you no longer need an invisible placeholder canvas, removing or refactoring this function can reduce complexity.

src/task_prompt.rs (2)

88-95: Revisit or remove commented-out code
This block is commented out. Either reintroduce or remove it to keep the codebase consistent and maintainable.


97-98: TODO: Decide on file-based tracking
There is a TODO reference for storing timestamps in a file. If you still need this functionality, consider creating a dedicated issue or implementing it to avoid incomplete features.

Would you like me to open a new issue to address the file-based time-tracking approach, or provide a draft implementation?

src/settings.rs (1)

148-159: Consider using a more robust error strategy
In save_settings_to_file, the .expect(...) call can crash the application on serialization or write errors. A custom error or a graceful fallback might be preferable for user-facing reliability.

src/widget/task_select_widget.rs (3)

6-15: Ensure clarity of widget state fields
Fields like did_click_option and did_select_option might be combined or inferred from local logic if that simplifies usage. Keeping ephemeral state in local scope can improve readability and maintainability.


25-25: Review RNG usage for widget ID
Using a random u64 is fine for unique IDs, but consider storing a stable ID if you need to persist state across sessions. Otherwise, ephemeral uniqueness is acceptable.


78-83: Replace println! with structured logging
Debugging with println! is fine during prototyping, but consider using a logger (e.g., log::info!) for consistent output and production readiness.

src/task_prompt_manager.rs (2)

69-71: Consider centralizing asynchronous handling instead of creating a new tokio::Runtime on each start.
Repeatedly building a runtime may be costly and can introduce complexities; a single shared runtime or a different approach (e.g., spawning tasks within an existing runtime) might simplify management.


103-103: Handle the todo!() branch gracefully.
The _other match arm calls todo!() if rx.recv() returns None. This may cause unexpected panics if the channel closes. Consider a safer fallback or graceful shutdown path.

src/widget/task_list_widget.rs (1)

190-194: Consider adding basic tests or integration checks for UI logic.
While the UI code is generally straightforward, test coverage helps ensure that date navigation, editing tasks, and adding tasks remain functional after future refactors.

src/sync_manager.rs (2)

76-113: Log errors in case of request failures for better observability.
Right now, when client.execute(request) fails, the code sets SyncStatus::Disconnected and returns without logging the actual error cause. Logging the error can significantly aid troubleshooting.

- Err(_) => {
+ Err(err) => {
    self.sync_status = SyncStatus::Disconnected;
+   error!("Sync request failed: {}", err);
    return;
  }

115-117: Address the // TODO for syncing performed tasks.
The code only synchronizes newly created tasks. Consider providing a complete implementation to keep performed tasks in sync as well.

src/model/latest_task.rs (1)

34-40: Potential duplication with get_latest_task_file_path.

Both methods fetch data storage paths. Consider merging functionalities or clarifying naming to avoid confusion.

src/task_list.rs (3)

1-4: Connection sharing with Arc<Mutex<SqliteConnection>>.

Using a single locked connection can become a bottleneck under concurrency. Consider a connection pool for scalability.


10-14: Naming suggestion.

TaskListItem is fine, but consider TaskEntry or TaskRecord for clarity.


131-184: Lack of transactional safety in multi-step updates.

The rename/merge logic involves several DB operations. Using a transaction can prevent partial data updates on errors.

Cargo.toml (1)

30-30: Commented out dependency "mock".

Either remove it or uncomment if needed to avoid confusion.

src/model/task.rs (4)

21-36: Return robust error information instead of silently converting failures into None.
Using .ok() discards all error details, which can make debugging difficult. Consider returning Result<Option<Self>, Error> or a custom error type to preserve error context.

-pub fn get_task_by_name(task_name: &str, connection: &mut SqliteConnection) -> Option<Self> {
+pub fn get_task_by_name(task_name: &str, connection: &mut SqliteConnection) -> Result<Option<Self>, diesel::result::Error> {
     ...
-    .ok()
+    .map(Some)
}

86-99: Regex-based searching might be slow on large datasets.
Invoking .fetch_most_recent_tasks(1000) and then filtering in-memory is okay for smaller data sets, but consider a more efficient DB-side filtering (e.g., using LIKE) if performance becomes a concern.


112-122: Filtering logic is sound but watch out for big input list sizes.
The method uses the same regex approach as above and can become expensive if task_names is large, as it’s all done in memory.


124-135: Address the TODO comment for calling delete_unused_tasks.
You might integrate this function in a periodic maintenance routine or whenever TaskPerformed entries are removed, to keep your task table clean.

Would you like me to open an issue or propose a solution that schedules or triggers this deletion?

src/model/task_performed.rs (2)

1-16: Consider using a date or datetime type rather than a raw String.
Storing dates as strings can cause parsing overhead and potential data consistency issues. Diesel supports chrono types, which could provide stronger guarantees.


18-74: Data retrieval methods are straightforward but silently ignore certain errors.
All these retrieval functions (get_task_by_task_id_and_date, get_all_tasks_by_task_id, get_all_tasks_by_date) call .ok() or unwrap_or(...), which can obscure underlying DB errors. Consider propagating them for improved observability.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 30c6a7c and 1b41a99.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • .env (1 hunks)
  • .github/workflows/rust.yaml (1 hunks)
  • .gitignore (1 hunks)
  • .vscode/extensions.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • Cargo.toml (1 hunks)
  • README.md (1 hunks)
  • codecov.yml (1 hunks)
  • diesel.toml (1 hunks)
  • docs/SetupGuide.md (1 hunks)
  • migrations/2024-10-16-222233_create_table_tasks/down.sql (1 hunks)
  • migrations/2024-10-16-222233_create_table_tasks/up.sql (1 hunks)
  • migrations/2024-10-16-223041_create_table_tasks_performed/down.sql (1 hunks)
  • migrations/2024-10-16-223041_create_table_tasks_performed/up.sql (1 hunks)
  • src/bin/task_list_runner.rs (1 hunks)
  • src/bin/task_prompt_runner.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/model.rs (1 hunks)
  • src/model/latest_task.rs (1 hunks)
  • src/model/task.rs (1 hunks)
  • src/model/task_performed.rs (1 hunks)
  • src/schema.rs (1 hunks)
  • src/settings.rs (1 hunks)
  • src/sync_manager.rs (1 hunks)
  • src/task_list.rs (1 hunks)
  • src/task_prompt.rs (1 hunks)
  • src/task_prompt_manager.rs (1 hunks)
  • src/widget.rs (1 hunks)
  • src/widget/task_list_widget.rs (1 hunks)
  • src/widget/task_prompt_widget.rs (1 hunks)
  • src/widget/task_select_widget.rs (1 hunks)
  • tests/res/test_read_settings.json (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.35.1)
codecov.yml

[error] 1-1: wrong new line character: expected \n

(new-lines)

.github/workflows/rust.yaml

[error] 1-1: wrong new line character: expected \n

(new-lines)

🪛 actionlint (1.7.4)
.github/workflows/rust.yaml

38-38: property "cache-coverage-restore" is not defined in object type {cache-test-restore: {conclusion: string; outcome: string; outputs: {cache-hit: string; cache-matched-key: string; cache-primary-key: string}}}

(expression)


47-47: input "restore-keys" is not defined in action "actions/cache/save@v4". available inputs are "enableCrossOsArchive", "key", "path", "upload-chunk-size"

(action)


97-97: input "restore-keys" is not defined in action "actions/cache/save@v4". available inputs are "enableCrossOsArchive", "key", "path", "upload-chunk-size"

(action)

🔇 Additional comments (56)
.vscode/settings.json (1)

1-3: Good Configuration for Spell Checker Custom Words

The JSON file correctly sets up custom words for the cSpell extension, accommodating project-specific terminology.

.vscode/extensions.json (1)

1-8: Solid VS Code Extension Recommendations

The file provides a comprehensive list of recommended extensions that align well with the new project setup—offering spell checking, SQL formatting, SQLite editing, and Rust analysis support.

.gitignore (1)

1-19: Updated .gitignore for Rust Transition and Project Artifacts

The updated entries correctly exclude Cargo build outputs, the SQLite database file, user files, test directories, coverage reports, and VS Code settings. This keeps the repository clean from build artifacts and personal configurations.

.env (1)

1-2: Environment Variable for Database Configuration

Setting DATABASE_URL=database.db ensures that the application can easily locate the SQLite database and supports the new database configuration strategy.

tests/res/test_read_settings.json (1)

1-7: Review: JSON Configuration is Correct

The JSON structure is well-formed and clearly defines the settings. Ensure that the file paths and naming conventions align with the rest of the project.

diesel.toml (1)

1-10: LGTM: Diesel Configuration File

The [print_schema] and [migrations_directory] sections are configured appropriately. The file is clear and follows Diesel’s expected configuration guidelines.

src/model.rs (1)

1-3: Clean module organization

The module structure looks well-organized, with clear separation of concerns between task, latest_task, and task_performed modules. This modular approach will facilitate better maintenance and code organization.

src/widget.rs (1)

1-3: Well-structured widget modules

The widget module organization follows good practices with a logical separation of UI components for task list, prompt, and selection. This structure aligns well with the application's task management functionality.

src/schema.rs (3)

3-10: Good database schema design for tasks

The task table schema is well-designed with appropriate fields for tracking task state. The inclusion of is_synced_to_server is a good practice for applications that support synchronization.


12-19: Proper composite key implementation for task_performed

The use of a composite primary key (date, task_id) is an appropriate design for the task_performed table. This structure efficiently supports tracking multiple performances of the same task on different dates.


21-24: Good practice allowing tables in same query

Allowing the tables to appear in the same query will facilitate complex queries that need to join or reference both tables, which is essential for aggregating task performance data.

README.md (2)

1-5: Project renamed to Kronos with coverage tracking

The README now reflects the new project name and includes a code coverage badge, which is good for transparency about test coverage.


7-17: Clear testing instructions added

The instructions for running tests with coverage both locally and on CI are clear and helpful for contributors. Including the exact command for tarpaulin with all the necessary flags is particularly useful.

migrations/2024-10-16-222233_create_table_tasks/up.sql (2)

1-7: Table schema looks well-designed.

The task table has a good structure with appropriate constraints: PRIMARY KEY for id, UNIQUE and NOT NULL for name, and sensible defaults for last_used (current timestamp) and is_synced_to_server (FALSE). Using INTEGER for the primary key in SQLite is efficient as it maps to a ROWID.


9-14: Good index design for query optimization.

Creating indexes on the name, last_used, and is_synced_to_server columns will improve query performance for the expected query patterns. Well-named indexes make database maintenance easier.

src/lib.rs (2)

1-8: Well-organized module structure.

The library is well-organized with clearly defined modules that separate different concerns (sync management, models, schemas, settings, UI components, etc.). This modular approach promotes maintainability and testability.


10-14: Good setup for Diesel ORM and migrations.

The imports and embedded migrations setup is appropriate for a Diesel-based application. Using EmbeddedMigrations ensures that database schema migrations are packaged with the application code.

.github/workflows/rust.yaml (2)

1-9: Good workflow setup with appropriate triggers.

The workflow configuration is well-structured with suitable triggers for all branch pushes and proper environment variable settings.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 1-1: wrong new line character: expected \n

(new-lines)


10-13: Consider the test job condition.

The test job only runs if the commit message contains [ci]. This is an intentional choice, but be aware that it will skip tests for pushes without this marker. Is this the intended behavior for all branches?

src/bin/task_list_runner.rs (5)

1-2: Looks good.

The crate-level attributes for handling Windows console and missing docs are clearly documented.


3-8: Imports appear consistent.

Using Arc<Mutex> for shared data across threads in an eframe context is a conventional approach for concurrency.


33-35: Struct definition is clear.

No issues found in the simple struct for your app state.


37-45: Constructor logic is succinct.

Initialization is straightforward and properly sets up the task_list_widget.


47-53: UI rendering logic looks appropriate.

The update method uses CentralPanel and adds the widget without any obvious issues.

src/widget/task_prompt_widget.rs (3)

1-10: Struct initialization is straightforward.

TaskPromptWidget holds TaskPrompt and TaskSelectWidget with no immediate concerns.


12-27: Widget constructor sets up the select widget effectively.

The code properly configures TaskSelectWidget with the provided task name options and a maximum height.


36-73: UI integration flows well.

This egui::Widget implementation is cohesive. The logic for buttons, enabling "Accept", and closing the viewport is handled cleanly.

migrations/2024-10-16-223041_create_table_tasks_performed/up.sql (2)

1-9: Table definition looks solid.

Columns, primary key, and foreign key constraints appear correct and well structured.


11-15: Indexes on task_id and date are beneficial for performance.

No immediate concerns.

src/main.rs (1)

141-163:

Details

✅ Verification successful

Task prompt creation is orderly.

The usage of Arc<Mutex<SqliteConnection>> and Arc<RwLock<LatestTaskManager>> is consistent with the rest of the code. Just ensure that concurrency constraints in Diesel are respected.


🏁 Script executed:

#!/bin/bash
# Checking references to Diesel documentation about thread safety:
echo "Please consult Diesel's official docs about thread safety here: https://docs.rs/diesel/latest/diesel/"
echo "Specifically ensure that shared connections are properly locked or that a connection pool is used."

Length of output: 422


Diesel Concurrency Verification

The task prompt creation looks orderly and follows the established patterns. The usage of Arc<Mutex<SqliteConnection>> and Arc<RwLock<LatestTaskManager>> is consistent with the rest of the code. However, please verify that Diesel’s thread safety requirements are fully met. In particular, ensure that connections shared via Arc<Mutex<_>> are properly synchronized or consider using a connection pool if needed. You can refer to Diesel’s official docs for guidance: Diesel Thread Safety Documentation.

src/task_prompt.rs (3)

11-18: Evaluate concurrency approach with Arc/Mutex
It might be worthwhile to confirm that atomic database operations won't cause contention or data race conditions at scale. Consider verifying that accesses to db_connection and latest_task_manager remain consistent to avoid possible deadlocks or unexpected panics.

To confirm, you could simulate heavy concurrent usage and ensure no timeouts or lock-ups occur in a multi-threaded scenario.


44-49: Potential for panic when converting to i32
The .try_into().unwrap() call may panic if the minute difference exceeds the i32 range. Consider graceful handling (e.g., capping or returning a Result).


100-425: Extensive test coverage
Your test suite thoroughly covers functions like test_create_new_task_prompt, test_update_task_with_exiting_task, and others, ensuring reliability. The usage of a temporary SQLite database is a solid approach for isolation.

src/settings.rs (2)

13-18: Validate application data directory resolution
Using dirs::data_dir() might fail in unusual environments. Consider verifying that you have appropriate permissions and fallback logic if the directory is unavailable.


183-257: Solid integration tests
The tests accurately validate that settings are persisted and loaded correctly, ensuring resilience and correctness. Great job maintaining coverage here.

src/widget/task_select_widget.rs (1)

37-43: Check error handling of filter method
If Task::filter_all_matching_tasks can fail or return partial results, consider how you might surface errors to the UI. Currently, the code recovers gracefully by returning an empty collection, but be mindful of silent failures.

src/task_prompt_manager.rs (1)

72-110: Check for potential deadlocks in the loop with locked rx and state.
Inside the loop, both rx.lock() and state.lock() are acquired in different orders depending on branch logic. Although the code avoids issues by dropping locks promptly, keep an eye on possible lock-order inversions to ensure no future code changes inadvertently introduce deadlocks.

src/sync_manager.rs (1)

64-65: Validate thread-safety and concurrency when locking the SQLite connection.
Although Mutex<SqliteConnection> is used here, ensure that no other code portions attempt concurrent DB access in an incompatible manner. Diesel’s connections can be sensitive to concurrency.

src/model/latest_task.rs (4)

14-18: Struct design looks good.

Using Option<i32> for task_id with a DateTime<Local> effectively covers cases without a known task.


20-22: Shared settings via Arc<Mutex<Settings>>.

This is viable but be mindful of potential panics if the lock is poisoned.


24-32: Use of unwrap() on the mutex lock.

Calling lock().unwrap() will panic if the lock is poisoned. Consider returning a custom error or gracefully handling lock failures.


105-273: Test coverage is solid.

Tests are thorough. Adding negative tests (e.g., corrupted JSON scenarios) would ensure robust error handling.

src/task_list.rs (1)

195-516: Comprehensive test coverage.

Tests are robust, covering add, update, and delete operations thoroughly. Good job.

Cargo.toml (1)

6-26: Version locking is consistent.

Still, ensure that pinned versions remain free of security advisories and check for available updates periodically.

src/model/task.rs (7)

38-43: Creation logic looks good.
The insertion and returning of the newly created task are well-structured. Concurrency for duplicate names is handled at the database level by returning an error on unique constraint violation.


45-54: Timestamp update logic is clear.
The method cleanly updates last_used and returns the updated record as a Result<Task, Error>. This approach is consistent and straightforward.


56-63: Consider handling load errors gracefully.
unwrap_or(vec![]) may suppress critical database failures, causing silent data loss in some scenarios. Propagating or logging errors ensures better visibility into operational issues.

Would you like to propagate the error or at least log it? For example:

- .unwrap_or(vec![])
+ .unwrap_or_else(|e| {
+     log::error!("Failed fetching recent tasks: {}", e);
+     vec![]
+ })

65-73: Helper function approach is good.
get_or_create_task cleanly checks for the existence of a task before creating one, returning a meaningful Result.


101-110: Ensure the generated regex pattern meets user expectations.
The code expands each character in the search string into a pattern .*C.* for each character C, which can be correct for fuzzy matching but might produce unexpected results for certain special characters or large input lengths.


138-157: Synchronization methods look fine.
The methods to fetch unsynced tasks and update sync status are concise. Returning Result<Task, diesel::result::Error> is helpful for detecting DB write failures.


160-463: Extensive test coverage.
It’s great to see thorough testing, including edge cases for lookups, creation, and updates. You might consider adding tests for unusual search strings to ensure regex-based methods handle them correctly.

src/model/task_performed.rs (5)

76-95: Update logic is correctly constrained.
Filtering by task_id and date ensures the correct record is updated. Returning a Result<TaskPerformed, Error> is also consistent with standard Diesel patterns.


97-130: Insertion logic is clear.
Both normal insert and “insert or overwrite” variants are well-structured and handle existing records with minimal duplication.


132-153: Deletion approach is reliable.
Filtering by task_id and date is straightforward. Returning the count of deleted rows helps confirm successful removal.


165-174: Fetching unsynced tasks is effective.
This query-based approach is a neat way to identify records that haven’t been uploaded to the server. The usage of unwrap_or(vec![]) might mask DB issues, but it’s consistent with the rest of the code.

Would you like to handle or log errors here as well?


177-488: Well-designed tests.
The module includes robust test coverage, verifying insertion, update, retrieval, and deletion. This comprehensive suite greatly reduces the risk of regressions.

Comment thread migrations/2024-10-16-223041_create_table_tasks_performed/down.sql Outdated
Comment thread README.md

[![codecov](https://codecov.io/gh/richy1623/Kronos/branch/tokio/graph/badge.svg?token=XIPY37MUZT)](https://codecov.io/gh/richy1623/Kronos)

<!-- TODO remove the tokio branch -->
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Todo item needs to be addressed

There's a commented TODO about removing the "tokio" branch. This should be addressed before finalizing the PR.


🏁 Script executed:

#!/bin/bash
# Check if the tokio branch exists and its status
git branch -a | grep tokio
git show-ref --verify --quiet refs/heads/tokio && echo "Branch exists locally" || echo "Branch doesn't exist locally"
git ls-remote --heads origin tokio && echo "Branch exists on remote" || echo "Branch doesn't exist on remote"

Length of output: 299


Action Required: Resolve the tokio branch removal discrepancy

  • The TODO comment in README.md at line 5 is still present.
  • Verification shows that while the branch does not exist locally, the remote "tokio" branch still exists.
  • Please either:
    • Remove the remote "tokio" branch (if it's no longer needed) and eliminate the TODO comment, or
    • Update the comment to accurately reflect its current status if the branch is still required.

Comment thread src/lib.rs
Comment on lines +16 to +26
pub fn establish_connection(database_url: &str) -> SqliteConnection {
dotenv().ok();

// let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let mut connection = SqliteConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));

connection.run_pending_migrations(MIGRATIONS).unwrap();

connection
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in database connection.

The current implementation panics if database connection fails, which may not be appropriate for a library. Consider returning a Result type instead. Also, there's a commented-out line that should be removed.

-pub fn establish_connection(database_url: &str) -> SqliteConnection {
+pub fn establish_connection(database_url: &str) -> Result<SqliteConnection, diesel::ConnectionError> {
     dotenv().ok();

-    // let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
-    let mut connection = SqliteConnection::establish(&database_url)
-        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
+    let mut connection = SqliteConnection::establish(&database_url)?;

-    connection.run_pending_migrations(MIGRATIONS).unwrap();
+    connection.run_pending_migrations(MIGRATIONS)
+        .map_err(|e| diesel::ConnectionError::InvalidConnectionUrl(format!("Migration failed: {:?}", e)))?;

     connection
+    Ok(connection)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn establish_connection(database_url: &str) -> SqliteConnection {
dotenv().ok();
// let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let mut connection = SqliteConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
connection.run_pending_migrations(MIGRATIONS).unwrap();
connection
}
pub fn establish_connection(database_url: &str) -> Result<SqliteConnection, diesel::ConnectionError> {
dotenv().ok();
let mut connection = SqliteConnection::establish(&database_url)?;
connection.run_pending_migrations(MIGRATIONS)
.map_err(|e| diesel::ConnectionError::InvalidConnectionUrl(format!("Migration failed: {:?}", e)))?;
Ok(connection)
}

Comment thread .github/workflows/rust.yaml Outdated
Comment thread .github/workflows/rust.yaml Outdated
Comment thread src/task_list.rs
Comment on lines +32 to +55
pub fn change_date(&mut self, date: NaiveDate) {
let get_all_tasks_by_date = TaskPerformed::get_all_tasks_by_date(
&date.to_string(),
&mut self.db_connection.lock().unwrap(),
);
let mut task_for_date = get_all_tasks_by_date
.into_iter()
.map(|task_performed| TaskListItem {
task_name: Task::get_task_by_id(
task_performed.task_id.clone(),
&mut self.db_connection.lock().unwrap(),
)
.unwrap()
.name,
task_performed,
})
.collect::<Vec<TaskListItem>>();
task_for_date.sort_by_key(|task| Reverse(task.task_performed.time_spent));

// task_for_date.sort_by(compare);

self.date = date;
self.tasks_for_date = task_for_date;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DB calls wrapped in unwrap() can panic.

Task::get_task_by_id(...).unwrap() might fail if DB queries fail. Recommend returning a Result to improve reliability.

Comment thread src/task_list.rs
Comment on lines +61 to +104
pub fn add_task(&mut self, task_name: &str, time_spent: i32) {
let mut connection = self.db_connection.lock().unwrap();

let task = Task::get_or_create_task(task_name, &mut connection)
.expect("Failed to get or create task");

let task_performed_index = self
.tasks_for_date
.iter()
.position(|task_list_item| task_list_item.task_name == task_name);

let task_performed = match task_performed_index {
Some(task_performed_index) => {
// Remove and extract the old TaskListItem from the tasks_for_date
let mut task_performed = self
.tasks_for_date
.swap_remove(task_performed_index)
.task_performed;
// Update the total time spent
task_performed.time_spent += time_spent;
TaskPerformed::update_task_performed(&task_performed, &mut connection)
.expect("todo")
}
None => {
TaskPerformed::insert_task_performed(
&TaskPerformed {
date: self.date.to_string(),
task_id: task.id,
time_spent,
},
&mut connection,
)
.expect("todo") // TODO this should probably return the error
}
};

self.tasks_for_date.push(TaskListItem {
task_name: task_name.to_string(),
task_performed,
});

self.tasks_for_date
.sort_by_key(|task| Reverse(task.task_performed.time_spent));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Error handling in add_task is incomplete.

expect("todo") items highlight future improvements. Consider implementing proper error propagation.

Comment thread src/task_list.rs
Comment on lines +106 to +129
pub fn delete_task_performed(&mut self, task_name: &str, date: &NaiveDate) {
let mut connection = self.db_connection.lock().unwrap();

let task = Task::get_task_by_name(task_name, &mut connection);

let task = match task {
Some(task) => task,
None => return,
};

TaskPerformed::delete_task_performed(task.id, &date.to_string(), &mut connection).unwrap();

// Update the tasks_for_date if the task did exist there
match self
.tasks_for_date
.iter()
.position(|task_list_item| task_list_item.task_performed.task_id == task.id)
{
Some(index) => {
self.tasks_for_date.swap_remove(index);
}
None => (),
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

delete_task_performed silently unwraps DB calls.

Any DB error will panic. An error-returning approach can improve maintainability and debugging.

Comment thread Cargo.toml
Comment on lines +1 to +4
[package]
name = "kronos"
version = "0.1.0"
edition = "2024"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Rust edition "2024" is not yet stable.

This may cause build or compatibility issues. Verify that the required toolchain supports it or downgrade to a stable edition like 2021.

Comment thread src/model/task.rs
Comment on lines +14 to +19
pub struct Task {
pub id: i32,
pub name: String,
pub last_used: i32,
pub is_synced_to_server: bool,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using a 64-bit integer for timestamps.
Storing last_used as an i32 could lead to rollover issues (Y2038 problem) in the long term, especially on 32-bit platforms. Switching to an i64 timestamp type may offer improved compatibility and stability.

Here is a potential fix:

-pub last_used: i32,
+pub last_used: i64,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub struct Task {
pub id: i32,
pub name: String,
pub last_used: i32,
pub is_synced_to_server: bool,
}
pub struct Task {
pub id: i32,
pub name: String,
pub last_used: i64,
pub is_synced_to_server: bool,
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 22

♻️ Duplicate comments (13)
README.md (1)

3-5: Action Required: Resolve the tokio branch TODO.
The Codecov badge still points to the tokio branch and the associated TODO comment remains. Please remove this comment and/or delete or rename the branch if it’s no longer needed, and update the badge URL to reference the correct branch (e.g., main or alpha).

src/lib.rs (1)

20-23: 🛠️ Refactor suggestion

Use ? for error handling.
Replace the unwrap_or_else and unwrap() calls with the ? operator to propagate connection or migration errors, improving robustness. For example:

- let mut connection = SqliteConnection::establish(&database_url)
-     .unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
+ let mut connection = SqliteConnection::establish(&database_url)?;
...
- connection.run_pending_migrations(MIGRATIONS).unwrap();
+ connection.run_pending_migrations(MIGRATIONS)?;
.github/workflows/rust.yaml (3)

46-47: Remove invalid 'restore-keys' parameter from cache/save action.

According to the static analysis, the restore-keys input is not defined in the actions/cache/save@v4 action. This parameter should only be used with the restore action.

          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
-          restore-keys: |
-            ${{ runner.os }}-cargo-

38-38: Incorrect cache ID reference.

The condition references cache-coverage-restore, but the correct ID is cache-test-restore (from line 21).

-        if: (success() || failure()) && steps.cache-coverage-restore.outputs.cache-hit != 'true'
+        if: (success() || failure()) && steps.cache-test-restore.outputs.cache-hit != 'true'

94-95: Remove invalid 'restore-keys' parameter from cache/save action.

The restore-keys input is not defined in the actions/cache/save@v4 action. This parameter should only be used with the restore action.

          key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
-          restore-keys: |
-            ${{ runner.os }}-cargo-coverage-
src/bin/task_prompt_runner.rs (1)

29-35: Add proper error handling for run_native result.

The unwrap() call on line 34 will panic if there's an error running the application. Consider proper error handling.

-    eframe::run_native(
+    if let Err(e) = eframe::run_native(
         "My egui App",
         native_options,
         Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc, task_prompt_widget)))),
-    )
-    .unwrap();
+    ) {
+        eprintln!("Failed to run application: {}", e);
+        std::process::exit(1);
+    }
src/model/latest_task.rs (2)

85-101: 🛠️ Refactor suggestion

Return Result from update_latest_task_performed instead of panicking

The TODO comment is spot-on. Persisting to disk can fail; propagating an error lets callers surface it to users or attempt recovery.

-pub fn update_latest_task_performed(&mut self, task_id: Option<i32>) -> LatestTask {
+pub fn update_latest_task_performed(
+    &mut self,
+    task_id: Option<i32>,
+) -> std::io::Result<LatestTask> {
@@
-        fs::write(
+        fs::write(
@@
-        )
-        .expect(&format!(
-            "Failed to save file: \"{}\"",
-            self.get_latest_task_file_path()
-                .to_str()
-                .unwrap_or("<unable to print path>")
-        ));
-        latest_task
+        )?;
+        Ok(latest_task)

Upstream callers (e.g., task_prompt.rs) can then handle the Result.


70-82: ⚠️ Potential issue

Unprotected unwrap() may crash on corrupted JSON

If latest_task.json exists but contains malformed JSON, the .unwrap() on line 81 will panic, wiping out the whole application state.

-let data = match fs::read_to_string(self.get_latest_task_file_path()) {
+let data = match fs::read_to_string(self.get_latest_task_file_path()) {
@@
-serde_json::from_str(&data).unwrap()
+match serde_json::from_str::<LatestTask>(&data) {
+    Ok(t) => t,
+    Err(e) => {
+        log::error!("Failed to deserialize latest task: {}", e);
+        LatestTask {
+            task_id: None,
+            date_time_performed: Local::now(),
+        }
+    }
+}
src/widget/task_list_widget.rs (1)

113-118: Parsing type mismatch (u32 vs i32) persists
The enabled-check still uses parse::<u32>() while the actual update/add uses parse::<i32>(), recreating the range-overflow bug flagged in the previous review.
Please make both sides consistent—prefer i32 everywhere unless you explicitly want to reject negative numbers.

Also applies to: 169-171, 135-136, 179-180

src/task_list.rs (1)

22-30: Constructor still panics on DB errors (same as earlier feedback)
The call chain inside new eventually unwraps DB operations in change_date, so the constructor can panic. Returning Result<Self> would allow callers to handle failures gracefully.

Cargo.toml (1)

4-4: Rust edition 2024 is still nightly – building this crate on stable will fail unless the MSRV is explicitly nightly.
Use edition = "2021" until 2024 lands, or document the nightly requirement in README.md and CI.

src/model/task.rs (2)

14-19: last_used should be i64 to avoid Y2038 and cross-platform issues
This has been pointed out in an earlier review but the struct still uses
i32. Switching to a 64-bit signed integer is the conventional way to store
Unix timestamps safely.

-    pub last_used: i32,
+    pub last_used: i64,

51-53: ⚠️ Potential issue

i32 cast of Unix timestamp will overflow in 2038

chrono::Utc::now().timestamp() returns an i64. Casting to i32
silently truncates the high bits and will panic in debug builds once the value
exceeds i32::MAX (19-Jan-2038 UTC). Store the full i64 instead.

-            .set(task::last_used.eq(chrono::Utc::now().timestamp() as i32))
+            .set(task::last_used.eq(chrono::Utc::now().timestamp()))
🧹 Nitpick comments (25)
.env (1)

1-1: Consider using a template for environment variables

Committing a .env with real paths or credentials can be risky. It’s often better to track a .env.example in source control and add .env to .gitignore, so each developer can maintain their own local settings without leaking sensitive data.

docs/SetupGuide.md (1)

1-24: Enhance setup instructions with prerequisites

The guide covers running and generating migrations but omits key prerequisites:

  • Installing the Diesel CLI (e.g., cargo install diesel_cli --no-default-features --features sqlite)
  • Setting up the .env file (e.g., DATABASE_URL=database.db)
  • Ensuring Rust is installed (rustup, toolchain)

Consider adding a “Prerequisites” section before “Get Started” to cover these items, for example:

## Prerequisites

- Rust toolchain installed (`rustup`)
- Diesel CLI installed:
  ```bash
  cargo install diesel_cli --no-default-features --features sqlite
  • Create a .env file at the project root with:
    DATABASE_URL=database.db
This will help new contributors get up and running without guessing their environment setup.

</blockquote></details>
<details>
<summary>.gitignore (2)</summary><blockquote>

`4-5`: **Consider broader DB ignore patterns.**
You may want to ignore all SQLite files using `*.db` in case additional databases are generated, or confirm that only `database.db` needs to be excluded.

---

`13-14`: **Coverage report ignore.**
Excluding `tarpaulin-report.html` is aligned with CI outputs; consider ignoring the entire report directory if present.

</blockquote></details>
<details>
<summary>src/lib.rs (1)</summary><blockquote>

`10-12`: **Review import usage.**
While pulling in `diesel::prelude::*`, `MigrationHarness`, and `dotenv`, consider importing only the necessary traits and items to reduce namespace clutter.

</blockquote></details>
<details>
<summary>migrations/2024-10-16-222233_create_table_tasks/up.sql (2)</summary><blockquote>

`15-16`: **Fix comment formatting inconsistency.**

There's an extra dash at the beginning of this comment line that's inconsistent with other comment styles in the file.

```diff
--- Create an index on is_synced_to_server for faster queries for syncing
+-- Create an index on is_synced_to_server for faster queries for syncing

6-6: Consider explicit boolean storage for SQLite compatibility.

SQLite doesn't have a native BOOLEAN type - it uses INTEGER 0/1 internally. While this will work, explicitly defining it as INTEGER with a CHECK constraint would make the implementation more clear.

-        is_synced_to_server BOOLEAN NOT NULL DEFAULT FALSE
+        is_synced_to_server INTEGER NOT NULL DEFAULT 0 CHECK (is_synced_to_server IN (0, 1))
migrations/2024-10-16-223041_create_table_tasks_performed/up.sql (2)

17-18: Fix comment formatting inconsistency.

There's an extra dash at the beginning of this comment line that's inconsistent with other comment styles in the file.

--- Create an index on is_synced_to_server for faster queries for syncing
+-- Create an index on is_synced_to_server for faster queries for syncing

3-3: Consider adding a CHECK constraint for date format.

Since you're storing dates as TEXT in 'YYYY-MM-DD' format, consider adding a CHECK constraint to ensure the format is always valid.

-        date TEXT NOT NULL, -- Store dates as 'YYYY-MM-DD'
+        date TEXT NOT NULL CHECK (date LIKE '____-__-__' AND date IS date(date)), -- Store dates as 'YYYY-MM-DD'
src/bin/task_prompt_runner.rs (2)

5-6: Remove unnecessary comment.

The comment "it's an example" appears to be leftover from template code and doesn't provide useful information.

-// it's an example
 use eframe::egui;

30-30: Use a more descriptive app title.

"My egui App" seems like a generic placeholder. Consider using a more descriptive title that matches your application's purpose.

-        "My egui App",
+        "Task Prompt",
src/widget/task_prompt_widget.rs (1)

58-62: Prefer is_empty() over string comparison

self.task_select_widget.get_input_text() != "" allocates a temporary &str.
str::is_empty() is clearer and marginally cheaper:

-                                self.task_select_widget.get_input_text() != "",
+                                !self.task_select_widget.get_input_text().is_empty(),
src/main.rs (1)

141-163: Blocking run_simple_native inside async context stalls the Tokio executor

spawn_task_prompt and spawn_task_list are invoked from the async main loop but call the blocking eframe::run_simple_native, monopolising the async executor thread until the window closes.
You can prevent starvation by spawning these GUI functions onto a dedicated thread:

tokio::task::spawn_blocking(move || {
    spawn_task_prompt(con.clone(), settings_handle.clone());
});

This keeps the core runtime responsive to other async tasks (e.g. timers or network I/O).

src/widget/task_select_widget.rs (1)

78-83: println! inside the render loop increases latency

Printing on every click blocks the UI thread and spams stdout.
Prefer tracing::debug!, log::info!, or remove the statement entirely.

-                                println!("Clicked: {}", task_option);
+                                log::debug!("Task option clicked: {}", task_option);
src/task_prompt.rs (2)

44-49: Potential panic when elapsed time exceeds i32::MAX

num_minutes().try_into().unwrap() will panic on extremely long intervals.
Return a Result<i32> or clamp the value.

-        (Local::now() - self.latest_task_performed.date_time_performed)
-            .num_minutes()
-            .try_into()
-            .unwrap()
+        i32::try_from(
+            (Local::now() - self.latest_task_performed.date_time_performed).num_minutes()
+        ).unwrap_or(i32::MAX)

25-30: Unchecked unwrap() on mutex lock – may panic

If the lock is poisoned the entire constructor panics. Consider graceful error handling, e.g. returning Result<Self, TaskPromptError>.

src/sync_manager.rs (1)

88-106: HTTP status code too strict

The server might legitimately return 201 Created or 200 OK. Restricting success to 202 Accepted causes unnecessary OutOfSync states.

src/settings.rs (2)

34-37: Replace stdout print! with structured logging

print! writes directly to STDOUT and bypasses the log facade you are already using one line below.
Besides polluting console output, a missing trailing newline (println!) can also lead to interleaved log messages when several threads are active.

-print!("Creating Settings object with application_storage_path '{}'.",
-application_storage_path
-    .to_str()
-    .unwrap_or("<unable to print path>"));
+log::trace!(
+    "Creating Settings object with application_storage_path '{}'.",
+    application_storage_path
+        .to_str()
+        .unwrap_or("<unable to print path>")
+);

65-71: Error message references the wrong path constant

expect!() uses APPLICATION_STORAGE_PATH in the error message even though the directory being created is application_storage_path (the parameter).
If the caller passes a non-default directory, the panic message will mislead debugging efforts.

-            std::fs::create_dir_all(application_storage_path.as_path()).expect(&format!(
-                "Failed to create application save directory '{}'",
-                APPLICATION_STORAGE_PATH
+            std::fs::create_dir_all(application_storage_path.as_path()).expect(&format!(
+                "Failed to create application save directory '{}'",
+                application_storage_path
src/model/latest_task.rs (1)

29-33: Hold the settings lock only once

get_latest_task_file_path acquires the Mutex twice (once for the Path, once for join).
Holding the guard for the whole calculation avoids possible dead-locks if another thread tries to lock inside PathBuf::join chain and improves readability.

-Path::new(&self.settings.lock().unwrap().get_data_storage_path())
-    .join(LATEST_TASK_FILE_NAME)
+let base = self.settings.lock().unwrap();
+base.get_data_storage_path().join(LATEST_TASK_FILE_NAME)
src/model/task_performed.rs (1)

26-34: Docs claim to return Result but function returns Option

The rust-doc still says “A Result … or None otherwise”, which is misleading.

-/// A `Result` containing a `TaskPerformed` if found, or `None` otherwise.
+/// `Some(TaskPerformed)` if found, otherwise `None`.
src/task_list.rs (1)

32-55: Minimise lock contention by re-using the DB guard

change_date acquires self.db_connection.lock() repeatedly inside the iterator, leading to multiple mutex lock/unlock cycles.

-        let get_all_tasks_by_date = TaskPerformed::get_all_tasks_by_date(
-            &date.to_string(),
-            &mut self.db_connection.lock().unwrap(),
-        );
-        let mut task_for_date = get_all_tasks_by_date
-            .into_iter()
-            .map(|task_performed| TaskListItem {
-                task_name: Task::get_task_by_id(
-                    task_performed.task_id.clone(),
-                    &mut self.db_connection.lock().unwrap(),
-                )
+        let mut conn = self.db_connection.lock().unwrap();
+        let get_all_tasks_by_date =
+            TaskPerformed::get_all_tasks_by_date(&date.to_string(), &mut conn);
+        let mut task_for_date = get_all_tasks_by_date
+            .into_iter()
+            .map(|task_performed| TaskListItem {
+                task_name: Task::get_task_by_id(task_performed.task_id, &mut conn)

This keeps the mutex held once, improving performance and avoiding dead-lock surprises if the lock becomes re-entrant in the future.

src/task_prompt_manager.rs (2)

240-248: Flaky assertions: replace sleep polling with tokio::time::timeout

Using fixed sleep intervals makes the test suite slow and occasionally flaky
on loaded CI machines. Prefer timeout to wait up to the desired duration:

tokio::time::timeout(Duration::from_secs(1), async {
    while !manager
        .task_prompt_manager_join_handle
        .as_ref()
        .unwrap()
        .is_finished()
    {
        tokio::task::yield_now().await;
    }
})
.await
.expect("manager thread did not stop in time");

109-110: Broadcast happens under a double lock

state.lock() is taken immediately after it was released a few lines earlier,
only to clone the enum for broadcasting. Holding the lock while calling
broadcast_tx.send is unnecessary; clone first, then broadcast – this slightly
reduces contention.

-                let _ = broadcast_tx.send(state.lock().unwrap().clone());
+                let next = *state.lock().unwrap();
+                let _ = broadcast_tx.send(next);
src/model/task.rs (1)

112-122: Accept &[String] instead of &Vec<String>

Taking &Vec<T> prevents callers from passing a slice and needlessly ties the
API to a concrete container. A simple signature change improves flexibility
without affecting call-sites:

-    pub fn filter_all_matching_tasks<'a>(
-        task_names: &'a Vec<String>,
+    pub fn filter_all_matching_tasks<'a>(
+        task_names: &'a [String],
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 30c6a7c and 0509c9f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • .env (1 hunks)
  • .github/workflows/rust.yaml (1 hunks)
  • .gitignore (1 hunks)
  • .vscode/extensions.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • Cargo.toml (1 hunks)
  • README.md (1 hunks)
  • codecov.yml (1 hunks)
  • diesel.toml (1 hunks)
  • docs/SetupGuide.md (1 hunks)
  • migrations/2024-10-16-222233_create_table_tasks/down.sql (1 hunks)
  • migrations/2024-10-16-222233_create_table_tasks/up.sql (1 hunks)
  • migrations/2024-10-16-223041_create_table_tasks_performed/down.sql (1 hunks)
  • migrations/2024-10-16-223041_create_table_tasks_performed/up.sql (1 hunks)
  • src/bin/task_list_runner.rs (1 hunks)
  • src/bin/task_prompt_runner.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/model.rs (1 hunks)
  • src/model/latest_task.rs (1 hunks)
  • src/model/task.rs (1 hunks)
  • src/model/task_performed.rs (1 hunks)
  • src/schema.rs (1 hunks)
  • src/settings.rs (1 hunks)
  • src/sync_manager.rs (1 hunks)
  • src/task_list.rs (1 hunks)
  • src/task_prompt.rs (1 hunks)
  • src/task_prompt_manager.rs (1 hunks)
  • src/widget.rs (1 hunks)
  • src/widget/task_list_widget.rs (1 hunks)
  • src/widget/task_prompt_widget.rs (1 hunks)
  • src/widget/task_select_widget.rs (1 hunks)
  • tests/res/test_read_settings.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/lib.rs (4)
src/task_prompt_manager.rs (1)
  • settings (166-175)
src/model/latest_task.rs (1)
  • settings (129-138)
src/model/task_performed.rs (1)
  • connection (190-213)
src/model/task.rs (1)
  • connection (177-197)
src/bin/task_prompt_runner.rs (5)
src/model/latest_task.rs (2)
  • settings (129-138)
  • new (25-27)
src/settings.rs (1)
  • new (143-145)
src/task_prompt.rs (1)
  • new (21-42)
src/widget/task_prompt_widget.rs (1)
  • new (13-27)
src/lib.rs (1)
  • establish_connection (16-26)
🪛 YAMLlint (1.35.1)
codecov.yml

[error] 1-1: wrong new line character: expected \n

(new-lines)

.github/workflows/rust.yaml

[error] 1-1: wrong new line character: expected \n

(new-lines)

🔇 Additional comments (26)
.vscode/settings.json (1)

1-3: Custom spell-check configuration looks solid

The VSCode settings correctly whitelist domain-specific terms for cSpell, reducing noise from false-positive spell checks.

migrations/2024-10-16-223041_create_table_tasks_performed/down.sql (1)

2-2: Rollback script correctly uses IF EXISTS

The DROP TABLE IF EXISTS task_performed; statement ensures clean rollbacks even if the table is missing. This addresses idempotency and avoids errors during migration down.

tests/res/test_read_settings.json (1)

1-6: Test configuration is well-structured.

The JSON structure appears properly formatted with appropriate settings for testing the application's configuration functionality. The file includes task prompt delay timing, storage paths, and file paths that will be useful for validating the settings module behavior.

diesel.toml (2)

4-6: Configuration for schema generation looks good.

The Diesel schema configuration is properly set up to generate the schema in src/schema.rs with appropriate custom derives for QueryId and Clone.


8-9: Migrations directory configuration is correct.

The migrations directory is properly configured to point to the "migrations" folder, which aligns with the project structure visible in the PR.

.vscode/extensions.json (1)

2-7: Helpful extension recommendations for the project.

The recommended extensions are appropriate for a Rust project with SQL components:

  • Code spell checker for documentation quality
  • SQL formatting and SQLite editor extensions for database work
  • Rust-analyzer for Rust development support

This will help ensure consistent development environment across the team.

src/model.rs (1)

1-3: Good module organization for the data models.

The model structure is well-organized, separating concerns into logical modules for latest task tracking, base task model, and task performance records. This modularization will support clean separation of concerns.

src/widget.rs (1)

1-3: Verify module file existence.
The public modules task_list_widget, task_prompt_widget, and task_select_widget are correctly declared; please confirm that corresponding *.rs files or subdirectories exist under src/widget/ to avoid compilation errors.

README.md (2)

11-13: Local coverage instructions look good.
The cargo tarpaulin command with these flags is clear and comprehensive for generating local coverage reports.


15-17: CI coverage trigger is appropriate.
Guidance to include [ci] in commit messages aligns with the workflow’s conditional coverage job.

src/schema.rs (3)

3-10: Schema definition for task table is correct.
The columns (id, name, is_synced_to_server, last_used) and their types align with the SQL migration.


12-18: Schema definition for task_performed table is correct.
The composite primary key (date, task_id) matches the migration, and the column types are consistent.


21-24: Allow tables in same query.
The allow_tables_to_appear_in_same_query! directive is properly configured to enable joins between task and task_performed.

.gitignore (4)

1-2: Ignore Cargo build artifacts.
The /target directory exclusion is correct for Rust projects.


7-8: User data files exclusion.
Ignoring data/latest_task.json is appropriate for ephemeral user data; ensure this file isn’t required for versioned configuration.


10-11: Verify test directory exclusion.
Confirm that ignoring test/ won’t inadvertently exclude a tests/ directory used by Cargo for integration tests; refine pattern if necessary.


17-18: VSCode settings exclusion.
Ignoring .vscode prevents committing IDE-specific settings, which is appropriate.

src/lib.rs (3)

1-8: Verify module structure.
The pub mod declarations match the intended project layout; please ensure each module (sync_manager, model, schema, settings, task_list, task_prompt, task_prompt_manager, widget) has a corresponding source file or directory to avoid compilation errors.


14-14: Embed migrations correctly.
The embed_migrations!() macro usage is correct and will include all embedded migrations at compile time.


25-26: No issues with the return statement.

migrations/2024-10-16-222233_create_table_tasks/up.sql (1)

5-5: Good use of a default timestamp with STRFTIME.

Using a Unix timestamp format for last_used is a good choice for cross-platform compatibility and ease of sorting/comparison.

.github/workflows/rust.yaml (1)

1-1:

Details

✅ Verification successful

Fix newline character formatting.

The YAML file has inconsistent newline characters according to the static analysis.


🏁 Script executed:

#!/bin/bash
# Check line endings in the YAML file
file .github/workflows/rust.yaml
# Convert to LF line endings
sed -i 's/\r$//' .github/workflows/rust.yaml

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Verify if there are any CRLF line endings in the YAML file
grep -n $'\r' .github/workflows/rust.yaml || echo "✅ No CRLF line endings found"

Length of output: 110


No CRLF line endings detected – no action needed

The file .github/workflows/rust.yaml uses consistent LF line endings; the suggested newline formatting fix isn’t required.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 1-1: wrong new line character: expected \n

(new-lines)

src/bin/task_list_runner.rs (1)

50-54: Great use of CentralPanel to host the widget

The update implementation is clean and idiomatic; nothing to change here.

src/widget/task_prompt_widget.rs (1)

29-33: ⚠️ Potential issue

Return early after closing the window to prevent double updates

update_task_performed calls ViewportCommand::Close, but the caller continues rendering the remaining UI frame, risking a second invocation of this function during the same tick (e.g. when both did_select_option and the “Accept” button fire).
Add a short-circuit after closing to guarantee idempotency:

 fn update_task_performed(&mut self, ui: &mut egui::Ui) {
     self.task_prompt.task_name_option = self.task_select_widget.get_input_text().to_string();
     self.task_prompt.update_task();
     ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
+    // Prevent further UI processing in this frame
+    return;
 }

Alternatively, set a local flag and guard repeated execution.

Likely an incorrect or invalid review comment.

src/main.rs (1)

121-127: ⚠️ Potential issue

Potential panic: unwrap() on optional viewport minimised flag

viewport().minimized is Option<bool> and may be None on platforms that don’t expose window state.
Replace with a default to avoid cross-platform crashes:

-            let is_minimized = ui.ctx().input(|i| i.viewport().minimized).unwrap();
+            let is_minimized = ui
+                .ctx()
+                .input(|i| i.viewport().minimized)
+                .unwrap_or(false);

Likely an incorrect or invalid review comment.

src/task_list.rs (1)

171-178: Use the returned row to avoid stale data after upsert

insert_or_overwrite_task_performed may modify time_spent (e.g. triggers).
Discarding its return value and pushing the pre-insert new_task can leave tasks_for_date out of sync.

-        TaskPerformed::insert_or_overwrite_task_performed(&new_task, &mut connection).unwrap();
-        self.tasks_for_date.push(TaskListItem {
-            task_performed: new_task,
+        let persisted =
+            TaskPerformed::insert_or_overwrite_task_performed(&new_task, &mut connection).unwrap();
+        self.tasks_for_date.push(TaskListItem {
+            task_performed: persisted,
             task_name: task_name.to_string(),
         });

Comment thread codecov.yml
Comment on lines +6 to +7
threshold: 5% # the leniency in hitting the targetcomment:
comment:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix YAML syntax: separate comment: key from inline comment

The line merging the comment: key into the threshold comment will break YAML parsing and prevent Codecov from picking up your settings. Split the inline comment and ensure comment: starts on its own line at the root level.

Proposed fix:

@@ -5,3 +5,4
-        threshold: 5% # the leniency in hitting the targetcomment:
+        threshold: 5% # the leniency in hitting the target
+comment:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
threshold: 5% # the leniency in hitting the targetcomment:
comment:
threshold: 5% # the leniency in hitting the target
comment:

@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE ID EXISTS task;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

SQL syntax error in DROP TABLE statement.

There's a syntax error in the SQL command. ID EXISTS should be IF EXISTS.

-DROP TABLE ID EXISTS task;
+DROP TABLE IF EXISTS task;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DROP TABLE ID EXISTS task;
DROP TABLE IF EXISTS task;

Comment thread src/lib.rs
Comment on lines +16 to +19
pub fn establish_connection(database_url: &str) -> SqliteConnection {
dotenv().ok();

// let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Propagate errors instead of panicking.
The signature -> SqliteConnection enforces panicking on failure. Consider returning Result<SqliteConnection, diesel::ConnectionError> (or a custom error type) and using ? for establish and migrations to avoid unexpected panics in consuming code.

time_spent INTEGER NOT NULL DEFAULT 0, -- Set default to 0
is_synced_to_server BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (date, task_id),
FOREIGN KEY (task_id) REFERENCES Task (id) ON DELETE CASCADE
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix table reference capitalization in foreign key constraint.

The referenced table name Task should be lowercase task to match the actual table name defined in the previous migration. SQLite is case-sensitive for identifiers.

-        FOREIGN KEY (task_id) REFERENCES Task (id) ON DELETE CASCADE
+        FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FOREIGN KEY (task_id) REFERENCES Task (id) ON DELETE CASCADE
FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE

Comment on lines +22 to +24
Arc::new(Mutex::new(kronos::establish_connection(
settings.get_database_file_path().to_str().unwrap(),
))),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling for database path conversion.

The .unwrap() call on the result of .to_str() will panic if the path contains non-UTF8 characters. Consider proper error handling.

-            settings.get_database_file_path().to_str().unwrap(),
+            settings.get_database_file_path().to_str()
+                .unwrap_or_else(|| panic!("Database path contains invalid UTF-8 characters")),

Alternatively, consider using the Display formatter for paths:

-            settings.get_database_file_path().to_str().unwrap(),
+            settings.get_database_file_path().display().to_string(),

Comment thread src/task_list.rs
Comment on lines +61 to +96
pub fn add_task(&mut self, task_name: &str, time_spent: i32) {
let mut connection = self.db_connection.lock().unwrap();

let task = Task::get_or_create_task(task_name, &mut connection)
.expect("Failed to get or create task");

let task_performed_index = self
.tasks_for_date
.iter()
.position(|task_list_item| task_list_item.task_name == task_name);

let task_performed = match task_performed_index {
Some(task_performed_index) => {
// Remove and extract the old TaskListItem from the tasks_for_date
let mut task_performed = self
.tasks_for_date
.swap_remove(task_performed_index)
.task_performed;
// Update the total time spent
task_performed.time_spent += time_spent;
TaskPerformed::update_task_performed(&task_performed, &mut connection)
.expect("todo")
}
None => {
TaskPerformed::insert_task_performed(
&TaskPerformed {
date: self.date.to_string(),
task_id: task.id,
time_spent,
},
&mut connection,
)
.expect("todo") // TODO this should probably return the error
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

add_task panics on every DB error

All DB operations in this method use expect("todo") / unwrap, which will crash the GUI on the first failure.

Expose the error instead:

pub fn add_task(&mut self, task_name: &str, time_spent: i32) -> Result<(), diesel::result::Error> {
    let mut connection = self.db_connection.lock().unwrap();
    let task = Task::get_or_create_task(task_name, &mut connection)?;self.tasks_for_date.push(TaskListItem {});
    Ok(())
}

Propagate the error upward and show a user-friendly dialog at the UI layer.

Comment on lines +69 to +74
let task_prompt_manager_run_handle = thread::spawn(move || {
let mut rx = rx.lock().unwrap(); // Lock the receiver
loop {
let current_state = state.lock().unwrap();

match *current_state {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Do not hold the rx mutex for the whole lifetime of the loop

let mut rx = rx.lock().unwrap(); is executed before the loop and therefore
the MutexGuard is held for the entire lifetime of the manager thread.
While no other code path currently tries to lock rx, this makes the design brittle
and violates the “lock as short as possible” rule – any future attempt to access
rx (e.g. in start() for graceful-shutdown logic) will dead-lock.

Move the lock inside the loop or, even better, drop the outer Mutex entirely
and give the spawned thread ownership of the Receiver.

-            let mut rx = rx.lock().unwrap(); // Lock the receiver
-            loop {
+            let mut rx = rx.lock().unwrap();
+            loop {

➡ Suggested rewrite:

let mut rx = rx;          // take ownership, no mutex needed
loop {
    tokio::select! {
        Some(new_state) = rx.recv() => {}
        // …
    }
}

Comment on lines +51 to +56
pub fn start(&mut self) {
if let Some(handle) = &self.task_prompt_manager_join_handle {
if !handle.is_finished() {
return;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up finished thread before re-spawning a new one

start() silently discards a finished JoinHandle when it wants to spawn a new manager thread.
If the previous thread panicked the panic will never be propagated, and the OS resources
associated with the handle are leaked until the process exits.

-        if let Some(handle) = &self.task_prompt_manager_join_handle {
-            if !handle.is_finished() {
-                return;
-            }
-        }
+        if let Some(handle) = self.task_prompt_manager_join_handle.take() {
+            // Propagate any panic that happened inside the thread
+            if let Err(e) = handle.join() {
+                eprintln!("TaskPromptManager thread terminated with panic: {:?}", e);
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn start(&mut self) {
if let Some(handle) = &self.task_prompt_manager_join_handle {
if !handle.is_finished() {
return;
}
}
pub fn start(&mut self) {
if let Some(handle) = self.task_prompt_manager_join_handle.take() {
// Propagate any panic that happened inside the thread
if let Err(e) = handle.join() {
eprintln!("TaskPromptManager thread terminated with panic: {:?}", e);
}
}
// ... now spawn or re-spawn your manager thread ...

Comment on lines +118 to +139
// TODO refactor
*self.state.lock().unwrap() = TaskPromptManagerState::Stopped;

let sender = self.tx.clone();

if let Some(handle) = self.task_prompt_manager_join_handle.take() {
thread::spawn(|| {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();

runtime.spawn_blocking(move || {
sender
.blocking_send(TaskPromptManagerState::Stopped)
.unwrap()
});
})
.join()
.unwrap();

handle.join().unwrap();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Drop implementation is over-engineered and may panic

  1. Spawning a helper thread that creates another Tokio runtime just to call
    blocking_send is unnecessary – blocking_send works perfectly from the
    current thread.
  2. unwrap() on blocking_send will panic when the channel is already closed,
    potentially during unwinding → double-panic = abort.
  3. The panic in the helper thread will be swallowed because the join() result
    is unwrapped.

A straightforward, panic-free alternative:

-        let sender = self.tx.clone();
-        if let Some(handle) = self.task_prompt_manager_join_handle.take() {
-            thread::spawn(|| {
-                let runtime = tokio::runtime::Builder::new_current_thread()
-                    .build()
-                    .unwrap();
-                runtime.spawn_blocking(move || {
-                    sender
-                        .blocking_send(TaskPromptManagerState::Stopped)
-                        .unwrap()
-                });
-            })
-            .join()
-            .unwrap();
-            handle.join().unwrap();
-        }
+        // Best-effort shutdown signal; ignore error if receiver side is gone
+        let _ = self.tx.blocking_send(TaskPromptManagerState::Stopped);
+
+        if let Some(handle) = self.task_prompt_manager_join_handle.take() {
+            let _ = handle.join(); // swallow panic, we are in Drop
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO refactor
*self.state.lock().unwrap() = TaskPromptManagerState::Stopped;
let sender = self.tx.clone();
if let Some(handle) = self.task_prompt_manager_join_handle.take() {
thread::spawn(|| {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
runtime.spawn_blocking(move || {
sender
.blocking_send(TaskPromptManagerState::Stopped)
.unwrap()
});
})
.join()
.unwrap();
handle.join().unwrap();
}
// TODO refactor
*self.state.lock().unwrap() = TaskPromptManagerState::Stopped;
// Best-effort shutdown signal; ignore error if receiver side is gone
let _ = self.tx.blocking_send(TaskPromptManagerState::Stopped);
if let Some(handle) = self.task_prompt_manager_join_handle.take() {
let _ = handle.join(); // swallow panic, we are in Drop
}

Comment thread src/model/task.rs
Comment on lines +124 to +135
//TODO call this on delete_task_performed or on startup
pub fn delete_unused_tasks(connection: &mut SqliteConnection) -> Result<usize, Error> {
diesel::delete(task::table)
.filter(
task::id.ne_all(
task_performed::table
.select(task_performed::task_id)
.filter(task_performed::task_id.eq(task::id)),
),
)
.execute(connection)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

delete_unused_tasks sub-query can be simplified

Using ne_all with a correlated sub-query ends up translating to
WHERE NOT (id = ANY (SELECT …)), which is harder for SQLite’s query planner.
A more idiomatic and performant pattern is WHERE NOT EXISTS:

-        diesel::delete(task::table)
-            .filter(
-                task::id.ne_all(
-                    task_performed::table
-                        .select(task_performed::task_id)
-                        .filter(task_performed::task_id.eq(task::id)),
-                ),
-            )
+        diesel::delete(task::table)
+            .filter(diesel::dsl::not(diesel::dsl::exists(
+                task_performed::table
+                    .filter(task_performed::task_id.eq(task::id))
+            )))

This yields the same result set with a simpler execution plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant