feat(gui): one-click Close in its own bottom sidebar section

Close is now a direct shutdown trigger: visiting the Close page (the
sidebar entry) fires shutdown_app() immediately — no confirm step, no
intermediate body. The farewell overlay paints and os._exit(0) lands
~1s later from a daemon thread.

Layout: Close moved into its own bottom-of-sidebar section so the
destructive action is visually separated from Account/Activate.

- New shutdown_app() in components/_legacy.py replaces quit_button.
  os._exit thread is skipped when "pytest" is in sys.modules so the
  test suite doesn't suicide on rendering 99_Close.
- pages/99_Close.py shrinks to set_page_config + chrome + shutdown_app.
- app.py nav grows a new "Close" section header (new
  nav.section_close key in en/es packs) pinned at the bottom of the
  navigation dict.

Tests updated:
- TestQuitButtonRenders → TestClosePageShutsDownImmediately.
  Assert the shutdown caption renders + no confirm button exists.
- test_smoke EXPECTED_SUBSTRINGS["99_Close"] now pins
  "Shutting down" / "Cerrando" (the visible page body) instead of
  the removed page title.

2008 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 20:17:14 +00:00
parent ff2eaeb6c4
commit c568aec8a7
8 changed files with 59 additions and 63 deletions

View File

@@ -198,12 +198,17 @@ def _build_navigation() -> dict[str, list]:
)
account_header = _t("nav.section_account") or "Account"
close_header = _t("nav.section_close") or "Close"
# Close lives in its own section pinned at the very bottom of the
# sidebar — its own click is the shutdown, so we visually separate
# it from the navigable pages above to reduce mis-click risk.
return {
"": [home],
section_label("cleaners"): by_section["cleaners"],
section_label("transformations"): by_section["transformations"],
section_label("automations"): by_section["automations"],
account_header: [activate, close],
account_header: [activate],
close_header: [close],
}