Skip to content

Implement interactive onboarding wizard#13

Open
lstpsche wants to merge 3 commits intoVeirt:mainfrom
lstpsche:feat/onboarding-wizard
Open

Implement interactive onboarding wizard#13
lstpsche wants to merge 3 commits intoVeirt:mainfrom
lstpsche:feat/onboarding-wizard

Conversation

@lstpsche
Copy link
Copy Markdown
Contributor

@lstpsche lstpsche commented Feb 17, 2026

Summary

Adds a new onboard subcommand (alias: init) that replaces the current manual config setup with a styled, interactive setup wizard.

Before this change, users had to manually create the config directory and write a TOML file by hand. Now they can run:

weathr onboard
# or
weathr init

What changed

New files:

  • src/onboard.rs -- the full onboarding flow: prompts, geocoding integration, styled output, config save.

Modified files:

  • Cargo.toml -- added dialoguer (interactive prompts with fuzzy-select).
  • src/main.rs -- converted flat CLI to optional subcommand via clap's #[command(subcommand)]. Existing flags and behavior are untouched when no subcommand is given. Rustls crypto provider is now installed at startup to cover all HTTP code paths (geolocation, onboard, weather).
  • src/config.rs -- added Serialize to Config and Location. Added save() (creates parent dirs automatically), get_config_dir() methods. Made get_config_path() and validate() public.
  • src/weather/types.rs -- added Serialize to WeatherUnits.
  • src/error.rs -- added OnboardError enum. Added WriteError and SerializeError variants to ConfigError.
  • src/lib.rs -- registered the new onboard module.

New dependencies

Crate Version Purpose
dialoguer 0.12 (with fuzzy-select) Select, FuzzySelect, Input, Confirm interactive prompts

Terminal styling uses crossterm::style::Stylize (already a project dependency). URL construction uses reqwest::Url::parse_with_params (re-exported from the url crate that reqwest already depends on). No additional direct dependencies needed for either.

How it works

The wizard runs in three phases:

Phase 1 -- Directory and file setup

Resolves the platform-specific config path and loads the existing config file if present. If the file exists but is corrupted, a warning is printed and defaults are used. Falls back to Config::default() if no file exists. Directory creation is handled by Config::save() automatically.

Phase 2 -- Interactive prompts

Walks through every config setting with styled prompts. Existing values are shown as defaults -- pressing Enter keeps them.

Prompt sequence:

  1. Location method (Select): Enter coordinates / Search by city name / Use auto-detection
  2. Coordinates (Input<f64> with validation) or City search (text input → Open-Meteo Geocoding API → FuzzySelect from results). City search automatically sets the city name and switches display mode to "City".
  3. Auto-location (Confirm, coordinates path only): Enable IP-based auto-detection? Skipped after city search since the user explicitly chose a location.
  4. Location display (Select): Coordinates only / City name / Both (city + coordinates)
  5. City name language (Input): Language code for city names in the HUD (default: "auto" = system locale)
  6. Hide location (Confirm): Hide coordinates in the UI?
  7. Temperature unit (Select): Celsius / Fahrenheit
  8. Wind speed unit (Select): km/h / m/s / mph / knots
  9. Precipitation unit (Select): mm / inch
  10. Hide HUD (Confirm): Hide the status line?
  11. Silent mode (Confirm): Suppress non-error output?

A single reqwest::Client is created once and reused across city search calls within the wizard.

Phase 3 -- Save and finish

Validates the final config, serializes with toml::to_string_pretty(), writes to the config path (creating parent directories if needed), and prints a success message.

City search via Geocoding API

