Refactor UI lifecycle functions into AppLifecycle with persistent ownership
Summary
Replaced free lifecycle functions with an AppLifecycle class in src/tunacode/ui/lifecycle.py.
Updated TextualReplApp to persist a single lifecycle instance (self._lifecycle) and reuse that instance across on_mount and on_unmount.
This removes function-level indirection and makes REPL startup idempotent through _repl_started inside lifecycle state.
Files changed
src/tunacode/ui/PLAN.mdsrc/tunacode/ui/lifecycle.pysrc/tunacode/ui/app.py
Behavior notes
- Theme and session metadata initialization stay in mount flow.
- Setup dismissal still proceeds into REPL startup.
- Slopgotchi timer ownership remains on
TextualReplApp._slopgotchi_timer, with lifecycle start/stop helpers managing it. on_unmountnow fails loudly if lifecycle was never initialized.
Validation
uv run ruff check src/tunacode/ui/app.py src/tunacode/ui/lifecycle.py✅uv run pytest tests/unit/ui/test_supported_themes.py -q✅uv run ruff check .✅