diff --git a/conductor/directives/reset_session_preserves_project_path/meta.md b/conductor/directives/reset_session_preserves_project_path/meta.md new file mode 100644 index 00000000..db192b66 --- /dev/null +++ b/conductor/directives/reset_session_preserves_project_path/meta.md @@ -0,0 +1,7 @@ +# reset_session_preserves_project_path + +## v1 + +**Why this iteration:** Lifted verbatim from `docs/guide_state_lifecycle.md:217-244 (§3 Reset)`. +**Source:** `docs/guide_state_lifecycle.md:217-244 (§3 Reset)` +**Lifted:** 2026-07-02 (Phase A expansion harvest; user directive 2026-07-02) diff --git a/conductor/directives/reset_session_preserves_project_path/v1.md b/conductor/directives/reset_session_preserves_project_path/v1.md new file mode 100644 index 00000000..32a6dd56 --- /dev/null +++ b/conductor/directives/reset_session_preserves_project_path/v1.md @@ -0,0 +1,27 @@ +## _handle_reset_session field-clear contract + +`src/app_controller.py:3286-3356 _handle_reset_session` is the **nuclear** reset, called from the Reset Session button in the message panel. It clears 30+ state buckets: + +- **AI client**: `ai_client.reset_session()` + `clear_comms_log()` +- **Tool stats**: `_tool_log.clear()`, `_tool_stats.clear()`, `_comms_log.clear()` +- **Discussion**: `self.disc_entries.clear()`; for each discussion in project: `discussions[d_name]["history"] = []` +- **Files**: `self.files.clear()`, `self.context_files.clear()` +- **Tracks**: `self.tracks.clear()` +- **Project dict (full replacement)**: `self.project = project_manager.default_project(...)` +- **Project paths**: `self.project_paths = []` +- **AI status**: `ai_status = "session reset"`, `ai_response = ""` +- **UI inputs**: `ui_ai_input = ""`, `ui_manual_approve = False`, `ui_auto_add_history = False` +- **MMA**: `active_track = None`, `active_tier = None`, `mma_status = "idle"`, ... +- **Provider/model**: `_current_provider = "gemini"`, `_current_model = "gemini-2.5-flash-lite"` +- **Locks + queues**: `_pending_history_adds.clear()`, `_api_event_queue.clear()`, `_pending_gui_tasks.clear()` +- **Prompts**: `ui_use_default_base_prompt = True`, all 3 system prompts = `''` +- **Persona/tool settings**: `ui_active_persona = ''`, `ui_active_tool_preset = None`, ... +- **Generation params**: `temperature = 0.0`, `top_p = 1.0`, `max_tokens = 8192` + +## What It Does NOT Touch (the preserve list) + +| Field | Why preserved | +|---|---| +| `self.active_project_path` | `_do_project_switch` writes to this path; clearing it would cause OSError on next switch and an infinite re-switch loop. The 2026-06-08 regression test `test_context_sim_live` documents this. | +| `self.history` (the `HistoryManager`) | The undo stack survives a reset. Ctrl+Z after a reset can restore the pre-reset state. This may be a bug or a feature. | +| The on-disk `manual_slop.toml` | The saved project TOML is not deleted or rewritten. Switching projects after reset reloads from disk. | diff --git a/conductor/directives/undo_redo_100_snapshot_capacity/meta.md b/conductor/directives/undo_redo_100_snapshot_capacity/meta.md new file mode 100644 index 00000000..4efa1fc6 --- /dev/null +++ b/conductor/directives/undo_redo_100_snapshot_capacity/meta.md @@ -0,0 +1,7 @@ +# undo_redo_100_snapshot_capacity + +## v1 + +**Why this iteration:** Lifted verbatim from `docs/guide_state_lifecycle.md:56-117 (§1 Undo/Redo: HistoryManager + UISnapshot)`. +**Source:** `docs/guide_state_lifecycle.md:56-117 (§1 Undo/Redo: HistoryManager + UISnapshot)` +**Lifted:** 2026-07-02 (Phase A expansion harvest; user directive 2026-07-02) diff --git a/conductor/directives/undo_redo_100_snapshot_capacity/v1.md b/conductor/directives/undo_redo_100_snapshot_capacity/v1.md new file mode 100644 index 00000000..07eed9b4 --- /dev/null +++ b/conductor/directives/undo_redo_100_snapshot_capacity/v1.md @@ -0,0 +1,36 @@ +## HistoryManager — the 100-snapshot undo/redo stack + +`src/history.py:71 HistoryManager` is a 100-snapshot capacity stack with the following API: + +- `push(state, description)` — appends; clears the redo stack; pops the oldest if capacity exceeded. +- `undo(current_state, current_description)` — moves current state to redo stack; returns the top of the undo stack. +- `redo(...)` — inverse of undo. +- `jump_to_undo(index, current_state, current_description)` — time-travels to any past snapshot, moving subsequent states to the redo stack. +- `can_undo`, `can_redo` properties +- `get_history()` — returns `[{description, timestamp}, ...]` for the History List view + +The `max_capacity=100` is the default and is sufficient for a 5-second window of rapid typing or a longer session of infrequent edits. + +## The Push Trigger — debounced change detection at render frame + +The undo stack is **not** pushed on every keystroke. It's pushed via debounced change-detection at the start of every render frame: + +```python +current = self._take_snapshot() +if self._last_ui_snapshot is None: + self._last_ui_snapshot = current + return + +changed = ( + current.ai_input != self._last_ui_snapshot.ai_input or + ... + len(current.disc_entries) != len(self._last_ui_snapshot.disc_entries) or + ... +) + +if changed: + self.history.push(current, description="") + self._last_ui_snapshot = current +``` + +The check is at the start of every render frame. `copy.deepcopy(self.disc_entries)` is the most expensive part — O(N) where N is the entry count. The full snapshot push only happens when a change is detected.