Skip to main content

max / balanced_breakfast

Bring CONTRIBUTING and the architecture doc in step with the sync repositories The migration left three code samples describing an API that no longer exists: an awaited `db.items().list()`, an `async fn setup` and a `database_url` that is now a path. The lock-discipline section also had only half the rule -- the database half is the compiler's now, since a rusqlite Connection is not Sync and a Tauri command's future must be Send. Also fixes a path that predates this work: repositories are `repository/`, not `repository.rs`.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-08 01:22 UTC
Signed with PGP, not checked
Commit: 50a1993a2f4beeeffd7817cb4e5f866039d5b40d
Parent: 6752d21
2 files changed, +18 insertions, -10 deletions
M CONTRIBUTING.md +17 -9
@@ -50,18 +50,24 @@
50 50 - Secret encryption for plugin configs
51 51 - Circuit breaker enforcement (10 consecutive failures disables auto-fetch)
52 52
53 - **Lock discipline:** Hold the `PluginManager` lock only during synchronous operations. Release before any `.await`:
53 + **Lock discipline:** Hold the `PluginManager` lock only for as long as you need it, and never across an `.await`:
54 54
55 55 ```rust
56 - // GOOD: Release lock before async DB ops
56 + // GOOD: scope the guard, then do the slow thing outside it
57 57 let fetch_result = {
58 58 let plugins = self.plugins.read().await;
59 59 plugins.fetch(plugin_id, None)
60 60 };
61 - // Lock released here, now safe to await
62 - self.db.feeds().record_fetch_success(feed_id).await?;
61 + // Lock released here.
62 + self.db.feeds().record_fetch_success(feed_id)?;
63 63 ```
64 64
65 + The same rule applies in the other direction to database connections, and there
66 + it is enforced by the compiler: a rusqlite `Connection` is not `Sync`, and a
67 + Tauri command's future must be `Send`, so holding one across an `.await` is a
68 + compile error rather than a latent stall. Scope the connection in a block. See
69 + `Database::conn` for the related "one connection at a time" rule.
70 +
65 71 ## Plugin System
66 72
67 73 ### Contract: 4 Required Functions
@@ -101,7 +107,7 @@
101 107 filter: ItemFilter,
102 108 ) -> Result<Vec<ItemSnapshot>, ApiError> {
103 109 let db = state.orchestrator.database();
104 - let items = db.items().list(filter).await?;
110 + let items = db.items().list(filter)?;
105 111 Ok(items.into_iter().map(ItemSnapshot::from).collect())
106 112 }
107 113 ```
@@ -130,6 +136,8 @@
130 136 }
131 137 ```
132 138
139 + The repositories in `bb-db` are synchronous: SQLite has no async API, and the async this layer used to wear came from sqlx. Call them directly; do not wrap them in `async fn`.
140 +
133 141 `ApiError` implements `From` for `bb_db::DbError`, `rusqlite::Error`, `FeedError`, and `OrchestratorError`. The frontend receives `{code: "BAD_REQUEST", message: "..."}`.
134 142
135 143 ## Circuit Breaker
@@ -214,14 +222,14 @@
214 222
215 223 ```rust
216 224 // Common setup
217 - pub async fn setup(suffix: &str) -> Orchestrator {
225 + pub fn setup(suffix: &str) -> Orchestrator {
218 226 let config = OrchestratorConfig {
219 - database_url: "sqlite::memory:".to_string(),
227 + database_path: ":memory:".to_string(),
220 228 plugins_dir: temp_dir.to_string(),
221 229 fetch_interval_secs: 300,
222 230 };
223 - let orchestrator = Orchestrator::new(config).await.unwrap();
224 - orchestrator.migrate().await.unwrap();
231 + let orchestrator = Orchestrator::new(config).unwrap();
232 + orchestrator.migrate().unwrap();
225 233 orchestrator
226 234 }
227 235 ```
@@ -206,7 +206,7 @@
206 206 | Feed generator | `crates/bb-feed/src/generator.rs` |
207 207 | Ordering/filtering | `crates/bb-feed/src/ordering.rs` |
208 208 | Database layer | `crates/bb-db/src/` |
209 - | Repositories | `crates/bb-db/src/repository.rs` |
209 + | Repositories | `crates/bb-db/src/repository/` |
210 210 | Migrations | `migrations/sqlite/` (001-010) |
211 211 | Tauri app state | `src-tauri/src/state.rs` |
212 212 | Tauri commands | `src-tauri/src/commands/` |