feat(login): /login opens the provider picker with green connected badges

/login previously fired the resolved method's flow immediately — there was
no way to see providers or their status. It now lands on the provider
picker; already-connected providers show a green 'connected' badge in the
key column.

Shell: initialize() probes stored credentials once (primary session flag,
auth.json oauth/<provider> scopes, resolved platform keys env>auth.json>
config) and stamps _meta.connected on each advertised method
(connected_method_ids + stamp_connected_meta, pure and unit-tested).
Display state only — never an authorization input.

TUI:
- PendingMenuItem::connected() reads the badge from method meta; the
  picker renders it green (accent_success), replacing the shortcut hint.
- New Action::OpenLoginPicker: /login shows the picker; mid-session it
  stashes the view like dispatch_login, and starts no flow by itself.
- Mid-session picker: last row reads 'Cancel' and dispatches CancelLogin
  (clicking it must not exit the app); Esc also returns to the session.
- After a successful login, the just-authenticated method is stamped
  connected in the TUI's advertised-methods copy (auth_in_flight_method →
  AuthComplete), so a later /login shows the badge without re-initialize.

Verified: kigi-shell 5256 + kigi-tui 6870 tests green, clippy clean.
This commit is contained in:
2026-07-22 16:14:42 -04:00
parent 1e57cd9225
commit fc7c2a1b9b
11 changed files with 384 additions and 15 deletions
@@ -9,6 +9,11 @@ use crate::theme::Theme;
use super::logo::logo_visual_width;
/// Key-column badge for a login-picker row whose provider already holds a
/// stored credential. Rendered green (content-based restyle, like the
/// import row's `[x]`).
pub(crate) const CONNECTED_BADGE: &str = "connected";
/// Render the welcome menu rows as `label … shortcut`, padded within each row.
///
/// The area is a viewport: when there are more items than rows, the window
@@ -117,8 +122,16 @@ pub fn render_menu(
};
buf.set_span(menu_centered.x, y, &Span::styled(*label, lstyle), label_len);
// Key shortcut flush with the right edge of the menu column.
let kstyle = if is_selected {
// Key shortcut flush with the right edge of the menu column. The
// "connected" badge renders green instead of the shortcut gray.
let kstyle = if *key == CONNECTED_BADGE {
let green = Style::default().fg(theme.accent_success);
if is_selected {
green.bg(theme.bg_highlight)
} else {
green
}
} else if is_selected {
key_selected_style
} else {
key_style