Private
Public Access
0
0

progress on context composition

This commit is contained in:
2026-05-17 06:55:00 -04:00
parent 22f3b9f33a
commit c1487d32bb
3 changed files with 72 additions and 12 deletions
+21 -1
View File
@@ -2933,9 +2933,25 @@ class AppController:
self.ui_file_paths = [f.path for f in preset.files]
self.screenshots = list(preset.screenshots)
self._save_active_project()
# We need to tell gui_2 to populate the full FileItem state from the preset,
# which it does in _handle_refresh_from_project. But we also need to pass the detailed properties.
# We will let the project_manager handle merging in the preset files via configuration next turn.
# Wait, project manager doesn't load preset files into self.files automatically here.
# Let's write the preset files into self.project["files"] directly.
import copy
self.project.setdefault("files", {})["paths"] = [
{
"path": f.path,
"view_mode": f.view_mode,
"custom_slices": copy.deepcopy(f.custom_slices),
"ast_mask": copy.deepcopy(f.ast_mask),
"ast_signatures": getattr(f, "ast_signatures", False),
"ast_definitions": getattr(f, "ast_definitions", False)
} for f in preset.files
]
self._save_active_project()
return preset
def _cb_load_track(self, track_id: str) -> None:
"""
[C: src/gui_2.py:App._render_mma_track_browser]
@@ -3445,6 +3461,10 @@ class AppController:
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id)
flat.setdefault("files", {})["paths"] = self.context_files
# Configure MCP so that aggregate.py can fetch skeletons for external files (e.g. gencpp)
file_dicts = [f.to_dict() if hasattr(f, 'to_dict') else {"path": str(f)} for f in self.context_files]
mcp_client.configure(file_dicts, [self.active_project_root] if self.active_project_root else None)
persona = self.personas.get(self.ui_active_persona)
strategy = persona.aggregation_strategy if persona else "auto"
+44 -10
View File
@@ -153,12 +153,15 @@ class App:
for f in self.context_files:
p = f.path if hasattr(f, 'path') else str(f)
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm))
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(self.screenshots))
self.controller.save_context_preset(preset)
self.ui_new_context_preset_name = ""
self.show_missing_files_modal = False
self.show_missing_files_modal = False
self.controller._predefined_callbacks['get_app_debug_info'] = lambda: self.app_debug_info
self.controller._gettable_fields['app_debug_info'] = 'app_debug_info'
self.controller._predefined_callbacks['save_context_preset_force'] = _save_context_preset_force
@@ -3011,6 +3014,9 @@ def render_ast_inspector_modal(app: App) -> None:
imgui.same_line()
imgui.text(f"[{kind}] {name}")
if imgui.is_item_hovered():
app._hovered_ast_node = full_path
# Calculate space left and align radio buttons to the right
btn_width = 150 # Estimated width of the 3 radio buttons
avail_width = imgui.get_content_region_avail().x
@@ -3061,6 +3067,9 @@ def render_ast_inspector_modal(app: App) -> None:
elif mode == 'sig':
# Blue, alpha 0.2
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(0, 0, 255, 0.2)))
elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None):
# Yellow, alpha 0.3 for hover
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(255, 255, 0, 0.3)))
imgui.text(f"{line_num:4} | {line_text}")
imgui.end_child()
@@ -4126,7 +4135,7 @@ def render_text_viewer_window(app: App) -> None:
"""Renders the standalone text/code/markdown viewer window."""
if not app.show_text_viewer: return
imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
expanded, opened = imgui.begin(f"Text Viewer - {app.text_viewer_title}", app.show_text_viewer)
expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer", app.show_text_viewer)
app.show_text_viewer = bool(opened)
if not opened:
app.ui_editing_slices_file = None
@@ -4148,20 +4157,45 @@ def render_text_viewer_window(app: App) -> None:
if imgui.button("Clear Selection"): app._slice_sel_start = -1; app._slice_sel_end = -1
imgui.same_line()
if imgui.button("Auto-Populate AST Slices"): app._populate_auto_slices(app.ui_editing_slices_file)
imgui.same_line()
if imgui.button("Edit Tags"): imgui.open_popup("Edit Context Tags")
if imgui.begin_popup("Edit Context Tags"):
tags = app.controller.project.setdefault("context_tags", ["auto-ast", "bug", "feature", "important"])
imgui.text("Context Tags")
imgui.separator()
to_remove_tag = -1
for i, t in enumerate(tags):
imgui.push_id(f"tag_{i}")
imgui.set_next_item_width(150)
ch, new_t = imgui.input_text("##t", t)
if ch: tags[i] = new_t
imgui.same_line()
if imgui.button("X"): to_remove_tag = i
imgui.pop_id()
if to_remove_tag != -1: tags.pop(to_remove_tag)
if imgui.button("+ Add Tag"): tags.append("new-tag")
if imgui.button("Close"): imgui.close_current_popup()
imgui.end_popup()
to_remove = -1
tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"])
for idx, slc in enumerate(app.ui_editing_slices_file.custom_slices):
imgui.push_id(f"slc_row_{idx}"); imgui.text(f"Slice {idx+1}: {slc['start_line']}-{slc['end_line']}"); imgui.same_line()
imgui.set_next_item_width(100); changed_tag, new_tag = imgui.input_text("Tag", slc.get('tag', ''))
if changed_tag: slc['tag'] = new_tag
imgui.same_line(); imgui.set_next_item_width(200); changed_comm, new_comm = imgui.input_text("Comment", slc.get('comment', ''))
current_tag = slc.get('tag', '')
if current_tag not in tags and current_tag: tags.append(current_tag)
tag_idx = tags.index(current_tag) if current_tag in tags else 0
imgui.set_next_item_width(150)
ch_tag, new_tag_idx = imgui.combo("Category/Tag", tag_idx, tags)
if ch_tag: slc['tag'] = tags[new_tag_idx]
imgui.same_line(); imgui.set_next_item_width(300); changed_comm, new_comm = imgui.input_text("Note/Comment", slc.get('comment', ''))
if changed_comm: slc['comment'] = new_comm
imgui.same_line()
if imgui.button("Remove"): to_remove = idx
imgui.pop_id()
if to_remove != -1: app.ui_editing_slices_file.custom_slices.pop(to_remove)
imgui.separator()
if imgui.button("Copy"): imgui.set_clipboard_text(app.text_viewer_content)
imgui.separator()
if imgui.button("Copy"): imgui.set_clipboard_text(app.text_viewer_content)
imgui.same_line(); _, app.text_viewer_wrap = imgui.checkbox("Word Wrap", app.text_viewer_wrap)
imgui.separator()
renderer = markdown_helper.get_renderer(); tv_type = getattr(app, "text_viewer_type", "text")
+7 -1
View File
@@ -827,12 +827,15 @@ class ContextFileEntry:
path: str
view_mode: str = "summary"
custom_slices: list = field(default_factory=list)
ast_mask: dict = field(default_factory=dict)
ast_signatures: bool = False
ast_definitions: bool = False
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, "custom_slices": self.custom_slices}
return {"path": self.path, "view_mode": self.view_mode, "custom_slices": self.custom_slices, "ast_mask": self.ast_mask, "ast_signatures": self.ast_signatures, "ast_definitions": self.ast_definitions}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "ContextFileEntry":
@@ -843,6 +846,9 @@ class ContextFileEntry:
path=data.get("path", ""),
view_mode=data.get("view_mode", "summary"),
custom_slices=data.get("custom_slices", []),
ast_mask=data.get("ast_mask", {}),
ast_signatures=data.get("ast_signatures", False),
ast_definitions=data.get("ast_definitions", False),
)
@dataclass