feat(rust): native Rust edition of AccessiWeather, replacing the Python app - #753
Merged
Merged
Conversation
Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
The accessiweather binary wires aw-core/aw-providers/aw-store/aw-speech into a Slint UI with keyboard shortcuts, accessible labels/descriptions, status announcements, Add Location / Settings / text dialogs, an update timer, --offline fixtures, a headless --check and a windowed --smoke. Adds a three-OS GitHub Actions matrix and packaging scripts. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Contributor
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Contributor
Author
|
Recorded Linux UI test of the release binary with offline fixtures and a fresh isolated config dir.
|
Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
The native edition now uses wxDragon (wxWidgets bindings) like the other Rust apps: native Frame/Panel/Choice/TextCtrl/ListBox controls, menu accelerators, modal wx dialogs for add-location, alert details, discussion and settings, worker-thread refresh posted back via call_after, and wx timers for periodic refresh and the --smoke exit. Slint, AccessKit and the .slint UI are removed. Also adds a Compress-Archive fallback when 7z is missing on Windows. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
…macOS The Alert Details menu item no longer carries a Ctrl+Shift+A accelerator; on wxOSX it captured Cmd+D so Forecast Discussion never opened. Enter or double-click in the alerts list still opens details. Descriptions now say Cmd instead of Ctrl on macOS and SpinCtrls get a range hint. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
wxOSX matched the &D mnemonic of "Alert &Details" before the Cmd+D accelerator on "Forecast &Discussion". Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Contributor
Author
|
Linux (GTK) UI test of the wxDragon revision — debug binary, offline fixtures, fresh config dirs. Full flow on 521c6d6; changed behaviour rechecked on c7009b2.
|
…Discussion wxOSX exposes &-mnemonics on wxButton labels as Cmd+letter shortcuts, so "Alert &details" swallowed Cmd+D ahead of the menu accelerator. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Exits 0 once the smoke timer has fired with data but the toolkit failed to quit, and 1 with a diagnostic if the event loop never got there. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
Without a Common-Controls v6 manifest wxWidgets shows a modal warning box before the first frame, which is what blocked the Windows CI smoke run. wxMSW also swallows Return before a wxListBox sees KEY_DOWN, so Alert details is now opened from the frame's CHAR_HOOK when the list has focus. Shell scripts are pinned to LF so package.sh runs under Git Bash. Co-Authored-By: Joshua Tubbs <orin8722@gmail.com>
- Announce through prismer (prism, as the Python app does via prismatoid): best backend acquired once, used only when it works at runtime, spoken without interrupting. Drops the tts crate, the aw-speech crate and the Read aloud / Stop speaking commands the Python app never had. - Read and write accessiweather.json in <base>/Config like Python, with its portable-mode rules (.portable marker, forced env var, legacy config dir). - Load API keys from the keyring using python-keyring's Credential Manager layout, or from the encrypted api-keys.keys bundle in portable mode; keys are never serialised to JSON. - Offline/smoke runs never write the user's config. - Fix the crash on exit (timers destroyed after wxWidgets) that failed the Windows smoke test. - CI: run on pull requests, v* tags and manual dispatch only; install prism's Linux deps and ship libprism beside the binary on Linux/macOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aw_core::model ports accessiweather.models field-for-field (current, environmental, aviation, forecast, hourly, minutely, marine, trends, alerts, source attribution, lifecycle/confidence/anomaly results, text products) so Python's dataclasses.asdict JSON loads directly. The old weather/alerts types stay until the providers and presenter move over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ogic Pure aw-core ports of weather_client_fusion*, thermal_comfort, AlertAggregator, alert_lifecycle, forecast_confidence, weather_client_trends, weather_history/weather_anomaly and the source selection / automatic-mode budget rules, each checked against golden output generated from the Python app (rust/tools/golden). serde_json now parses floats exactly (float_roundtrip) so golden values compare bit for bit, and WeatherAlerts::active treats an alert expiring exactly now as inactive, as Python does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on and playback Ports noaa_radio/ (stations, WeatherIndex and wxradio.org clients, stream URL provider, preferences and availability files, player, session, hotkey toggle, alert auto-tune) with a rodio + symphonia network player. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WeatherPresenter, the presentation builders, priority engine, unit and time formatting, impact summaries, mobility briefing, TAF decoding and the tray tooltip formatter, with an injectable clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WeatherDataCache in aw-store writes <config>/weather_cache/*.json exactly as Python's json.dump(indent=2) does (ASCII escapes, float repr, key order, CRLF on Windows) and reads Python's files, including named original_tz datetimes. Stale marking, strict loads, schema invalidation and purging at twice the max age follow cache.py; golden tests pin the bytes and the load/purge results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…geocoding providers Adds the Python-parity provider layer on aw_core::model: Open-Meteo forecast parsing and NWS-shaped mapper, Pirate Weather client with unit bundles, alerts and minutely parsing, Open-Meteo air quality/pollen/UV plus AirNow, Open-Meteo Marine surf summaries, Open-Meteo geocoding with accent/country fallbacks, the location manager (Census, NWS/Nominatim reverse lookup) and Windows current-location detection. Shared unit and thermal-comfort helpers live in aw-core. Legacy provider modules remain as openmeteo::legacy, pirateweather::legacy and geocoding::legacy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports the noaa_radio_dialog.py handlers (finder modes, play/stop/switch, next and preferred stream, favorites, health checks, suppression) onto a DialogView the wx UI renders, plus app-wide wiring for the dialog, hotkey toggle and alert auto-tune. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…treaming transport Ports the Python AI backend: OpenRouter/Venice streaming with the 10s first-token, 15s stall and 90s ceilings, fallback to the free router, refusal and error mapping, explanation cache, model catalogs and browser filters, key validation, tool schemas/executor/formatters and the Weather Assistant's grounded tool-calling loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds aw_core::location_sorting (sort_locations_for_display) and gives AppConfig Python's add/update/remove/reorder semantics: no alphabetical re-sort of the stored list, and no implicit current location on load. Golden data comes from rust/tools/golden/mainwin.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alogs Mirrors ui/main_window*.py, app_shortcuts.py and ui/dialogs/location_dialog.py: Python's layout, labels, names, menu bar, accelerators, two-field status bar, Recent Events, All Locations view, section focus (Ctrl+1..5, F6), debounced location switching and the Add/Edit/Reorder dialogs. Unported dialogs route through ui::commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rust/tools/golden/display.py replays the NWS, Open-Meteo and Pirate Weather cassettes through the Python parsers, adds synthetic cases and records WeatherPresenter and tray tooltip output under 17 settings variants with a frozen clock; the Rust test compares every field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app now builds WeatherClient over the live sources with the on-disk cache, shows aw_core::display::WeatherPresenter text, pre-warms other locations and NWS text products like Python, polls alerts every 60 s, persists NWS zone drift on the UI thread, re-configures the client after Settings, and searches/detects locations with LocationManager and CurrentLocationService. Notification, sound and tray work hooks in at ui::weather_events::weather_updated. --offline/--smoke/--check replay the recorded cassette bodies; --check now requires Python's panel text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d shutdown One radio context for the dialog, hotkey, auto-tune and exit; the startup guidance, onboarding and update dialogs from the Help work; --fake-version/--fake-nightly feed the update check as in Python.
… ports
Remove aw_core::{weather, alerts, presenter, sources, units} and
aw_providers::{weather_client, nws_legacy} plus the Open-Meteo, Pirate
Weather and geocoding legacy clients. Marine/aviation enrichment and
alert aggregation now have one implementation (the orchestrator's /
aw_core's, which the NWS goldens now exercise), thermal comfort and the
NWS unit helpers use aw_core, and repr(float)/truthiness/round/str()
helpers delegate to aw_core::py (float repr verified identical across
all copies). aw-ai formats units through aw_core::display::units.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e quirks One UV band, dewpoint and str.title implementation; serde becomes a test-only dependency of the app. New tests pin Python's naive generated_at handling in the mobility briefing and the offline cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…emoval Settings saves and key imports now reconfigure the live weather client through the lifecycle's refresh; the tray and dialogs read cached weather from the real client.
…l superseded runs - Format the golden generators under rust/tools and exempt them from the app-code docstring and style rules, like tests. - The cache golden test compares legacy naive timestamps by instant: they load as machine-local time and CI runs in UTC. - Cancel a superseded Rust build when a newer push arrives, and time out jobs after 90 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bug fixes Settings dialog backends now call the ported code: model catalogs and the Venice balance (aw_ai::models, which the model browser now uses for all its filtering and text), OpenRouter/Venice/Pirate Weather/AirNow key checks, sample sounds and the sound pack manager (one soundpacks folder, aw-audio's), update checks and the Update Available dialog over Settings, launch at login through the lifecycle (no OS registration in sample-data or source runs) and the tray text preview. Onboarding's "Test key now" opens Settings on the AI tab, Forecaster Notes summaries use AiExplainer with per-panel cancellation, and the debug menu shares the app notifier. The notifier refresh hooks stay as named hooks for the notifications workstream. Config loading follows AppConfig.from_dict: loose booleans and floats, the legacy specific_alert_sounds_enabled key, from_dict's validations, marine_mode truthiness and the build-aware update_channel default (saved as Python does), golden-tested against ConfigManager.load_config. github_backend_url is a typed setting. aw-services now uses aw-notify's ActivationRequest. Python bugs fixed on the Rust side: Reset to defaults keeps locations and active API keys, Import settings keeps active API keys, the Advanced Lookup date presets fill the dates and its close box closes, and removing an empty selected Forecaster Notes tab loads the tab the notebook moves to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summaries go through the app's shared explanation cache, so reopening a product answers instantly; Regenerate Summary drops that entry and asks the model again. Python builds a fresh, uncached explainer per click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port MacOSLocationProvider: a one-shot requestLocation on the calling worker thread, whose run loop is pumped until a delegate callback decides the outcome or 15 s pass. Denied/restricted authorization, native failures and timeouts report Python's messages. The .app Info.plist gains NSLocationWhenInUseUsageDescription with the Nuitka build's text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full refreshes and the 60-second event poll now run the ported alert notification system and notification event manager: toasts through the app notifier, one sound per alert batch from the notifier's sound settings, immediate alert popups, NOAA radio auto-tune, the Event Center, the data_updated and fetch_error sounds, the Forecaster Notes daily climate check and the settings refresh hooks. A clicked alert toast now opens the alert by id in the list on screen, so All Locations shows the right one. The decisions sit behind a small sink trait, pinned by a golden test against the Python main-window glue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings saves and onboarding/portable key imports refresh the notifier and alert settings too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Settings dialog now uses aw_services::import_export for settings export/import, the encrypted API key bundle, both resets and the installed-to-portable copy; settings_actions keeps only thin wrappers that pick the config file and keyring. Functions that save take the file as an Option, so --offline runs change memory only and never delete files, and key-bundle work there uses a throwaway in-memory keyring. The onboarding wizard goes through the same wrappers. Kept from the app copy: a reset keeps locations, the current location and the active keys; an import keeps the active keys and never deletes stored ones; reset-all-data fails when the folder cannot be read and recreates it. Also drops the unused open-folder helpers from aw-services and the app's own copy of open_in_shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…data runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rust releases now carry the Python release asset names, so the updater ports Python's select_asset (installer for installed copies, portable zip for portable ones, disk image on macOS, AppImage on Linux) and its restart plans: run the setup program, unzip over a portable folder, replace the .app, or swap a running AppImage. The workspace version becomes 0.11.0, a plain version newer than the last Python release. Python's last-resort "first asset" pick is not ported: it hands a Mac the Linux AppImage when a release has no disk image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prism's AVSpeech and VoiceOver backends dispatch_sync onto the main queue unless already on the main thread. The app initialises prism on the main thread; cargo's test threads leave the main thread blocked, so the tests hung the macOS CI job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rust/xtask ports the build and release scripts: changelog gate, notes and nightly decision (changelog_tools.py), build metadata, icons (create_icons.py, pixel-identical to Pillow), packaging (build_nuitka.py, build.py, build_appimage.py), the download page (build_pages.py) and the radio stream check. Golden tests replay the Python tools on the same inputs. Packages keep the Python file and release asset names, AppId, install folder and portable rules, so Python installs update into this edition: the Inno Setup installer removes the Python runtime on upgrade, Windows packages carry the VC++ runtime, and macOS gains a disk image, the only format Python's Mac updater installs. rust-build.yml replaces build.yml (same triggers, gate, renames, checksums, notes and notification) and rust-integration.yml replaces the Python live API runs. rust.yml checks PRs on debug builds only, with the CHANGELOG gate; ci.yml runs only for Python changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…golden Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ly when linked Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rust-build.yml only runs on the default branch for manual dispatch, so packaging changes (and this PR) could not be exercised before merge. Pull requests now build and package every artifact; the release and notification steps never run for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build.yml is replaced by rust-build.yml; these tests asserted on its Nuitka steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
rust/, a native Rust edition of AccessiWeather that behaves and reads like the wxPython app for a screen-reader user. The UI is wxDragon (Rust bindings for wxWidgets 3.3), so every control is the platform's own widget, and screen reader announcements go through prism via prismer, the same library the Python app uses through prismatoid. The Python app is untouched apart from lint settings for the new golden-data scripts.Every window, dialog, menu, shortcut, setting and notification of the Python app is ported: the main window and All Locations view, Settings (all eight tabs and sub-dialogs), locations, alerts and toasts, the Event Center, Forecaster Notes / National Products / Advanced Lookup, Explain Weather and the Weather Assistant, Weather History, Precipitation Timeline, Aviation, Air Quality, UV Index, NOAA Weather Radio, the Sound Pack Manager (wizard, community packs, sharing), the tray and global hotkeys, updates, onboarding, Report Issue and the Debug menu.
Both editions share the configuration (
<base>/Config/accessiweather.json), API keys (python-keyring's Credential Manager layout, or the encrypted portable bundle), alert state, weather cache and NOAA radio preferences, and the same single-instance lock.Replaces the Python releases
Merging this PR makes the Rust edition the product:
rust-build.ymlreplacesbuild.yml. It keeps the nightly schedule from dev with the same should-build gate, plusv*tags and manual dispatch..dmg.cargo xtask:changelog,build-meta,icons,package,pagesandcheck-streams, each golden-tested against the Python tool.rust-integration.ymlruns live-API tests on a schedule, replacing the cassette-recordingintegration-tests.yml.ci.ymlnow runs only when Python files change.tests/,scripts/andinstaller/, plus the golden-data generators inrust/tools/golden/, which exist only to compare against the Python app.How parity is checked
Golden tests compare the Rust output with the Python app's own output for the same inputs: recorded NWS/Open-Meteo/IEM responses and synthetic cases, with a frozen clock, run through the real Python code. The generators live in
rust/tools/golden/, their output inrust/testdata/golden/.accessiweather --checkreplays recorded Python runs through the real data path and fails unless the main window's text matches Python's.Crates
aw-coreaw-providersaw-storeaw-notifyruntime_state.json, Windows toasts with click activationaw-audioaw-radioaw-aiaw-servicesxtaskaccessiweatherDeliberate differences (mostly Python bug fixes)
CI
rust.ymlruns fmt, clippy (-D warnings), tests,--checkand a windowed--smokerun (Xvfb on Linux) on Linux, Windows and macOS, on a single debug build. Pull requests that change packaging also runrust-build.ymlas a dry run, which builds and smoke-tests every release artifact without publishing. prism links statically on Windows and ships as a shared library beside the executable on Linux/macOS.Test plan
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace(32 test binaries, including the golden suites)accessiweather --checkmatches the Python app's panel text on recorded datarust-build.ymldry run on this PR (macOS disk image, AppImage and the Fedora smoke test haven't run on real hardware yet)Link to Devin session (earlier revisions): https://app.devin.ai/sessions/6221f07a853548b9abba513df4724e3c
Requested by: @Orinks
🤖 Generated with Claude Code