Private
Public Access
0
0

feat(models): Implement ContextPreset and ContextFileEntry

This commit is contained in:
2026-05-16 05:05:22 -04:00
parent c88330cc48
commit 8467cdd525
3 changed files with 75 additions and 53 deletions
+1 -7
View File
@@ -568,7 +568,7 @@ class App:
for f in self.context_files:
path = f.path if hasattr(f, 'path') else str(f)
view_mode = f.view_mode if hasattr(f, 'view_mode') else 'summary'
preset_files.append(models.FileViewPreset(path=path, view_mode=view_mode))
preset_files.append(models.ContextFileEntry(path=path, view_mode=view_mode))
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(self.screenshots))
self.controller.project['context_presets'][name] = preset.to_dict()
@@ -945,7 +945,6 @@ class App:
threading.Thread(target=_stats_worker, daemon=True).start()
return total_lines, total_ast
def _close_vscode_diff(self) -> None:
if hasattr(self, '_vscode_diff_process') and self._vscode_diff_process:
try:
@@ -997,7 +996,6 @@ class App:
self._vscode_diff_process = result
except Exception as e:
self._patch_error_message = str(e)
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
"""
@@ -1117,8 +1115,6 @@ def main() -> None:
if __name__ == "__main__":
main()
#region: Main Interface
def render_main_interface(app: App) -> None:
render_error_tint(app)
app.perf_monitor.start_frame()
@@ -1215,8 +1211,6 @@ def render_custom_title_bar(app: App) -> None:
# Controls are now embedded in _show_menus.
pass
#endregion: Main Interface
#region: Diagnostics & Analytics
def render_usage_analytics_panel(app: App) -> None:
+18 -7
View File
@@ -823,22 +823,27 @@ class WorkspaceProfile:
)
@dataclass
class FileViewPreset:
class ContextFileEntry:
path: str
view_mode: str = "summary"
custom_slices: list = field(default_factory=list)
def to_dict(self) -> dict[str, Any]:
"""
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
"""
return {"path": self.path, "view_mode": self.view_mode}
return {"path": self.path, "view_mode": self.view_mode, "custom_slices": self.custom_slices}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "FileViewPreset":
def from_dict(cls, data: dict[str, Any]) -> "ContextFileEntry":
"""
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
"""
return cls(path=data.get("path", ""), view_mode=data.get("view_mode", "summary"))
return cls(
path=data.get("path", ""),
view_mode=data.get("view_mode", "summary"),
custom_slices=data.get("custom_slices", []),
)
@dataclass
class NamedViewPreset:
@@ -868,14 +873,19 @@ class NamedViewPreset:
@dataclass
class ContextPreset:
name: str
files: list[FileViewPreset] = field(default_factory=list)
files: list[ContextFileEntry] = field(default_factory=list)
screenshots: list[str] = field(default_factory=list)
description: str = ""
def to_dict(self) -> dict[str, Any]:
"""
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
"""
return {"files": [f.to_dict() for f in self.files], "screenshots": self.screenshots}
return {
"files": [f.to_dict() for f in self.files],
"screenshots": self.screenshots,
"description": self.description,
}
@classmethod
def from_dict(cls, name: str, data: dict[str, Any]) -> "ContextPreset":
@@ -885,8 +895,9 @@ class ContextPreset:
files_data = data.get("files", [])
return cls(
name=name,
files=[FileViewPreset.from_dict(f) if isinstance(f, dict) else FileViewPreset(path=str(f)) for f in files_data],
files=[ContextFileEntry.from_dict(f) if isinstance(f, dict) else ContextFileEntry(path=str(f)) for f in files_data],
screenshots=data.get("screenshots", []),
description=data.get("description", ""),
)
#region: MCP Config
+56 -39
View File
@@ -1,45 +1,62 @@
import pytest
from src.models import ContextPreset, FileViewPreset
from src.models import ContextPreset, ContextFileEntry
def test_file_view_preset_serialization():
p = FileViewPreset(path="test.py", view_mode="skeleton")
d = p.to_dict()
assert d == {"path": "test.py", "view_mode": "skeleton"}
p2 = FileViewPreset.from_dict(d)
assert p2.path == "test.py"
assert p2.view_mode == "skeleton"
def test_context_file_entry_serialization():
p = ContextFileEntry(path="test.py", view_mode="skeleton")
d = p.to_dict()
# Check for default custom_slices
assert d == {"path": "test.py", "view_mode": "skeleton", "custom_slices": []}
p2 = ContextFileEntry.from_dict(d)
assert p2.path == "test.py"
assert p2.view_mode == "skeleton"
assert p2.custom_slices == []
# Test with custom_slices
p3 = ContextFileEntry(path="test.py", view_mode="summary", custom_slices=["1-10"])
d3 = p3.to_dict()
assert d3["custom_slices"] == ["1-10"]
p4 = ContextFileEntry.from_dict(d3)
assert p4.custom_slices == ["1-10"]
def test_context_preset_serialization():
f1 = FileViewPreset(path="a.py", view_mode="full")
f2 = FileViewPreset(path="b.py", view_mode="summary")
preset = ContextPreset(
name="test_preset",
files=[f1, f2],
screenshots=["shot1.png"]
)
d = preset.to_dict()
assert len(d["files"]) == 2
assert d["files"][0]["path"] == "a.py"
assert d["screenshots"] == ["shot1.png"]
p2 = ContextPreset.from_dict("test_preset", d)
assert p2.name == "test_preset"
assert len(p2.files) == 2
assert p2.files[0].view_mode == "full"
assert p2.screenshots == ["shot1.png"]
f1 = ContextFileEntry(path="a.py", view_mode="full")
f2 = ContextFileEntry(path="b.py", view_mode="summary", custom_slices=["5-15"])
preset = ContextPreset(
name="test_preset",
files=[f1, f2],
screenshots=["shot1.png"],
description="Test description"
)
d = preset.to_dict()
assert len(d["files"]) == 2
assert d["files"][0]["path"] == "a.py"
assert d["files"][1]["custom_slices"] == ["5-15"]
assert d["screenshots"] == ["shot1.png"]
assert d["description"] == "Test description"
p2 = ContextPreset.from_dict("test_preset", d)
assert p2.name == "test_preset"
assert p2.description == "Test description"
assert len(p2.files) == 2
assert p2.files[0].view_mode == "full"
assert p2.files[1].custom_slices == ["5-15"]
assert p2.screenshots == ["shot1.png"]
def test_context_preset_from_dict_legacy():
# Test handling of legacy string paths in preset files
d = {
"files": ["legacy.py", {"path": "new.py", "view_mode": "skeleton"}],
"screenshots": []
}
preset = ContextPreset.from_dict("legacy_test", d)
assert len(preset.files) == 2
assert preset.files[0].path == "legacy.py"
assert preset.files[0].view_mode == "summary" # Default
assert preset.files[1].path == "new.py"
assert preset.files[1].view_mode == "skeleton"
# Test handling of legacy string paths in preset files and missing description/custom_slices
d = {
"files": ["legacy.py", {"path": "new.py", "view_mode": "skeleton"}],
"screenshots": []
}
preset = ContextPreset.from_dict("legacy_test", d)
assert len(preset.files) == 2
assert preset.files[0].path == "legacy.py"
assert preset.files[0].view_mode == "summary" # Default
assert preset.files[0].custom_slices == []
assert preset.files[1].path == "new.py"
assert preset.files[1].view_mode == "skeleton"
assert preset.files[1].custom_slices == []
assert preset.description == ""