max / balanced_breakfast
8 files changed,
+131 insertions,
-79 deletions
| @@ -3186,12 +3186,13 @@ | |||
| 3186 | 3186 | ||
| 3187 | 3187 | [[package]] | |
| 3188 | 3188 | name = "makeover-build" | |
| 3189 | - | version = "0.51.0" | |
| 3189 | + | version = "0.53.0" | |
| 3190 | 3190 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3191 | - | checksum = "1fdd63f552182f0c39a0416a4420ecfb66c89e4a11531878b4a832d6ebc6aad7" | |
| 3191 | + | checksum = "82a170c530883774fc44d41e8e785260ee1b44cc26d9bfaa4d77154176af794c" | |
| 3192 | 3192 | dependencies = [ | |
| 3193 | 3193 | "makeover", | |
| 3194 | 3194 | "makeover-geometry", | |
| 3195 | + | "makeover-timing", | |
| 3195 | 3196 | "makeover-webview", | |
| 3196 | 3197 | ] | |
| 3197 | 3198 | ||
| @@ -3207,6 +3208,15 @@ | |||
| 3207 | 3208 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3208 | 3209 | checksum = "774bfd81b003f0d0059ff50daa8e625e6969175bc0132ddf7c2108bba40c47d5" | |
| 3209 | 3210 | ||
| 3211 | + | [[package]] | |
| 3212 | + | name = "makeover-timing" | |
| 3213 | + | version = "0.1.1" | |
| 3214 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3215 | + | checksum = "c83b1e0502c0179754752e59dd98fa4b15df8f9d0b454e0291b80c984aa2bd49" | |
| 3216 | + | dependencies = [ | |
| 3217 | + | "makeover-geometry", | |
| 3218 | + | ] | |
| 3219 | + | ||
| 3210 | 3220 | [[package]] | |
| 3211 | 3221 | name = "makeover-touch" | |
| 3212 | 3222 | version = "0.24.0" | |
| @@ -3219,9 +3229,9 @@ | |||
| 3219 | 3229 | ||
| 3220 | 3230 | [[package]] | |
| 3221 | 3231 | name = "makeover-webview" | |
| 3222 | - | version = "0.59.1" | |
| 3232 | + | version = "0.60.0" | |
| 3223 | 3233 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3224 | - | checksum = "59714ea075f5c266616199eb8345808f85092be04278471ffde546bdb0f43cb6" | |
| 3234 | + | checksum = "203b3a259e249ed32ffd9d48d75a6c12e4aa173095607cc2c852f2c1f3fe9c0e" | |
| 3225 | 3235 | dependencies = [ | |
| 3226 | 3236 | "makeover-geometry", | |
| 3227 | 3237 | "makeover-layout", | |
| @@ -5633,7 +5643,7 @@ | |||
| 5633 | 5643 | ||
| 5634 | 5644 | [[package]] | |
| 5635 | 5645 | name = "synckit-client" | |
| 5636 | - | version = "0.8.1" | |
| 5646 | + | version = "0.9.1" | |
| 5637 | 5647 | dependencies = [ | |
| 5638 | 5648 | "apple-native-keyring-store", | |
| 5639 | 5649 | "argon2", | |
| @@ -8076,42 +8086,6 @@ | |||
| 8076 | 8086 | "winnow 1.0.4", | |
| 8077 | 8087 | ] | |
| 8078 | 8088 | ||
| 8079 | - | [[patch.unused]] | |
| 8080 | - | name = "quasi-axum" | |
| 8081 | - | version = "0.56.0" | |
| 8082 | - | ||
| 8083 | - | [[patch.unused]] | |
| 8084 | - | name = "quasi-basics" | |
| 8085 | - | version = "0.56.0" | |
| 8086 | - | ||
| 8087 | - | [[patch.unused]] | |
| 8088 | - | name = "quasi-http" | |
| 8089 | - | version = "0.56.0" | |
| 8090 | - | ||
| 8091 | - | [[patch.unused]] | |
| 8092 | - | name = "quasi-immediate" | |
| 8093 | - | version = "0.56.0" | |
| 8094 | - | ||
| 8095 | - | [[patch.unused]] | |
| 8096 | - | name = "quasi-notifs" | |
| 8097 | - | version = "0.56.0" | |
| 8098 | - | ||
| 8099 | - | [[patch.unused]] | |
| 8100 | - | name = "quasi-router" | |
| 8101 | - | version = "0.56.0" | |
| 8102 | - | ||
| 8103 | - | [[patch.unused]] | |
| 8104 | - | name = "quasi-store" | |
| 8105 | - | version = "0.1.0" | |
| 8106 | - | ||
| 8107 | - | [[patch.unused]] | |
| 8108 | - | name = "quasi-tauri" | |
| 8109 | - | version = "0.56.0" | |
| 8110 | - | ||
| 8111 | - | [[patch.unused]] | |
| 8112 | - | name = "quasi-webview" | |
| 8113 | - | version = "0.56.0" | |
| 8114 | - | ||
| 8115 | 8089 | [[patch.unused]] | |
| 8116 | 8090 | name = "kberg" | |
| 8117 | 8091 | version = "0.1.0" | |
| @@ -8123,3 +8097,39 @@ | |||
| 8123 | 8097 | [[patch.unused]] | |
| 8124 | 8098 | name = "painhours" | |
| 8125 | 8099 | version = "0.1.0" | |
| 8100 | + | ||
| 8101 | + | [[patch.unused]] | |
| 8102 | + | name = "quasi-axum" | |
| 8103 | + | version = "0.64.0" | |
| 8104 | + | ||
| 8105 | + | [[patch.unused]] | |
| 8106 | + | name = "quasi-basics" | |
| 8107 | + | version = "0.64.0" | |
| 8108 | + | ||
| 8109 | + | [[patch.unused]] | |
| 8110 | + | name = "quasi-http" | |
| 8111 | + | version = "0.64.0" | |
| 8112 | + | ||
| 8113 | + | [[patch.unused]] | |
| 8114 | + | name = "quasi-immediate" | |
| 8115 | + | version = "0.64.0" | |
| 8116 | + | ||
| 8117 | + | [[patch.unused]] | |
| 8118 | + | name = "quasi-notifs" | |
| 8119 | + | version = "0.64.0" | |
| 8120 | + | ||
| 8121 | + | [[patch.unused]] | |
| 8122 | + | name = "quasi-router" | |
| 8123 | + | version = "0.64.0" | |
| 8124 | + | ||
| 8125 | + | [[patch.unused]] | |
| 8126 | + | name = "quasi-store" | |
| 8127 | + | version = "0.1.0" | |
| 8128 | + | ||
| 8129 | + | [[patch.unused]] | |
| 8130 | + | name = "quasi-tauri" | |
| 8131 | + | version = "0.64.0" | |
| 8132 | + | ||
| 8133 | + | [[patch.unused]] | |
| 8134 | + | name = "quasi-webview" | |
| 8135 | + | version = "0.64.0" |
| @@ -73,7 +73,7 @@ | |||
| 73 | 73 | bb-pdf = { path = "crates/bb-pdf" } | |
| 74 | 74 | ||
| 75 | 75 | # External sibling crates (shared under ~/Code/Libraries) | |
| 76 | - | synckit-client = { git = "https://makenot.work/git/max/synckit.git", version = "0.8" } | |
| 76 | + | synckit-client = { git = "https://makenot.work/git/max/synckit.git", version = "0.9" } | |
| 77 | 77 | synckit-config = { git = "https://makenot.work/git/max/synckit.git", version = "0.2" } | |
| 78 | 78 | tauri = "2.10.2" | |
| 79 | 79 | tauri-plugin-updater = "2" |
| @@ -18,7 +18,7 @@ | |||
| 18 | 18 | # Materialises the generated files: themes/, geometry.css, layout.css, | |
| 19 | 19 | # typography.css. makeover and makeover-geometry reach the build script through | |
| 20 | 20 | # here rather than as pins of their own. | |
| 21 | - | makeover-build = "0.52.0" | |
| 21 | + | makeover-build = "0.53.0" | |
| 22 | 22 | # Cuts the two house faces into frontend/fonts/. A git dependency because | |
| 23 | 23 | # quasi-type is `publish = false`, which is also why makeover-build cannot do | |
| 24 | 24 | # this and only emits the CSS that fetches the result. The rev is the one |
| @@ -37,7 +37,6 @@ | |||
| 37 | 37 | <div class="header-actions"> | |
| 38 | 38 | <label for="search-input" class="sr-only">Search items</label> | |
| 39 | 39 | <input type="text" id="search-input" placeholder="Search... (/) | ? for help" class="field search-input"> | |
| 40 | - | <span id="search-spinner" class="search-spinner" aria-hidden="true"></span> | |
| 41 | 40 | <label for="sort-select" class="sr-only">Sort order</label> | |
| 42 | 41 | <button id="unread-toggle" class="button button--sm" title="Show unread only (U)" aria-pressed="false">Unread</button> | |
| 43 | 42 | <select id="sort-select" class="field" title="Sort order (S)"> |
| @@ -68,7 +68,7 @@ | |||
| 68 | 68 | 13. Tag: .tag | |
| 69 | 69 | 14. Context menu: .context-menu | |
| 70 | 70 | 15. Health indicator: .health-dot (state-by-color, F3 fix pending) | |
| 71 | - | 16. Skeleton & loading: .skeleton-item, .skeleton-line, spinner | |
| 71 | + | 16. Skeleton & loading: .skeleton-item, .skeleton-line | |
| 72 | 72 | 17. Progress bar: .progress-bar-container | |
| 73 | 73 | 18. Update banner: .update-banner | |
| 74 | 74 | 19. Health popover: .health-popover | |
| @@ -323,21 +323,11 @@ | |||
| 323 | 323 | width: 180px; | |
| 324 | 324 | } | |
| 325 | 325 | ||
| 326 | - | /* Search spinner */ | |
| 327 | - | .search-spinner { | |
| 328 | - | display: none; | |
| 329 | - | width: 14px; | |
| 330 | - | height: 14px; | |
| 331 | - | border: 2px solid var(--border); /* spinner ring thickness, not a frame */ | |
| 332 | - | border-top-color: var(--category-four); | |
| 333 | - | border-radius: var(--radius-full); | |
| 334 | - | animation: spin 0.6s linear infinite; | |
| 335 | - | } | |
| 336 | - | .search-spinner.active { display: inline-block; } | |
| 337 | - | ||
| 338 | - | @keyframes spin { | |
| 339 | - | to { transform: rotate(360deg); } | |
| 340 | - | } | |
| 326 | + | /* `.search-spinner` and `@keyframes spin` are gone (2026-08-26). They were the | |
| 327 | + | only rotation left in this file, and a spinner turns at a rate it invented: | |
| 328 | + | it reads as progress on a wait that has none to report. Search here is a | |
| 329 | + | local query behind a 300ms debounce, so under rule 4 of wiki | |
| 330 | + | `loading-and-progress-standard` there was nothing for it to say. */ | |
| 341 | 331 | ||
| 342 | 332 | .search-input::placeholder { color: var(--content-muted); } | |
| 343 | 333 | ||
| @@ -1191,9 +1181,9 @@ | |||
| 1191 | 1181 | } | |
| 1192 | 1182 | ||
| 1193 | 1183 | /* === 16. Skeleton & loading ============================================ | |
| 1194 | - | Canonical: .skeleton-item, .skeleton-line.{short,medium,long}, | |
| 1195 | - | .search-spinner (above). Charter target (F5): add BB.ui.renderSkeleton | |
| 1196 | - | helper; widen first-paint usage beyond items panel. | |
| 1184 | + | Canonical: .skeleton-item, .skeleton-line.{short,medium,long}. Charter | |
| 1185 | + | target (F5): add BB.ui.renderSkeleton helper; widen first-paint usage | |
| 1186 | + | beyond items panel. | |
| 1197 | 1187 | ========================================================================== */ | |
| 1198 | 1188 | .skeleton-item { | |
| 1199 | 1189 | display: flex; | |
| @@ -1236,6 +1226,17 @@ | |||
| 1236 | 1226 | background-color: var(--category-four); | |
| 1237 | 1227 | transition: width var(--transition-slow) ease; | |
| 1238 | 1228 | } | |
| 1229 | + | /* The two numbers the bar is drawn from, said out loud. A 3px bar at the foot | |
| 1230 | + | of the header reports roughly-a-quarter and nothing else; this says which | |
| 1231 | + | quarter of what. Empty until something has counted, so a refresh that has not | |
| 1232 | + | heard from a feed yet shows a trough and no claim. */ | |
| 1233 | + | .progress-reading { | |
| 1234 | + | position: absolute; | |
| 1235 | + | right: var(--gap-section); | |
| 1236 | + | bottom: var(--gap-bound); | |
| 1237 | + | color: var(--content-muted); | |
| 1238 | + | font-size: var(--font-size-xs); | |
| 1239 | + | } | |
| 1239 | 1240 | ||
| 1240 | 1241 | /* Toast with action */ | |
| 1241 | 1242 | .toast-action { |
| @@ -55,16 +55,17 @@ | |||
| 55 | 55 | ||
| 56 | 56 | // Search input: debounced at 300ms, with request ID so last request wins | |
| 57 | 57 | const searchInput = document.getElementById('search-input'); | |
| 58 | - | const searchSpinner = document.getElementById('search-spinner'); | |
| 58 | + | // No indicator. Search is a local query over stored items, so under | |
| 59 | + | // rule 4 of wiki `loading-and-progress-standard` there is nothing an | |
| 60 | + | // indicator would add: the wait is shorter than the 300ms debounce in | |
| 61 | + | // front of it, and a mark that appears and goes inside a third of a | |
| 62 | + | // second is a flicker rather than a report. The request id still | |
| 63 | + | // stands, because last-request-wins is correctness and not decoration. | |
| 59 | 64 | searchInput.addEventListener('input', BB.utils.debounce(async () => { | |
| 60 | 65 | BB.state.set('currentSearch', searchInput.value); | |
| 61 | 66 | BB.state.resetPagination(); | |
| 62 | - | const myId = ++loadRequestId; | |
| 63 | - | searchSpinner.classList.add('active'); | |
| 67 | + | ++loadRequestId; | |
| 64 | 68 | await BB.items.load(); | |
| 65 | - | if (loadRequestId === myId) { | |
| 66 | - | searchSpinner.classList.remove('active'); | |
| 67 | - | } | |
| 68 | 69 | }, 300)); | |
| 69 | 70 | ||
| 70 | 71 | // Sort select: guarded so rapid changes don't interleave results |
| @@ -35,17 +35,46 @@ | |||
| 35 | 35 | ||
| 36 | 36 | /** | |
| 37 | 37 | * Create and append a progress bar to a container element. | |
| 38 | + | * | |
| 39 | + | * Takes the two counts rather than a percentage (2026-08-26, wiki | |
| 40 | + | * `loading-and-progress-standard`). Three things follow from that and each | |
| 41 | + | * of them was wrong before: | |
| 42 | + | * | |
| 43 | + | * 1. A bar can only be drawn once something is actually counted. The old | |
| 44 | + | * caller opened with `set(5)` to "indicate started", which is five | |
| 45 | + | * percent of nothing: a bar claiming progress before a single feed had | |
| 46 | + | * been fetched. `set` with no total draws an empty trough instead. | |
| 47 | + | * 2. Done over total is what the bar reports, and `3/12` is a fact where | |
| 48 | + | * `25%` is a rounding of one. Rule 1: never a remaining time, an arrival | |
| 49 | + | * time, or a rate extrapolated forward, and a percentage is one step from | |
| 50 | + | * all three. | |
| 51 | + | * 3. The numbers are readable. A 3px bar at the foot of the header says | |
| 52 | + | * roughly-a-quarter and nothing else; the reading says which quarter of | |
| 53 | + | * what. | |
| 54 | + | * | |
| 38 | 55 | * @param {HTMLElement} container - Parent element. | |
| 39 | - | * @returns {{set: function(number), remove: function}} Controller. | |
| 56 | + | * @param {string} [noun] - What is being counted, for the reading. | |
| 57 | + | * @returns {{set: function(number, number), remove: function}} Controller. | |
| 40 | 58 | */ | |
| 41 | - | function showProgress(container) { | |
| 59 | + | function showProgress(container, noun) { | |
| 42 | 60 | const bar = document.createElement('div'); | |
| 43 | 61 | bar.className = 'progress-bar-container'; | |
| 44 | 62 | bar.innerHTML = '<div class="progress-bar" style="width: 0%"></div>'; | |
| 63 | + | const reading = document.createElement('span'); | |
| 64 | + | reading.className = 'progress-reading'; | |
| 45 | 65 | container.appendChild(bar); | |
| 66 | + | container.appendChild(reading); | |
| 46 | 67 | return { | |
| 47 | - | set(pct) { bar.querySelector('.progress-bar').style.width = pct + '%'; }, | |
| 48 | - | remove() { bar.remove(); }, | |
| 68 | + | set(done, total) { | |
| 69 | + | // A total of zero is nothing to do rather than everything done, | |
| 70 | + | // which is `makeover_layout::Meter`'s reading of the same case. | |
| 71 | + | const share = total > 0 ? Math.min(done / total, 1) : 0; | |
| 72 | + | bar.querySelector('.progress-bar').style.width = (share * 100) + '%'; | |
| 73 | + | reading.textContent = total > 0 | |
| 74 | + | ? done + '/' + total + (noun ? ' ' + noun : '') | |
| 75 | + | : ''; | |
| 76 | + | }, | |
| 77 | + | remove() { bar.remove(); reading.remove(); }, | |
| 49 | 78 | }; | |
| 50 | 79 | } | |
| 51 | 80 |
| @@ -196,7 +196,13 @@ | |||
| 196 | 196 | button.textContent = 'Refreshing...'; | |
| 197 | 197 | ||
| 198 | 198 | const header = document.querySelector('.header'); | |
| 199 | - | const progress = BB.ui.showProgress(header); | |
| 199 | + | const progress = BB.ui.showProgress(header, 'feeds'); | |
| 200 | + | ||
| 201 | + | // How many feeds there are, once something has said. Kept here rather | |
| 202 | + | // than read back off the bar so the finish below can fill it honestly: | |
| 203 | + | // `fetchAll` resolves with items and errors and says nothing about how | |
| 204 | + | // many feeds it walked. | |
| 205 | + | let counted = 0; | |
| 200 | 206 | ||
| 201 | 207 | // Listen for per-plugin progress events | |
| 202 | 208 | let unlisten = null; | |
| @@ -204,7 +210,8 @@ | |||
| 204 | 210 | unlisten = await window.__TAURI__.event.listen('fetch-progress', (event) => { | |
| 205 | 211 | const { completed, total } = event.payload; | |
| 206 | 212 | if (total > 0) { | |
| 207 | - | progress.set(Math.round((completed / total) * 100)); | |
| 213 | + | counted = total; | |
| 214 | + | progress.set(completed, total); | |
| 208 | 215 | } | |
| 209 | 216 | }); | |
| 210 | 217 | } catch (_) { | |
| @@ -212,10 +219,13 @@ | |||
| 212 | 219 | } | |
| 213 | 220 | ||
| 214 | 221 | try { | |
| 215 | - | progress.set(5); // indicate started | |
| 216 | - | ||
| 222 | + | // Nothing is set here. The bar used to open at five percent to | |
| 223 | + | // "indicate started", which is a bar claiming progress before a | |
| 224 | + | // single feed had been fetched -- rule 1 of wiki | |
| 225 | + | // `loading-and-progress-standard`, and the exact shape it forbids. | |
| 226 | + | // The trough is drawn empty and the first real event fills it. | |
| 217 | 227 | const result = await BB.api.feeds.fetchAll(); | |
| 218 | - | progress.set(100); | |
| 228 | + | progress.set(counted, counted); | |
| 219 | 229 | ||
| 220 | 230 | let msg = 'Fetched ' + result.itemsFetched + ' item' + (result.itemsFetched !== 1 ? 's' : ''); | |
| 221 | 231 | if (result.errors && result.errors.length > 0) { | |
| @@ -228,7 +238,9 @@ | |||
| 228 | 238 | BB.sources.load(); | |
| 229 | 239 | BB.items.load(); | |
| 230 | 240 | } catch (err) { | |
| 231 | - | progress.set(100); | |
| 241 | + | // The bar is left where the last event put it. Filling it on the way | |
| 242 | + | // out would say every feed was fetched, which is the one thing this | |
| 243 | + | // branch knows to be false. | |
| 232 | 244 | BB.ui.showErrorWithRetry('Failed to refresh: ' + BB.utils.getErrorMessage(err), refresh); | |
| 233 | 245 | } finally { | |
| 234 | 246 | if (unlisten) unlisten(); |