Commit graph

136 commits

Author SHA1 Message Date
CRT
a890dd506f create-waybetter: a TypeScript plugin project depends on waybetter ^0.6.0 like the app scaffold (^0.2.0 could not resolve to the current package)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:52:37 +07:00
CRT
10d86ef55c App menu: a Languages… row (after Store) opens the panel's Languages section; it shows only when the app says its panel has one (features.strings); English word wb.menu.strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:52:27 +07:00
CRT
fe7f60cd8d Share my setup, links: a waybetter://setup link opened from outside reaches the shell as text on the install-link event (shape and size checked natively, never decoded or applied there); the install controller hands it to the app's onSetupLink and stages nothing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:49:57 +07:00
CRT
a8710dfe02 Theme v2: import icon packs (install screen, packs in the theme choice)
Appearance > Icons gets Import icon pack: a *.icons.json file is parsed by the
runtime sanitizer and shown on an install screen (name, author, licence,
source, credits, every glyph, size, data only) before it joins the persisted
theme choice (packs, at most 8, validated natively for shape and size) and
the Icons picker. Imported packs can be removed again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:48:26 +07:00
CRT
d902d3f65a App menu: with no menu button in the titlebar, the tab strip reserves no room for it
With the logo as the menu's only titlebar trigger (`<AppMenu trigger={false}/>`), `--bt-tb-menu` still kept 38px free at the end of the titlebar-hosted tab strip. It is 0 when there is no `.bt-app-button`, so the right side is back to its layout before the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:45:26 +07:00
CRT
1c7a842d09 Languages section: a pack's name and its badges on one line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:44:14 +07:00
CRT
44ea2d2060 Share my setup, panel: Backup & sync gets "Share my setup" (Copy link, Save file) and "Use a setup" (paste a waybetter://setup link or open a file, then What will change with Apply and Cancel); shown only for an app that sets PanelSite.setup; English words
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:43:24 +07:00
CRT
8005b2f047 README: the optional command groups the host serves and what the app supplies for each
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:42:07 +07:00
CRT
409949edb9 Host: the settings backup commands (collect, export, open, apply) are served for every app; SiteHooks::backup_drafts and backup_apply carry what only the app knows; commands::BACKUP, testkit::assert_backup_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:41:12 +07:00
CRT
198b1160f2 String packs: the site tab's delivery layer lives in app.rs, so a host without the net feature builds again (a generated app's cargo check failed on the net-gated store_commands module)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:40:25 +07:00
CRT
38b877f03a Share my setup, model: one file or one waybetter://setup link with the look (theme id, accent, icon pack), the mode and the non-default flags, nothing personal; reading drops what the app does not know and refuses another app's setup; the plan lists exactly what would change and applying it changes nothing else (not in the UI yet)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:38:30 +07:00
CRT
3402c1d85d Host: developer mode (dev_* commands and the watcher) is served for every app that sets AppConfig::with_dev; SiteHooks::dev_theme_apply and dev_theme_end carry the app's theme file; commands::DEV, testkit::assert_dev_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:32:54 +07:00
CRT
c83f541006 String packs, icon packs and install words in every catalog: wb.strings.*, wb.install.strings.*, license/credits, the strings install and error words, wb.appearance.icons*; dropHint and the two kind errors translated again for their new English
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:31:43 +07:00
CRT
311f5d9e20 App menu: one home in the titlebar, the logo
The top-right ⋮ and the logo opened the same menu: one of them was redundant.
The logo stays as the only titlebar trigger (the app's own mark, where an app
menu lives); the right side is back to workspaces, accounts and the window
buttons. Alt+F, F10, the palette and the account menu's last row open the
menu under the logo. Apps that want a separate button keep <AppMenu/>'s
default trigger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:26:40 +07:00
CRT
48ff509b15 Host: workspaces (always) and encrypted folder sync (AppConfig::with_sync) commands; SiteHooks::backup_sections and backup_section_names say what a backup and sync carry; commands::WORKSPACES, commands::SYNC, testkit::assert_sync_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:26:03 +07:00
CRT
385ca189da Docs: string packs as a Store kind (licence and credits on every item), the delivery to site tabs, the Languages section and translate mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:26:02 +07:00
CRT
b4157d7eac String packs: translate mode. Strings carry an invisible key marker, a click on a word opens an editor that checks the edit with the pack rules, edits collect in the tab as a draft pack that applies after a reload, and "Copy pack file" gives the pack; the Languages section switches it on (the delivery then carries the whole inventory, packs or not); native code accepts such a delivery without packs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:25:04 +07:00
CRT
8a0ab6d3a1 Icon packs are the owner's pick: the theme choice carries it, Appearance offers it
- ThemeChoice gains an optional icons field (absent: follow the theme's pack; site: the site's
  own icons; else a pack id), validated natively as a slug (waybetter-host theme.rs, with a
  test) and kept by per-theme choices; variants is an override group natively too.