When the user chooses "Search by city name", the wizard:

  1. Prompts for a city name (minimum 2 characters).
  2. Calls the Open-Meteo Geocoding API (GET https://geocoding-api.open-meteo.com/v1/search?name=...&count=10&language=en).
  3. Displays results as a fuzzy-searchable list formatted as City, Region, Country (lat, lon) - pop. N.
  4. Includes a "-- Search again --" option at the bottom to retry with a different query.
  5. Non-ASCII city names (e.g. "Москва", "München") are properly percent-encoded via reqwest::Url::parse_with_params.
  6. On selection, populates city, sets display = "city", and preserves the existing city_name_language setting.

Sample output

Welcome banner and location prompt:

┌───────────────────────────────────────┐
│      Welcome to weathr setup!         │
│  Let's configure your weather app.    │
└───────────────────────────────────────┘

  Tip: existing values are shown as defaults. Press Enter to keep them.

── Location ────────────────────────────

? How would you like to set your location?
> Enter coordinates (latitude/longitude)
  Search by city name
  Use auto-detection (IP-based)

City search results:

? City name: Berlin
  Searching for "Berlin"...
? Select a city
> Berlin, Berlin, Germany (52.5244, 13.4105) - pop. 3426354
  Berlin, New Hampshire, United States (44.4688, -71.1854) - pop. 9293
  Berlin, Wisconsin, United States (43.9681, -88.9435) - pop. 5420
  -- Search again --

Location display and units:

? How should the location be displayed in the HUD? [current: city]
> Coordinates only
  City name
  Both (city + coordinates)
? City name language code ("auto" = system locale) [current: auto]: ↵
? Hide location coordinates in the UI? [current: no] (y/N)

── Units ───────────────────────────────

? Temperature unit [current: celsius]
> Celsius (°C)
  Fahrenheit (°F)

Completion:

── All set! ────────────────────────────

  Config saved to /home/user/.config/weathr/config.toml

  Run weathr to start!

Edge cases covered

Scenario Behavior
Config directory doesn't exist Created automatically by Config::save()
Config file doesn't exist Uses Config::default(), creates file on save
Config file exists but is corrupted Warns via stderr, falls back to Config::default()
Existing valid config All current values shown as defaults; Enter keeps them
Invalid lat/lon input (e.g. 999) Rejected by validator, re-prompts
City search returns no results Prints error, loops back to city name prompt
City search -- user wants different query "-- Search again --" option in results list
Network failure during city search Prints error, breaks out of search loop, keeps current coordinates
Non-ASCII city names (Cyrillic, CJK, accented) Proper percent-encoding via reqwest::Url
User presses Escape to cancel (any prompt) Prints "Setup cancelled.", exits with code 0 (not an error)
Hard error during onboarding Prints error message, exits with code 1

Backward compatibility

  • All existing CLI flags and behavior are unchanged when no subcommand is given.
  • Config loading precedence (local ./config.toml > XDG path) is unchanged.
  • All existing tests pass. No test modifications needed.

Screenshots

Initial

image

City search

image

Example of units selection

image

Full onboarding log after completion

image

@lstpsche lstpsche force-pushed the feat/onboarding-wizard branch 3 times, most recently from 57977a6 to 7e84aeb Compare February 22, 2026 19:43
Adds `onboard` (alias `init`) subcommand with a styled interactive
setup wizard that creates the config directory/file and walks the user
through preferences.
- Upgrade dialoguer 0.11 -> 0.12, console 0.15 -> 0.16
- Use interact_opt() on all Confirm prompts for consistent
  Escape-to-cancel behavior
- Replace custom urlencoded() with url::Url::parse_with_params
  for reliable URL construction and encoding
- Skip auto-location prompt after city search (intent is clear)
- Use &Path instead of &PathBuf in Config::save() and load_from_path()
… harden config

- Fix city search discarding `city`, `display`, and `city_name_language` fields
- Add prompts for location display mode and city name language
- Warn on corrupted config instead of silently replacing with defaults
- Make `Config::save()` create parent directories
- Replace `console` with `crossterm::style::Stylize` for styling
- Replace `url::Url` with `reqwest::Url`, remove redundant deps
- Reuse HTTP client across city search calls
- Install rustls provider early in `main()` to fix bare `cargo run` panic
@lstpsche lstpsche force-pushed the feat/onboarding-wizard branch from 235868b to e7e4719 Compare February 23, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant