When switching projects, the previous implementation ran the entire
save/load/refresh sequence on the main thread. With large project files
or slow disks, this caused the UI to freeze for several seconds.
Fix:
- _switch_project now returns immediately after setting flags; the
actual work runs in a daemon thread (_do_project_switch)
- New is_project_stale() property returns True while a switch is queued
or running; the GUI renders an amber/yellow tint overlay to signal
the controller state lags the user's last click
- AI ops are gated: _api_generate returns HTTP 409, _handle_generate_send
and _handle_md_only early-return with ai_status feedback, all when
is_project_stale() is true
- Queued switches (clicking project A then B in rapid succession) are
coalesced: B replaces A as the target; once A completes, B is
triggered automatically via the finally branch in _do_project_switch
- New state fields: _project_switch_in_progress, _project_switch_pending_path,
_project_switch_thread, _project_switch_lock
- AppController state class attributes use hasattr guard for _app to
keep the controller usable standalone in tests/headless mode
UX:
- Render loop keeps drawing during the switch
- User can still scroll, switch tabs, browse files
- Amber tint + popup explains what's happening and that AI ops are paused
- ai_status shows the target project name
Tests:
- _wait_for_switch helper added for the new async switch flow
- All 7 existing switch tests updated to call _wait_for_switch
- 2 new tests:
- test_switch_project_non_blocking: verifies _switch_project returns
in <0.2s and is_project_stale() is True during the switch
- test_api_generate_blocked_while_stale: verifies _api_generate
raises HTTPException(409) while a switch is in progress
All 33 related tests pass.
When switching projects, the previous project's context_files remained
visible in the Context Composition panel because the controller's
self.context_files list was not reloaded from the new project's TOML
files.paths entry.
Fix in _refresh_from_project:
- After loading self.files from the project TOML, populate
self.context_files with deep copies of those FileItem objects
- Reset self._app.ui_selected_context_files to match the new project's
auto_aggregate set
- Guard the _app access with hasattr so the controller is usable
standalone (in tests, headless mode, etc.) without an attached App
Test: 1 new test in tests/test_project_switch_persona_preset.py
- test_switch_project_resets_context_files: switches from project_a
(forth + gte_hello files) to project_b (gencpp timing files) and
asserts context_files contains ONLY project_b's files
Two fixes for the regression introduced in b92daef3 (and an additional
hardening for the persona->context_preset stale-reference class of bug):
1. Regression: persona_manager was missing on first project load.
_load_active_project creates preset_manager and tool_preset_manager
but did not create persona_manager, so the new
self.personas = self.persona_manager.load_all() line in
_refresh_from_project raised AttributeError on app startup before
the post-_load_active_project persona_manager creation could run.
Fix: create self.persona_manager in _load_active_project alongside
the other managers, so the manager is available when
_refresh_from_project runs.
2. Stale reference: persona's context_preset field pointed to a
preset (e.g. 'GTE') that no longer exists in the project, causing
load_context_preset to raise KeyError and crash the persona
selector panel (which triggered the cascading 'Missing End()' imgui
assertion).
Fix: wrap the load_context_preset call in render_persona_selector_panel
with try/except KeyError, surface the error in app.ai_status, and
clear app.ui_active_context_preset to keep the GUI state consistent.
Tests: 2 new tests in tests/test_project_switch_persona_preset.py
- test_load_active_project_creates_persona_manager (regression guard)
- test_load_context_preset_missing_raises_keyerror (verifies the
contract that load_context_preset raises for missing names; the
GUI layer is now responsible for catching the error)
When switching projects, the previous project's project-specific persona and
presets remained selected in the AI Settings panel because:
1. self.personas was not reloaded after switching project root
2. self.ui_active_persona / tool_preset / bias_profile / project_preset_name
were not validated against the newly-loaded personas/presets
Fix:
- Reload self.personas from self.persona_manager in _refresh_from_project
- Validate each active selection and reset to None/empty if it does not
exist in the newly-loaded manager dictionaries
- Push the active tool preset and bias profile to ai_client after the swap
- Initialize self.ui_active_bias_profile in class attribute block (was only
set later in __init__, causing AttributeError on direct attribute access)
Tests: 4 new tests in tests/test_project_switch_persona_preset.py verify
the reset behavior for persona, preset, tool preset, and global preset
preservation.
Previously, context (files, screenshots) was always sent with every message,
even on subsequent messages where the AI provider already had the context
from the first message via its history mechanism.
This change:
- Detects if the discussion has any AI responses already
- Only sends md_content (stable_md) on the first message
- Subsequent messages pass empty string for md_content to avoid redundant sending
- Context now properly goes in md_content parameter, not crammed into user_message
The fix is in _api_generate() in src/app_controller.py