Skip to main content

max / balanced_breakfast

v0.3.1 — Mobile/touch support, concurrent feed fetching, sync scheduler hardening Add mobile viewport and touch gesture modules (mobile.js, touch.js) with swipe navigation and responsive CSS. Switch feed fetching to concurrent via futures::join_all in orchestrator and Rhai plugin host. Harden sync scheduler with backoff, jitter, and forced full-sync fallback. Improve crypto error handling with tracing::warn on encrypt/decrypt failures. Add open crate for cross-platform link opening. Introduce open_in_browser Tauri command. Update feed ordering with tie-breaking by title. Clean up completed items from todo docs and remove archived done list.
Co-Authored-By
Claude Opus 4.6 <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-04-13 22:18 UTC
Commit: 8b8e3f9966ea76ccc8b0f669463c675b264714d0
Parent: 319bc1e
30 files changed, +1122 insertions, -617 deletions
M Cargo.lock +28 -8
@@ -190,6 +190,8 @@
190 190 "bb-feed",
191 191 "bb-interface",
192 192 "chrono",
193 + "futures",
194 + "open",
193 195 "parking_lot",
194 196 "rand 0.8.5",
195 197 "roxmltree",
@@ -243,6 +245,7 @@
243 245 "bb-interface",
244 246 "chrono",
245 247 "docengine",
248 + "futures",
246 249 "html2text",
247 250 "keyring",
248 251 "parking_lot",
@@ -920,7 +923,7 @@
920 923 "libc",
921 924 "option-ext",
922 925 "redox_users",
923 - "windows-sys 0.59.0",
926 + "windows-sys 0.61.2",
924 927 ]
925 928
926 929 [[package]]
@@ -1088,7 +1091,7 @@
1088 1091 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1089 1092 dependencies = [
1090 1093 "libc",
1091 - "windows-sys 0.59.0",
1094 + "windows-sys 0.61.2",
1092 1095 ]
1093 1096
1094 1097 [[package]]
@@ -1249,6 +1252,21 @@
1249 1252 "new_debug_unreachable",
1250 1253 ]
1251 1254
1255 + [[package]]
1256 + name = "futures"
1257 + version = "0.3.31"
1258 + source = "registry+https://github.com/rust-lang/crates.io-index"
1259 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
1260 + dependencies = [
1261 + "futures-channel",
1262 + "futures-core",
1263 + "futures-executor",
1264 + "futures-io",
1265 + "futures-sink",
1266 + "futures-task",
1267 + "futures-util",
1268 + ]
1269 +
1252 1270 [[package]]
1253 1271 name = "futures-channel"
1254 1272 version = "0.3.31"
@@ -1322,6 +1340,7 @@
1322 1340 source = "registry+https://github.com/rust-lang/crates.io-index"
1323 1341 checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
1324 1342 dependencies = [
1343 + "futures-channel",
1325 1344 "futures-core",
1326 1345 "futures-io",
1327 1346 "futures-macro",
@@ -2641,7 +2660,7 @@
2641 2660 source = "registry+https://github.com/rust-lang/crates.io-index"
2642 2661 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
2643 2662 dependencies = [
2644 - "windows-sys 0.59.0",
2663 + "windows-sys 0.61.2",
2645 2664 ]
2646 2665
2647 2666 [[package]]
@@ -3026,7 +3045,7 @@
3026 3045 checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
3027 3046 dependencies = [
3028 3047 "libc",
3029 - "windows-sys 0.45.0",
3048 + "windows-sys 0.61.2",
3030 3049 ]
3031 3050
3032 3051 [[package]]
@@ -3885,7 +3904,7 @@
3885 3904 "errno",
3886 3905 "libc",
3887 3906 "linux-raw-sys",
3888 - "windows-sys 0.59.0",
3907 + "windows-sys 0.61.2",
3889 3908 ]
3890 3909
3891 3910 [[package]]
@@ -3942,7 +3961,7 @@
3942 3961 "security-framework",
3943 3962 "security-framework-sys",
3944 3963 "webpki-root-certs",
3945 - "windows-sys 0.59.0",
3964 + "windows-sys 0.61.2",
3946 3965 ]
3947 3966
3948 3967 [[package]]
@@ -4832,6 +4851,7 @@
4832 4851 "serde_json",
4833 4852 "thiserror 2.0.18",
4834 4853 "tokio",
4854 + "tokio-stream",
4835 4855 "tracing",
4836 4856 "unicode-normalization",
4837 4857 "urlencoding",
@@ -5307,7 +5327,7 @@
5307 5327 "getrandom 0.3.4",
5308 5328 "once_cell",
5309 5329 "rustix",
5310 - "windows-sys 0.59.0",
5330 + "windows-sys 0.61.2",
5311 5331 ]
5312 5332
5313 5333 [[package]]
@@ -6285,7 +6305,7 @@
6285 6305 source = "registry+https://github.com/rust-lang/crates.io-index"
6286 6306 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
6287 6307 dependencies = [
6288 - "windows-sys 0.48.0",
6308 + "windows-sys 0.61.2",
6289 6309 ]
6290 6310
6291 6311 [[package]]
M Cargo.toml +5 -1
@@ -10,7 +10,7 @@
10 10 default-members = ["src-tauri"]
11 11
12 12 [workspace.package]
13 - version = "0.3.0"
13 + version = "0.3.1"
14 14 edition = "2021"
15 15 authors = ["BalancedBreakfast Contributors"]
16 16 license-file = "LICENSE"
@@ -40,6 +40,10 @@
40 40
41 41 # Concurrency
42 42 parking_lot = "0.12"
43 + futures = "0.3"
44 +
45 + # Cross-platform file opener
46 + open = "5"
43 47
44 48 # Utilities
45 49 chrono = { version = "0.4.43", features = ["serde"] }
@@ -168,7 +168,6 @@
168 168 | Document | Status | Last Verified | Notes |
169 169 |----------|:------:|:-------------:|-------|
170 170 | docs/apps/bb/todo.md | Current | 2026-03-11 | Updated with audit 5 action items |
171 - | docs/apps/bb/todo_done.md | Current | 2026-03-04 | Completed items archive |
172 171 | docs/apps/bb/description.md | Placeholder | 2026-03-04 | Intentional placeholder |
173 172 | docs/apps/bb/competition.md | Current | 2026-03-04 | Competitive analysis |
174 173 | docs/apps/bb/structural_metrics.md | New | 2026-03-11 | Structural metrics from audit 5 |
M docs/todo.md +2 -48
@@ -1,58 +1,14 @@
1 1 # Balanced Breakfast TODO
2 2
3 3 ## Status
4 - Done: All pre-beta phases, UX polish (Phase 8). Active: None. Next: Post-beta features.
4 + Done: All pre-beta phases. Active: None. Next: Post-beta features.
5 5
6 - v0.3.0. Audit grade A. 10 bundled plugins. Rhai sandbox hardened.
7 -
8 - **Scope:** Pre-beta coding complete. All remaining sections are post-beta.
9 -
10 - Completed work archived in `docs/archive/bb_todo_done.md`.
11 -
12 - ---
13 -
14 - ## Phase 8: UX Polish
15 -
16 - Addresses non-STRONG grades from the UX audit (`_meta/uxaudit.md`, 2026-04-04).
17 -
18 - ### 8A: Active User — Sources Sidebar Empty State
19 - First-time users see an empty sidebar with no guidance.
20 -
21 - - [x] In `sources.js` `render()`, detect when `sources.length === 0` and show onboarding message
22 - - [x] Message: cooking pot emoji + "Add your first feed to get started" with pointer to Add Feed button
23 - - [x] Remove onboarding when first source is added (reactive via `BB.state.subscribe('sources')`)
24 -
25 - ### 8B: Goal-Gradient — Positive Unread Completion State
26 - When a source reaches 0 unread, show a positive visual cue instead of just removing the count.
27 -
28 - - [x] In `sources.js`, when `source.unreadCount === 0` and `source.totalCount > 0`, render checkmark icon
29 - - [x] CSS: `.source-count.all-read` with checkmark glyph + muted green color
30 - - [x] "All" aggregate: when `totalUnread === 0`, show checkmark on the All row too
31 -
32 - ### 8C: Von Restorff — Stronger Selected-Item Highlight
33 - Currently selected item uses `background-color: var(--bg-tertiary)` which is subtle.
34 -
35 - - [x] Add left border accent to `.item.selected`: `border-left-color: var(--yolk)`
36 - - [x] Ensure selected + unread combination is visually distinct (darker accent background)
37 -
38 - ### 8D: Peak-End Rule — Session Summary
39 - Give users a sense of accomplishment about their reading session.
40 -
41 - - [x] Track session stats in `BB.state`: `sessionArticlesRead`, `sessionArticlesStarred`
42 - - [x] On app close (`beforeunload`), show brief toast: "This session: N read, M starred"
43 - - [x] Reset counters on app launch (initialized to 0 in state defaults)
44 -
45 - ### 8E: Fitts's Law — Wider Star Toggle Target
46 - Star button is icon-only (~18px) with no padding.
47 -
48 - - [x] Expand `.star-btn` padding: `0.25rem 0.4rem` with negative margin to maintain layout
6 + v0.3.0. Audit grade A. 602 tests.
49 7
50 8 ---
51 9
52 10 ## Phase 7: Plugin OAuth (Post-beta)
53 11
54 - Authenticated plugin feeds — users log into services via OAuth to access personalized content.
55 -
56 12 ### 7A: OAuth Infrastructure
57 13 - [ ] New host functions: `oauth_start(provider, scopes)`, `oauth_token(provider)`, `oauth_refresh(provider)`
58 14 - [ ] Tauri deep-link handler for OAuth callback URLs
@@ -69,8 +25,6 @@
69 25 - [ ] Dev.to — reading list, followed tags
70 26
71 27 ## Phase 4: Plugin Store (Long-term)
72 -
73 - Community plugin marketplace. Prerequisites: plugin sandboxing, plugin authoring guide.
74 28 - [ ] 4A: Plugin registry backend (manifest format, index, submission flow, CI validation)
75 29 - [ ] 4B: Client-side store UI (browse, search, install, update, uninstall)
76 30 - [ ] 4C: Publishing from the app
@@ -1,187 +1,12 @@
1 1 # Balanced Breakfast - Mobile Port
2 2
3 - Tauri 2 iOS/Android port. CSS-first responsive design with touch gestures. Based on GoingsOn mobile patterns.
4 -
5 - **What stays the same:**
6 - - `crates/bb-core/` — orchestrator, plugin manager, unchanged
7 - - `crates/bb-db/` — SQLite repository, unchanged
8 - - `crates/bb-interface/` — plugin types, unchanged
9 - - `crates/bb-feed/` — aggregation, unchanged
10 - - `src-tauri/src/commands/` — Tauri commands, unchanged
11 - - `src-tauri/frontend/js/` — all JS modules (minor mobile branches)
12 - - `BB.api` with `invoke()` — same IPC mechanism
13 -
14 - **What changes:** CSS responsiveness, new touch/mobile JS modules, bottom tab bar, single-column layout, Tauri mobile config.
3 + Done: Phases 1-5, Phase 6 partial. Active: None. Next: Phase 6 remaining, Phase 7 polish.
15 4
16 5 ---
17 6
18 - ## Design: Three-Panel → Single-Column
19 -
20 - **Desktop:** Sidebar (sources) | Items (list) | Detail (article) — all visible simultaneously.
21 -
22 - **Mobile (<=768px):** One panel at a time with drill-down navigation.
23 -
24 - ### Bottom Tab Bar
25 -
26 - | Tab | Default View | Description |
27 - |-----|-------------|-------------|
28 - | Feed | Items list (All) | Current feed items, sorted by selected order |
29 - | Saved | Starred items | Saved/starred articles |
30 - | Sources | Source list | Feed sources with unread counts, tap to filter |
31 - | + | Add Feed | Opens add-feed modal |
32 - | More | Popover | Settings, Sync, OPML, Shortcuts |
33 -
34 - ### Navigation Flow
35 -
36 - ```
37 - Sources tab → tap source → Feed tab (filtered to that source)
38 - Feed tab → tap item → Detail view (slides in full-screen)
39 - Detail view → back button / swipe-right → Items list
40 - ```
41 -
42 - ### Panel Visibility Rules
43 -
44 - | State | Sidebar | Items | Detail | Tab Bar |
45 - |-------|---------|-------|--------|---------|
46 - | Feed tab | hidden | visible | hidden | visible |
47 - | Saved tab | hidden | visible (starred) | hidden | visible |
48 - | Sources tab | visible (full-width) | hidden | hidden | visible |
49 - | Reading article | hidden | hidden | visible | hidden |
50 -
51 - ---
52 -
53 - ## Phase 1: CSS Foundation
54 -
55 - ### Layout
56 - - [ ] `viewport-fit=cover` meta tag
57 - - [ ] `@media (max-width: 768px)` master breakpoint (replace existing 768px + 600px rules)
58 - - [ ] Hide header (`display: none`) — search/sort/actions move to appropriate locations
59 - - [ ] Body padding: `env(safe-area-inset-top)` top, `calc(52px + env(safe-area-inset-bottom))` bottom
60 - - [ ] Sidebar: `display: none` by default, full-width when `.mobile-sources-visible`
61 - - [ ] Items panel: full-width, single column
62 - - [ ] Detail panel: fixed full-screen overlay when `.mobile-detail-open`
63 - - [ ] Remove all three-panel flex layout on mobile (`.main` → single column)
64 -
65 - ### Components
66 - - [ ] Modal → bottom sheet (full-width, rounded top corners, drag handle)
67 - - [ ] Toasts positioned above tab bar
68 - - [ ] `@media (hover: none)` disable hover effects on touch devices
69 - - [ ] Item cards: increase tap target size, larger star button
70 - - [ ] Source list items: full-width rows, larger tap targets
71 -
72 - ### Mobile Search
73 - - [ ] Search bar at top of Feed/Saved views (replaces header search)
74 - - [ ] Sort dropdown integrated into search bar area or as a pill row
75 -
76 - ---
77 -
78 - ## Phase 2: Bottom Tab Bar
79 -
80 - ### HTML
81 - - [ ] `<nav id="mobile-tab-bar" class="mobile-tab-bar">` before closing `</div>` of `#app`
82 - - [ ] 5 buttons: Feed, Saved, Sources, +, More
83 - - [ ] More popover: Settings, Sync, OPML Import/Export, Shortcuts
84 -
85 - ### CSS
86 - - [ ] Base styles: `display: none`, fixed bottom, z-index 1100, safe-area padding
87 - - [ ] Show at 768px: `display: flex`
88 - - [ ] Active tab highlight (accent color)
89 - - [ ] More popover positioned above tab bar
90 -
91 - ### JS (`navigation.js` — new file)
92 - - [ ] `initMobileTabBar()` — click handlers for each tab
93 - - [ ] Feed tab: show items panel, set source filter to current/All
94 - - [ ] Saved tab: show items panel, filter to starred
95 - - [ ] Sources tab: show sidebar full-width
96 - - [ ] + button: open add-feed modal
97 - - [ ] More button: toggle popover
98 - - [ ] `updateMobileTabBar(view)` — sync active tab state
99 - - [ ] Source tap handler: switch to Feed tab filtered to that source
100 -
101 - ---
102 -
103 - ## Phase 3: Detail View Navigation
104 -
105 - ### Back Button
106 - - [ ] Detail header: add back arrow button (visible only on mobile)
107 - - [ ] Back button returns to items list, closes detail panel
108 - - [ ] Update `BB.detail` to track mobile open/close state
109 -
110 - ### Transitions
111 - - [ ] Detail slides in from right (CSS transform + transition)
112 - - [ ] Items list slides out left (or stays, detail overlays)
113 - - [ ] Tab bar hidden when detail is open (full reading experience)
114 -
115 - ### Reader State
116 - - [ ] `BB.state.mobileDetailOpen` flag
117 - - [ ] On item select (mobile): set flag, show detail full-screen
118 - - [ ] On back: clear flag, return to items
119 -
120 - ---
121 -
122 - ## Phase 4: Touch Module (`js/touch.js` — new file)
123 -
124 - Port from GoingsOn's touch.js, adapted for BB's domain.
125 -
126 - ### Utilities
127 - - [ ] `isTouchDevice` detection
128 - - [ ] `addLongPress(element, callback, duration)` — 500ms hold
129 - - [ ] `addSwipeActions(element, config)` — reveal action behind item
130 - - [ ] `addPullToRefresh(container, callback)` — pull indicator + callback
131 - - [ ] `addDragToDismiss(element, onDismiss)` — swipe down to close
132 - - [ ] All functions return cleanup functions, no-op on non-touch
133 -
134 - ### Mobile Wiring (`js/mobile.js` — new file)
135 - - [ ] Items: swipe right = toggle star, swipe left = toggle read
136 - - [ ] Pull-to-refresh on items list → `BB.feeds.refreshAll()`
137 - - [ ] Pull-to-refresh on sources list → `BB.feeds.refreshAll()`
138 - - [ ] Long-press on item → show action sheet (star, read/unread, open in browser, save)
139 - - [ ] Detail view: swipe right → back to items
140 - - [ ] Modal swipe-to-dismiss wiring
141 -
142 - ---
143 -
144 - ## Phase 5: View Adaptations
145 -
146 - ### Items List (mobile)
147 - - [ ] Item rows: larger padding, bigger star toggle target
148 - - [ ] Unread indicator: bold title + accent left border (like GO task priority borders)
149 - - [ ] Source name badge on items (needed since sidebar isn't visible in Feed tab)
150 - - [ ] Inline sort pills above items (Newest / Score / Unread / Starred)
151 -
152 - ### Sources List (mobile)
153 - - [ ] Full-width card layout with unread counts
154 - - [ ] All/Saved as top sticky rows
155 - - [ ] Tag filter bar: horizontal scroll pills (like GO pill nav)
156 - - [ ] Edit/delete actions via long-press action sheet
157 -
158 - ### Detail View (mobile)
159 - - [ ] Full-screen reader with comfortable margins
160 - - [ ] Sticky header: back button + title + star + open-in-browser
161 - - [ ] Safe area padding top and bottom
162 - - [ ] Article body scrollable with momentum
163 -
164 - ### Keyboard
165 - - [ ] Disable keyboard shortcuts on touch devices
166 -
167 - ---
168 -
169 - ## Phase 6: Tauri Mobile Build Config
170 -
171 - ### Cargo.toml
172 - - [ ] Desktop-only deps gated: `tauri-plugin-shell`, `tauri-plugin-notification`, `tauri-plugin-window-state`
173 - - [ ] `crate-type = ["staticlib", "cdylib", "lib"]` for iOS
7 + ## Phase 6: Tauri Mobile Build Config — Remaining
174 8 - [ ] Platform-conditional `rusqlite` bundled feature for Android
175 -
176 - ### Source Gating
177 - - [ ] `lib.rs`: mobile entry point (`build_mobile_app()` + `#[cfg(mobile)]`)
178 - - [ ] Desktop-only plugin init gated in `main.rs`
179 - - [ ] Desktop-only window commands gated
180 -
181 - ### Capabilities
182 - - [ ] Split: `default.json` (cross-platform) + `desktop.json` (shell, notifications)
183 -
184 - ### Build & Test
9 + - [ ] Split capabilities: `default.json` (cross-platform) + `desktop.json` (shell, notifications)
185 10 - [ ] `cargo tauri ios init` → generates `gen/apple/`
186 11 - [ ] `cargo tauri android init` → generates `gen/android/`
187 12 - [ ] iOS simulator test
@@ -191,7 +16,7 @@
191 16 ---
192 17
193 18 ## Phase 7: Polish
194 -
19 + - [ ] Inline sort pills above items
195 20 - [ ] Physical device testing (iOS + Android)
196 21 - [ ] Safe area insets on various device models
197 22 - [ ] VoiceOver / TalkBack accessibility
@@ -207,25 +32,8 @@
207 32 | File | Role |
208 33 |------|------|
209 34 | `src-tauri/frontend/css/styles.css` | All responsive CSS |
210 - | `src-tauri/frontend/js/touch.js` | Gesture utilities (new) |
211 - | `src-tauri/frontend/js/mobile.js` | Mobile interaction wiring (new) |
212 - | `src-tauri/frontend/js/navigation.js` | Bottom tab bar + view switching (new) |
213 - | `src-tauri/frontend/js/sources.js` | Source list (mobile adaptations) |
214 - | `src-tauri/frontend/js/items.js` | Item list (mobile adaptations) |
215 - | `src-tauri/frontend/js/detail.js` | Detail panel (mobile full-screen) |
216 - | `src-tauri/frontend/js/components.js` | Action sheets, modal swipe-dismiss |
217 - | `src-tauri/frontend/js/app.js` | Bootstrap, keyboard gating |
218 - | `src-tauri/frontend/index.html` | Tab bar HTML, viewport meta |
35 + | `src-tauri/frontend/js/touch.js` | Gesture utilities |
36 + | `src-tauri/frontend/js/mobile.js` | Mobile interaction wiring |
37 + | `src-tauri/frontend/js/navigation.js` | Bottom tab bar + view switching |
219 38 | `src-tauri/Cargo.toml` | Desktop-only dep gating |
220 39 | `src-tauri/src/main.rs` | Desktop-only plugin/service gating |
221 - | `src-tauri/src/lib.rs` | Desktop-only module gating |
222 -
223 - ---
224 -
225 - ## Reference
226 -
227 - GoingsOn mobile implementation (fully complete):
228 - - `Apps/goingson/docs/todo/todo_mobile.md` — completed phases 1-6
229 - - `Apps/goingson/src-tauri/frontend/js/touch.js` — gesture utilities to port
230 - - `Apps/goingson/src-tauri/frontend/js/mobile.js` — wiring patterns to adapt
231 - - `Apps/goingson/src-tauri/frontend/js/navigation.js` — tab bar reference
@@ -57,6 +57,10 @@
57 57
58 58 # HTTP (download_and_open command)
59 59 ureq = "2.12.1"
60 + open.workspace = true
61 +
62 + # Concurrent fetching
63 + futures.workspace = true
60 64
61 65 # Logging
62 66 tracing.workspace = true
@@ -1,7 +1,7 @@
1 1 {
2 2 "$schema": "https://schema.tauri.app/config/2",
3 3 "productName": "BalancedBreakfast",
4 - "version": "0.3.0",
4 + "version": "0.3.1",
5 5 "identifier": "com.balancedbreakfast.app",
6 6 "build": {
7 7 "frontendDist": "../src-tauri/frontend"
@@ -48,5 +48,8 @@
48 48 # HTML sanitization for untrusted feed content
49 49 docengine = { path = "../../../../Shared/docengine" }
50 50
51 + # Concurrent fetching
52 + futures = { workspace = true }
53 +
51 54 # Article extraction (reader view)
52 55 readable-readability = "0.4"
@@ -124,6 +124,8 @@
124 124 <script src="js/shared-updater.js"></script>
125 125 <script src="js/updater.js"></script>
126 126 <script src="js/navigation.js"></script>
127 + <script src="js/touch.js"></script>
128 + <script src="js/mobile.js"></script>
127 129 <script src="js/app.js"></script>
128 130 </body>
129 131 </html>