- Appearance: an Icons row (Follow the theme / the site's own icons / the site's packs) when the
  site lists packs; English words in the catalog.
- The chrome's glyphs follow the owner's pick as well as the theme's suggestion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:19:55 +07:00
CRT
e88339bda6 Host: native update commands (update_status, update_check, apply_update, app_ready) and the update loop are served for every app that sets AppConfig::with_updates; commands::UPDATE, testkit::assert_update_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:18:34 +07:00
CRT
1904031e6f String packs in the shell: the install screen shows a pack's language, coverage against the site's inventory (and what is ignored or cannot be replaced yet), a sample, that it is data only, and any item's licence and credits; the Store lists string packs under Languages and removes them; the Languages section turns packs on and off, orders and removes them and hands the site tabs the new delivery; English words (wb.strings.*, wb.install.strings.*, wb.install.license/credits and the strings variants)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:14:41 +07:00
CRT
825742b964 Repair: the Google Fonts host commands as 7f9e172 meant them (a staging race split them from 43fc8a1)
Two agents staged the same host files at once: 7f9e172 went in with the diagnostics' hunks of app.rs, commands.rs and lib.rs instead of its own, and 43fc8a1 then dropped fonts_commands.rs. This puts back exactly the Google Fonts wiring from the working tree (AppConfig::with_fonts, SiteHooks::fonts_changed, commands::GFONTS, the fonts handler, fonts_commands.rs); the diagnostics stay as committed. theme.rs, still in progress, is not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:12:43 +07:00
CRT
43fc8a15f5 Diagnostics for focus and layering (--diag / WAYBETTER_DIAG=1): one timestamped line per event in the app's log folder
For the macOS reports (the menu never shows and the window goes inactive on a click; the find field gets no keyboard): off unless the app is started with `--diag` or WAYBETTER_DIAG=1. Then native code and the shell append to `diag.log` in the app's log folder (truncated at launch): menu open requests and every close with its reason, overlay counts, raise/lower and `shell_raise` results, native `shell_focus` calls and their result, the shell document's focus/blur/visibility, and where the find bar's focus is right after it opens and 300 ms later. A line is an event and a few words in plain ASCII (capped); never a URL, a query or page text. `diag_get` / `diag_log` are shell-only (in commands::SHELL and the template's list).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:11:53 +07:00
CRT
7f9e172196 Host: Google Fonts commands (gfonts_*) are served for every app that sets AppConfig::with_fonts; SiteHooks::fonts_changed; commands::GFONTS, testkit::assert_fonts_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:11:46 +07:00
CRT
da9ac11b24 Shell: the chrome's own glyphs follow the theme's icon pack
A site's theme adapter may name the pack of the global theme (iconPack); the frame's Icon draws
that pack's glyph for the same meaning (home, search, bell, mail, bookmark, user, ...) from its
validated path data, and its own line glyph where the pack has none. Rendered as React SVG
elements, never markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:07:49 +07:00
CRT
873f8561ca Host: the media panel's commands (gallery folders, previews, the clipboard history and its watcher) are served for every app that sets AppConfig::with_media; commands::MEDIA, media_commands::gallery_file/clip_image for an app's attach command, testkit::assert_media_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:02:35 +07:00
CRT
419f60b5b1 Store kind "strings": string packs install like themes and plugins (dropped .strings.json, links, the signed Store), validated as plain data on this side (envelope, plain text, size caps, the app's own site), kept in app data strings/ with their order and which are on (at most 8); licence and credits are optional fields of every catalog item; five shell-only commands (strings_list, strings_read, strings_set, strings_remove, strings_apply); the shell's delivery (packs + the inventory slice) goes into site tabs' session storage by revision, as the first document-start layer of a site tab and never in safe mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:56:54 +07:00
CRT
8e9442c2fc Report a problem: the webview's full version (the user agent carries a reduced one)
Seen live: the report said "WebView2 154.0.0.0", Chromium's reduced user agent version. The engine's brand list (`fullVersionList`) gives the full one, which the report now uses when it is a plain version (tested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:56:18 +07:00
CRT
8eb9dba4d7 App menu and mode words in every catalog: find in page, update check, safe mode, report a problem, Custom/Re-apply
menuMessages.en.json stays the English source of the menu's words; en.json now carries them too
(the i18n test takes them into the source union) and 48 catalogs translate all 51 menu words and
the seven new mode words (plural forms per language for the match count).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:50:45 +07:00
CRT
92b9d1d9fc Remote builds: list the output with numeric owners, so a build log never shows the machine's account name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:48:16 +07:00
CRT
5211995694 Find in page: WebView2 counts its active match from 1; the shell's index is 0-based
Seen live on the shared app: "domain" on example.com showed "2/2" for the first of two matches, and "2 matches" after Next. `ICoreWebView2Find::ActiveMatchIndex` starts at 1 (-1: none); `FindState::from_webview2` converts it (tested), so the bar reads 1/2, then 2/2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:47:23 +07:00
CRT
3450eac09a Host: the store's commands (install links, dropped files, the signed Store, the install screen) are served for every app that sets AppConfig::with_store; commands::STORE, testkit::assert_shell_only_group and assert_store_guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:40:37 +07:00
CRT
06e830b132 Startup cover never outlasts a page that does not say it painted; menu clicks ask nothing native; mode cards show their picture
Release blocker (macOS 1.0.0: both panes stayed on the shell's loading shape). After `shell_ready` the startup cover waited, with no limit, for every visible site tab to report that it painted. A page whose report never comes (its paint check is frame-driven and gives up after 10 s, and an engine may hold a hidden window's frames back) kept the shell raised over the panes, showing the skeleton, for good. The wait is now bounded (8 s after the window is shown); the page then shows as it is.

The app menu asked for the keyboard natively (`shell_focus`) on every open. It now does so only when the shell does not have it (keys pressed in a tab); a click on the shell already gave it the keyboard.

The mode cards' default picture was shrunk to 16px by the shell's base glyph rule ("a few dots"); it keeps its size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:40:02 +07:00
CRT
2e3babd2f5 Plugin API: a style injected at document start waits for the document root
WebView2 runs document-start scripts before the document has an element, so
injectCSS threw ("reading 'appendChild'") for every CSS feature that was on at
start, e.g. hiding promoted posts, until the next flags delivery restarted it.
Such styles now wait (one MutationObserver for all of them), are attached as
soon as the document has a root, and get their CSP check then; a style cleared
while waiting is never attached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:39:16 +07:00
CRT
26658e14ae Modes: a mode that no longer matches the features reads "Custom (based on <mode>)" and offers "Re-apply <mode>"
A mode is a starting point, never a lock and never applied again on its own. modeStatus() (runtime)
compares the flags with the mode's preset: custom, what re-applying turns on and off, and which of
those the preset gained in an update since it was applied. mode_set keeps the preset of that moment
(modes.json "preset", flag names only, bounded; forgotten when the mode changes). Settings, the
first-run screen and the app menu's Mode row show Custom; Re-apply lists what it turns on and off
before it writes anything; a preset that grew is mentioned once on the mode row (Review), never applied.
Tests: exact match, changed by the owner, preset grew, choice made before the preset was kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:33:41 +07:00
CRT
b75246bef8 String packs: a page gets only the slice of the inventory its packs' keys need (sliceInventory, isPageInventory); a slice does not call other keys new
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:30:21 +07:00
CRT
eb2bc62ad4 Find in a deck column; the app menu no longer closes on a passing blur
Ctrl+F pressed in a deck column (or in the shell while a deck is in front, for its focused column) opens that column's own find bar, laid over the column's header; F3 and Shift+F3 step through it. Same bar, same `tab_find`.

The app menu closed on any window blur. Taking the keyboard from a focused tab (Alt+F / F10 forwarded by the tab, then `shell_focus`) can pass through a blur on its way to the shell, which closed the menu a few milliseconds after it opened. It now closes on blur only if the focus is still away a moment later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:35:58 +07:00
CRT
1356d9364a release macos|linux: --commit <ref> builds an exact commit (its submodule pointers included), the artifact version comes from that commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:33:11 +07:00
CRT
0c6b1454d9 Remote builds: the updater tarball carries no account name in its entry headers (owner 0/root); the scan caught it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:31:26 +07:00
CRT
006a7d160c Theme runtime: a static sheet for what does not depend on the site's stylesheet (components, variants, decoration, icons); refresh()
The site adapter's staticCss(choice, mode) is written to its own <style> (wb-theme-static), rebuilt
only when the theme or the site's mode changes or on refresh(); the palette pass never reads it back
as the site's rules, and the first-frame snapshot carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:30:34 +07:00
CRT
7469f0e4af waybetter manifests: winget, Chocolatey, Homebrew cask and AUR files from one JSON description
Generic (publisher, id, name, version, installer URLs and sha256 or local files, silent switches, licence, homepage). winget: version, installer and locale manifests (validated with the installed winget), Chocolatey: nuspec + install script with checksum (choco pack), Homebrew: a cask for our own tap, AUR: PKGBUILD and .SRCINFO from the .deb or AppImage. waybetter release windows|macos|linux writes them to <outDir>/distribution/<version> once the public download URL is given (--download-base); nothing is submitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:28:44 +07:00
CRT
5a7aae7adb Remote builds: unpack with the current time (cargo must not reuse stale objects from a cached target), export blobs as committed, build at low priority
git archive stamps files with the commit time, older than the cached build in the shared target dir: a changed source looked unchanged and the build reused stale objects (seen as a missing module after a pointer bump).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:26:19 +07:00
CRT
7c683598a4 Report a problem: a diagnostic summary with no personal data, shown as it would be shared, with Copy and the issue page
The app menu's "Report a problem…" opens a small dialog with the summary (reportModel.ts, English on purpose: the people who read reports do): the app and framework versions, the system and webview (coarse, from the user agent), the language and how it was chosen, the mode, the theme and icon pack ids, the file names of the plugins that are on, what paused itself (the panel's `reportDetails`), and safe mode. The model has no field for an account, a handle, an address, a tab or a path, and a value that is not made of id characters is left out rather than trimmed into shape, so nothing of that kind can ride along (tested with hostile input). The dialog sends nothing: Copy puts the text on the clipboard, and "Open the issue page" opens `ShellSite.menu.report.issueUrl` bare in the system browser (the summary is never put in a URL). Without an issue page the button is off and the dialog says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:25:13 +07:00
CRT
1b0a489b36 waybetter release: builds of the committed state on a Mac over SSH and in WSL
release macos --remote and release linux --wsl export HEAD plus each submodule at its recorded commit (git archive, never the working tree, nothing fetched from origin), build it path-free through build-env with unsigned bundles, bring the artifacts back and scan them here. release linux --appimage/--deb signs the AppImage (the Linux update payload) on the release machine and adds linux-x86_64 to latest.json. The remote script is low priority, never installs, quits or replaces an app on the Mac. Config: prepareCommand, exportExclude.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:24:04 +07:00
CRT
f8144a3bff Windows installer: an install made under the app's old name is older than any setup of the renamed app, whatever its version number (upgrade in place, data kept)
The renamed app may start its numbering again. Told apart by DisplayName in the uninstall key
(the old app wrote its old name there; this template writes the new one). Also exempt from the
silent-downgrade abort. Compiled with and without a legacy name and run on the test variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:23:43 +07:00
CRT
16957f4ea7 Theme engine v2, the document side: component tokens, layout variants, decoration, icon packs
A theme may now say how each piece of a site is built, not only one radius and one density:
- components (themeComponents.ts): 36 generic parts with clamped tokens (radius, border incl.
  98.css-style bevels, fill by color role / glass / gradient, padding, sizes, type, shadow, shape,
  hover/active/focus), presets and parents; compiled over a site's part map as soft values
  (--wb-part-*) and rigid rules; text on a chosen fill keeps its contrast.
- variants (themeVariants.ts): fixed words on six axes; the owner holds an axis or switches layout
  changes off; the look of a variant is the site's skin.
- decoration (themeDecor.ts): dividers, chrome (title bar, masthead, prompt), a focus ring that is
  always visible, selection, scrollbars, links, motion under reduced-motion.
- icon packs (themeIcons.ts): a standalone document of glyphs by semantic name (path data, colored
  layers or a small PNG, never an SVG document), with license, source and credits; a theme only
  names a pack; the owner's icon choice is separate; drawn as CSS masks of the current color over
  the site's own svg boxes, behind forced-colors and mask-support guards, per group.
- a theme document gains the optional groups components, variants, decor, icons and
  colors.actions; a theme without them is sanitized to exactly what it was.
- theme.schema.json and the new icons.schema.json are generated from the same tables
  (scripts/theme-schema.mjs; a test fails when they are stale).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:22:36 +07:00
CRT
593ce46bd7 READMEs for strangers: overview, three layers, a real minimal adapter, security model; the long reference moves to docs/ARCHITECTURE.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:20:58 +07:00
CRT
ca46210e53 Safe mode: one run with only the shell working (the app menu, --safe-mode, Shift at launch on Windows)
Host: `safe.rs` is a flag in this process's memory, never written to disk, so the next normal start is back to the owner's setup. While it is on a site tab gets the tab bridge and nothing of the app's page layers: its document-start scripts are `SiteHooks::safe_init_scripts` (default: the bridge alone) instead of `init_scripts`; `site_page_loaded`, `tab_placed` and `tab_background` are not called; `page_scripts` is empty; `eval_in_site_tabs` does nothing. So no page runtime, plugins, theme, custom CSS, feature flags, fixes, hotfix data or words reach a page: the site runs as in a browser, and only the shell is the app's. It starts from the launch argument `--safe-mode` (for when the interface itself is broken), from Shift held while the app starts (Windows), or from the shell: `safe_mode_get` / `safe_mode_set {on}` (shell-only), where a switch rebuilds every tab webview (profiles marked stale, `tabs-rebuild` asks the shell for its layout) so document-start scripts change too. Tests: the argument, the flag, and that app.rs and host.rs gate every layer.

Frame: the app menu's "Restart in safe mode…" says what it does before anything reloads; while on, the titlebar shows an amber "Safe mode | Leave" chip (one click leaves) and the row reads "Leave safe mode"; the chrome drops its theme (and pane themes) and takes it back on leaving, without touching the first-frame cache. `features.safeMode` (on by default).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:13:03 +07:00
CRT
d24b1d2234 Words: the deck is "Deck" (it was "Power User Mode", which collided with the Power User mode); the three feature-health words in all 48 translated languages (machine)
Keys and saved data unchanged. Each language reuses its own word for a deck; only "Deck settings"
is newly translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:05:23 +07:00
CRT
16d89de712 Theme color model: a foreground the site paints dimmer than its secondary text becomes the theme's secondary text
A site may color an icon or text with one of its border grays (X's composer toolbar: 1.7:1 on its
own black). Placed by axis position it landed between a theme's background and its secondary
text, under every contrast a theme guarantees. In text usage that stretch of the axis (above the
most raised surface, below secondary text) now maps to the theme's secondary text; surface-dark
foregrounds (labels on a filled surface), borders and backgrounds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:58:48 +07:00
CRT
2f126659f9 App menu: "Check for updates" is a real check, once, now, and says what came of it calmly
Host: `updater::check_now` runs the same check the timer runs (the same updater endpoint, nothing else) for the app's shell-only `update_check`. It never queues or retries: while a check is in flight (the timer's or an earlier request's), or within 10 seconds of the last request, it only answers with the current status (`update::may_check_now`, tested). The timer's loop uses the same single-flight guard.

Frame: the menu's Updates row is that check (`updateRow`, tested): "Checking…", "Up to date · <version>", "Downloading <version>…", "Restart to update" when an update is ready (the row then installs it), and "Could not check" with a plain sentence as its tooltip when the server does not answer. The updater's error text is never shown there, and a failure is only mentioned when the owner asked for the check in this menu; the timer's own failures stay quiet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:54:51 +07:00