more gui_2.py

This commit is contained in:
ed
2026-06-13 12:00:33 -04:00
parent b61a2db01d
commit 9fc2c21e82
+104 -132
View File
@@ -3603,7 +3603,7 @@ def render_ast_inspector_modal(app: App) -> None:
if getattr(app, 'show_structural_editor_modal', False): if getattr(app, 'show_structural_editor_modal', False):
imgui.open_popup('Structural File Editor') imgui.open_popup('Structural File Editor')
app.show_structural_editor_modal = False app.show_structural_editor_modal = False
imgui.set_next_window_size(imgui.ImVec2(1400, 900), imgui.Cond_.first_use_ever) imgui.set_next_window_size(imgui.ImVec2(1400, 900), imgui.Cond_.first_use_ever)
expanded, opened = imgui.begin_popup_modal('Structural File Editor', True, imgui.WindowFlags_.none) expanded, opened = imgui.begin_popup_modal('Structural File Editor', True, imgui.WindowFlags_.none)
if opened: if opened:
@@ -3613,19 +3613,19 @@ def render_ast_inspector_modal(app: App) -> None:
else: else:
f_item = app.ui_editing_slices_file f_item = app.ui_editing_slices_file
f_path = f_item.path if hasattr(f_item, "path") else str(f_item) f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
if f_path != getattr(app, '_cached_ast_file_path', None): if f_path != getattr(app, '_cached_ast_file_path', None):
outline = "" outline = ""
try: try:
proj_dir = str(Path(app.controller.active_project_path).parent.resolve()) if getattr(app, 'controller', None) and app.controller.active_project_path else None proj_dir = str(Path(app.controller.active_project_path).parent.resolve()) if getattr(app, 'controller', None) and app.controller.active_project_path else None
mcp_client.configure([{"path": f_path}], [proj_dir] if proj_dir else None) mcp_client.configure([{"path": f_path}], [proj_dir] if proj_dir else None)
if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path) if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path)
elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path) elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path)
elif f_path.lower().endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(f_path) elif f_path.lower().endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(f_path)
except Exception as e: except Exception as e:
outline = f"Error fetching outline: {e}" outline = f"Error fetching outline: {e}"
app._cached_ast_nodes = [] app._cached_ast_nodes = []
import re #TODO(Ed): Review local import import re #TODO(Ed): Review local import
pattern = re.compile(r'^(\s*)\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)') pattern = re.compile(r'^(\s*)\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)')
@@ -3639,34 +3639,34 @@ def render_ast_inspector_modal(app: App) -> None:
stack.append((indent, name)) stack.append((indent, name))
full_path = '::'.join([s[1] for s in stack]) full_path = '::'.join([s[1] for s in stack])
app._cached_ast_nodes.append({ app._cached_ast_nodes.append({
'indent': indent, 'indent': indent,
'kind': kind, 'kind': kind,
'name': name, 'name': name,
'full_path': full_path, 'full_path': full_path,
'start_line': int(start_ln), 'start_line': int(start_ln),
'end_line': int(end_ln) 'end_line': int(end_ln)
}) })
try: try:
content = mcp_client.read_file(f_path) content = mcp_client.read_file(f_path)
app._cached_ast_file_lines = content.splitlines() app._cached_ast_file_lines = content.splitlines()
app.text_viewer_content = content app.text_viewer_content = content
except Exception: except Exception:
app._cached_ast_file_lines = ["Error loading file content."] app._cached_ast_file_lines = ["Error loading file content."]
app.text_viewer_content = "Error loading file content." app.text_viewer_content = "Error loading file content."
app._cached_ast_file_path = f_path app._cached_ast_file_path = f_path
imgui.text(f"Editing Structure: {f_path}") imgui.text(f"Editing Structure: {f_path}")
imgui.separator() imgui.separator()
avail = imgui.get_content_region_avail() avail = imgui.get_content_region_avail()
table_height = max(100.0, avail.y - imgui.get_frame_height_with_spacing() * 2 - 20) table_height = max(100.0, avail.y - imgui.get_frame_height_with_spacing() * 2 - 20)
if imgui.begin_table('structure_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v, imgui.ImVec2(0, table_height)): if imgui.begin_table('structure_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v, imgui.ImVec2(0, table_height)):
imgui.table_setup_column("AST & Slices", imgui.TableColumnFlags_.width_fixed, 400) imgui.table_setup_column("AST & Slices", imgui.TableColumnFlags_.width_fixed, 400)
imgui.table_setup_column("Content Preview", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("Content Preview", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row() imgui.table_next_row()
imgui.table_next_column() imgui.table_next_column()
# --- LEFT COLUMN: AST Tree & Slice Management --- # --- LEFT COLUMN: AST Tree & Slice Management ---
imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True) imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True)
# if True: # if True:
@@ -3700,7 +3700,7 @@ def render_ast_inspector_modal(app: App) -> None:
# if do_align: imgui.same_line((avail_width - btn_width)) # if do_align: imgui.same_line((avail_width - btn_width))
# else: imgui.same_line() # else: imgui.same_line()
# imgui.same_line((avail_width - btn_width) * 0.925) # imgui.same_line((avail_width - btn_width) * 0.925)
if not hasattr(f_item, 'ast_mask'): f_item.ast_mask = {} if not hasattr(f_item, 'ast_mask'): f_item.ast_mask = {}
current_mode = f_item.ast_mask.get(full_path, 'hide') current_mode = f_item.ast_mask.get(full_path, 'hide')
@@ -3721,12 +3721,15 @@ def render_ast_inspector_modal(app: App) -> None:
s_line = min(app._slice_sel_start, app._slice_sel_end) s_line = min(app._slice_sel_start, app._slice_sel_end)
e_line = max(app._slice_sel_start, app._slice_sel_end) e_line = max(app._slice_sel_start, app._slice_sel_end)
from src.fuzzy_anchor import FuzzyAnchor from src.fuzzy_anchor import FuzzyAnchor
slice_data = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line) slice_data = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line)
slice_data['tag'] = ""; slice_data['comment'] = "" slice_data['tag'] = ""; slice_data['comment'] = ""
f_item.custom_slices.append(slice_data) f_item.custom_slices.append(slice_data)
app._slice_sel_start = -1; app._slice_sel_end = -1 app._slice_sel_start = -1
app._slice_sel_end = -1
imgui.same_line() imgui.same_line()
if imgui.button("Clear Selection"): app._slice_sel_start = -1; app._slice_sel_end = -1 if imgui.button("Clear Selection"):
app._slice_sel_start = -1
app._slice_sel_end = -1
imgui.same_line() imgui.same_line()
if imgui.button("Auto-Populate"): app._populate_auto_slices(f_item) if imgui.button("Auto-Populate"): app._populate_auto_slices(f_item)
@@ -3747,9 +3750,9 @@ def render_ast_inspector_modal(app: App) -> None:
imgui.pop_id() imgui.pop_id()
if to_remove != -1: f_item.custom_slices.pop(to_remove) if to_remove != -1: f_item.custom_slices.pop(to_remove)
imgui.end_child() imgui.end_child()
imgui.table_next_column() imgui.table_next_column()
# --- RIGHT COLUMN: Content Preview with Highlights --- # --- RIGHT COLUMN: Content Preview with Highlights ---
with imscope.child("ast_content_scroll", imgui.ImVec2(0, 0), True): with imscope.child("ast_content_scroll", imgui.ImVec2(0, 0), True):
if not getattr(app, '_cached_ast_file_lines', None): if not getattr(app, '_cached_ast_file_lines', None):
@@ -3758,10 +3761,10 @@ def render_ast_inspector_modal(app: App) -> None:
draw_list = imgui.get_window_draw_list() draw_list = imgui.get_window_draw_list()
for i, line_text in enumerate(app._cached_ast_file_lines): for i, line_text in enumerate(app._cached_ast_file_lines):
line_num = i + 1 line_num = i + 1
pos = imgui.get_cursor_screen_pos() pos = imgui.get_cursor_screen_pos()
line_height = imgui.get_text_line_height() line_height = imgui.get_text_line_height()
avail_width = imgui.get_content_region_avail().x avail_width = imgui.get_content_region_avail().x
# 1. AST Highlight # 1. AST Highlight
deepest_node = None deepest_node = None
for node in app._cached_ast_nodes: for node in app._cached_ast_nodes:
@@ -3769,31 +3772,29 @@ def render_ast_inspector_modal(app: App) -> None:
if deepest_node is None or node['indent'] > deepest_node['indent']: deepest_node = node if deepest_node is None or node['indent'] > deepest_node['indent']: deepest_node = node
mode = 'hide' mode = 'hide'
if deepest_node: mode = getattr(f_item, 'ast_mask', {}).get(deepest_node['full_path'], 'hide') if deepest_node: mode = getattr(f_item, 'ast_mask', {}).get(deepest_node['full_path'], 'hide')
if mode == 'def': if mode == 'def': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.15)))
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.15))) elif mode == 'sig': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.15)))
elif mode == 'sig':
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.15)))
elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None): elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None):
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("status_warning", alpha=0.2))) draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("status_warning", alpha=0.2)))
# 2. Slice Highlight # 2. Slice Highlight
if hasattr(f_item, 'custom_slices'): if hasattr(f_item, 'custom_slices'):
is_auto = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') == 'auto-ast') is_auto = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') == 'auto-ast')
is_man = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') != 'auto-ast') is_man = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') != 'auto-ast')
if is_man: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2))) if is_man: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2)))
elif is_auto and mode == 'hide': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.1))) elif is_auto and mode == 'hide': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.1)))
# 3. Active Selection Highlight # 3. Active Selection Highlight
if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1: if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1:
s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end) s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end)
if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3))) if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3)))
imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False) imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False)
if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num
if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num
imgui.end_table() imgui.end_table()
imgui.separator() imgui.separator()
if imgui.button("Close", imgui.ImVec2(120, 0)): if imgui.button("Close", imgui.ImVec2(120, 0)):
app.ui_editing_slices_file = None app.ui_editing_slices_file = None
@@ -3806,11 +3807,9 @@ def render_ast_inspector_modal(app: App) -> None:
app.ui_inspecting_ast_file = None app.ui_inspecting_ast_file = None
def render_save_workspace_profile_modal(app: App) -> None: def render_save_workspace_profile_modal(app: App) -> None:
""" """Renders a popup modal for saving the current workspace profile (projects, layout, configuration).
Renders a popup modal for saving the current workspace profile (projects, layout, configuration).
SSDL Shape: SSDL: `[I:name_input] -> [B:scope_radio] -> [B:save_button] => [B:cancel_button]`
`[I:name_input] -> [B:scope_radio] -> [B:save_button] => [B:cancel_button]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -3831,7 +3830,7 @@ def render_save_workspace_profile_modal(app: App) -> None:
imgui.text("Scope:") imgui.text("Scope:")
if imgui.radio_button("Project", app._new_workspace_profile_scope == "project"): app._new_workspace_profile_scope = "project" if imgui.radio_button("Project", app._new_workspace_profile_scope == "project"): app._new_workspace_profile_scope = "project"
imgui.same_line() imgui.same_line()
if imgui.radio_button("Global", app._new_workspace_profile_scope == "global"): app._new_workspace_profile_scope = "global" if imgui.radio_button("Global", app._new_workspace_profile_scope == "global"): app._new_workspace_profile_scope = "global"
imgui.separator() imgui.separator()
if imgui.button("Save", (120, 0)): if imgui.button("Save", (120, 0)):
@@ -3848,12 +3847,10 @@ def render_save_workspace_profile_modal(app: App) -> None:
imgui.end_popup() imgui.end_popup()
def render_context_presets_panel(app: App) -> None: def render_context_presets_panel(app: App) -> None:
""" """Renders the Context Presets configuration panel. Enables naming, saving, loading,
Renders the Context Presets configuration panel. Enables naming, saving, loading,
and deleting context presets. and deleting context presets.
SSDL Shape: SSDL: `[I:presets_list] -> [B:save_new_preset] => [B:presets_action_buttons]`
`[I:presets_list] -> [B:save_new_preset] => [B:presets_action_buttons]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -3885,11 +3882,9 @@ def render_context_presets_panel(app: App) -> None:
if imgui.button(f"Delete##{name}"): app.delete_context_preset(name) if imgui.button(f"Delete##{name}"): app.delete_context_preset(name)
def render_context_screenshots(app: App) -> None: def render_context_screenshots(app: App) -> None:
""" """Renders a list of the currently attached screenshot image files.
Renders a list of the currently attached screenshot image files.
SSDL Shape: SSDL: `[I:screenshots]`
`[I:screenshots]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -3900,12 +3895,10 @@ def render_context_screenshots(app: App) -> None:
for i, s in enumerate(app.screenshots): imgui.text(s) for i, s in enumerate(app.screenshots): imgui.text(s)
def render_context_files_table(app: App) -> None: def render_context_files_table(app: App) -> None:
""" """Renders a two-column table mapping active context files to their AST view modes
Renders a two-column table mapping active context files to their AST view modes
(Definition, Signature, Hide) and slice indicators. Grouped by directories. (Definition, Signature, Hide) and slice indicators. Grouped by directories.
SSDL Shape: SSDL: `[I:group_by_dir] -> o-> [I:dir_node] -> o-> [I:file_row]`
`[I:group_by_dir] -> o-> [I:dir_node] -> o-> [I:file_row]`
ASCII Layout Map: ASCII Layout Map:
+-------------------------------------------------------------+ +-------------------------------------------------------------+
@@ -3921,7 +3914,7 @@ def render_context_files_table(app: App) -> None:
with imscope.table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders) as active: with imscope.table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders) as active:
if active: if active:
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200) imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
imgui.table_headers_row() imgui.table_headers_row()
@@ -4017,11 +4010,9 @@ def render_context_files_table(app: App) -> None:
imgui.text_colored(theme.get_color("status_warning"), "[Slices Active]") imgui.text_colored(theme.get_color("status_warning"), "[Slices Active]")
def render_context_presets(app: App) -> None: def render_context_presets(app: App) -> None:
""" """Renders context preset management controls (combobox selector, update button, save-as, and delete active).
Renders context preset management controls (combobox selector, update button, save-as, and delete active).
SSDL Shape: SSDL: `[I:presets] -> [B:presets_combo] -> [B:save_as_button] => [B:delete_active]`
`[I:presets] -> [B:presets_combo] -> [B:save_as_button] => [B:delete_active]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -4055,12 +4046,12 @@ def render_context_presets(app: App) -> None:
for f in app.context_files: for f in app.context_files:
import copy import copy
from src import models from src import models
p = f.path if hasattr(f, 'path') else str(f) p = f.path if hasattr(f, 'path') else str(f)
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') 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_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=active, files=preset_files, screenshots=list(app.screenshots)) preset = models.ContextPreset(name=active, files=preset_files, screenshots=list(app.screenshots))
app.controller.save_context_preset(preset) app.controller.save_context_preset(preset)
@@ -4082,12 +4073,9 @@ def render_context_presets(app: App) -> None:
root = app.controller.active_project_root root = app.controller.active_project_root
for f in app.context_files: for f in app.context_files:
path = f.path if hasattr(f, "path") else str(f) path = f.path if hasattr(f, "path") else str(f)
if not os.path.isabs(path): if not os.path.isabs(path): full_path = os.path.join(root, path)
full_path = os.path.join(root, path) else: full_path = path
else: if not os.path.exists(full_path): missing.append(path)
full_path = path
if not os.path.exists(full_path):
missing.append(path)
if missing: if missing:
app.missing_context_files = missing app.missing_context_files = missing
@@ -4098,12 +4086,12 @@ def render_context_presets(app: App) -> None:
for f in app.context_files: for f in app.context_files:
import copy import copy
from src import models from src import models
p = f.path if hasattr(f, 'path') else str(f) p = f.path if hasattr(f, 'path') else str(f)
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') 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_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(app.screenshots)) preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
app.controller.save_context_preset(preset) app.controller.save_context_preset(preset)
@@ -4118,11 +4106,9 @@ def render_context_presets(app: App) -> None:
app.ui_active_context_preset = "" app.ui_active_context_preset = ""
def render_snapshot_tab(app: App) -> None: def render_snapshot_tab(app: App) -> None:
""" """Renders the Snapshot tab-bar, containing the resolved aggregate markdown and active system prompts.
Renders the Snapshot tab-bar, containing the resolved aggregate markdown and active system prompts.
SSDL Shape: SSDL: `[I:aggregate_text] -> [B:copy_buttons] => [I:preview_panes]`
`[I:aggregate_text] -> [B:copy_buttons] => [I:preview_panes]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -4150,13 +4136,13 @@ def render_snapshot_tab(app: App) -> None:
display_md = app._focus_md_cache[cp_name] display_md = app._focus_md_cache[cp_name]
else: else:
flat = src.project_manager.flat_config(app.controller.project, app.active_discussion) flat = src.project_manager.flat_config(app.controller.project, app.active_discussion)
cp = app.controller.project.get('context_presets', {}).get(cp_name) cp = app.controller.project.get('context_presets', {}).get(cp_name)
if cp: if cp:
flat["files"]["paths"] = cp.get("files", []) flat["files"]["paths"] = cp.get("files", [])
flat["screenshots"]["paths"] = cp.get("screenshots", []) flat["screenshots"]["paths"] = cp.get("screenshots", [])
full_md, _, _ = src.aggregate.run(flat) full_md, _, _ = src.aggregate.run(flat)
app._focus_md_cache[cp_name] = full_md app._focus_md_cache[cp_name] = full_md
display_md = full_md display_md = full_md
if imgui.button("Copy"): imgui.set_clipboard_text(display_md) if imgui.button("Copy"): imgui.set_clipboard_text(display_md)
with imscope.child("last_agg_md", 0, 0, True): with imscope.child("last_agg_md", 0, 0, True):
markdown_helper.render(display_md, context_id="snapshot_agg") markdown_helper.render(display_md, context_id="snapshot_agg")
@@ -4167,11 +4153,9 @@ def render_snapshot_tab(app: App) -> None:
markdown_helper.render(app.last_resolved_system_prompt, context_id="snapshot_sys") markdown_helper.render(app.last_resolved_system_prompt, context_id="snapshot_sys")
def render_empty_context_modal(app: App) -> None: def render_empty_context_modal(app: App) -> None:
""" """Renders a popup modal warning the user when the context composition is empty.
Renders a popup modal warning the user when the context composition is empty.
SSDL Shape: SSDL: `[I:warning_text] -> [B:proceed_button] => [B:cancel_button]`
`[I:warning_text] -> [B:proceed_button] => [B:cancel_button]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -4192,8 +4176,8 @@ def render_empty_context_modal(app: App) -> None:
imgui.text("This may result in poor AI performance or loss of project context.") imgui.text("This may result in poor AI performance or loss of project context.")
imgui.separator() imgui.separator()
if imgui.button("Proceed Anyway", imgui.ImVec2(150, 0)): if imgui.button("Proceed Anyway", imgui.ImVec2(150, 0)):
if app._pending_generation_action == 'generate': app.controller._handle_generate_send() if app._pending_generation_action == 'generate': app.controller._handle_generate_send()
elif app._pending_generation_action == 'md_only': app.controller._handle_md_only() elif app._pending_generation_action == 'md_only': app.controller._handle_md_only()
app._pending_generation_action = None app._pending_generation_action = None
imgui.close_current_popup() imgui.close_current_popup()
imgui.same_line() imgui.same_line()
@@ -4202,12 +4186,10 @@ def render_empty_context_modal(app: App) -> None:
imgui.end_popup() imgui.end_popup()
def render_context_modals(app: App) -> None: def render_context_modals(app: App) -> None:
""" """Dispatches rendering calls to sub-modals relating to context management
Dispatches rendering calls to sub-modals relating to context management
(empty context warning, file picker, missing files warning, and AST inspector). (empty context warning, file picker, missing files warning, and AST inspector).
SSDL Shape: SSDL: `[I] -> [I:modals_dispatch]`
`[I] -> [I:modals_dispatch]`
ASCII Layout Map: ASCII Layout Map:
Conditionally opens (in order): Conditionally opens (in order):
@@ -4239,12 +4221,12 @@ def render_context_modals(app: App) -> None:
for f in app.context_files: for f in app.context_files:
import copy import copy
from src import models from src import models
p = f.path if hasattr(f, 'path') else str(f) p = f.path if hasattr(f, 'path') else str(f)
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') 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_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(app.screenshots)) preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
app.controller.save_context_preset(preset) app.controller.save_context_preset(preset)
@@ -4262,10 +4244,10 @@ def render_context_modals(app: App) -> None:
def _get_context_composition_state(app: App) -> tuple: def _get_context_composition_state(app: App) -> tuple:
files_state = [] files_state = []
for f in app.context_files: for f in app.context_files:
p = f.path if hasattr(f, 'path') else str(f) p = f.path if hasattr(f, 'path') else str(f)
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary' vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
agg = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False agg = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False
slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', [])) slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', []))
mask = tuple(sorted(getattr(f, 'ast_mask', {}).items())) mask = tuple(sorted(getattr(f, 'ast_mask', {}).items()))
files_state.append((p, vm, agg, slc, mask)) files_state.append((p, vm, agg, slc, mask))
screenshots_state = tuple(app.screenshots) screenshots_state = tuple(app.screenshots)
@@ -4287,8 +4269,8 @@ def _check_auto_refresh_context_preview(app: App) -> None:
def worker(): def worker():
try: try:
app.controller.context_files = app.context_files app.controller.context_files = app.context_files
res = app.controller._do_generate() res = app.controller._do_generate()
app.context_preview_text = res[0] app.context_preview_text = res[0]
except Exception: except Exception:
app.context_preview_text = "Error generating context preview." app.context_preview_text = "Error generating context preview."
finally: finally:
@@ -4303,12 +4285,10 @@ def _check_auto_refresh_context_preview(app: App) -> None:
app.controller.submit_io(worker) app.controller.submit_io(worker)
def render_context_preview_window(app: App) -> None: def render_context_preview_window(app: App) -> None:
""" """Renders the Context Preview window. Automatically refreshes aggregated context previews in the background
Renders the Context Preview window. Automatically refreshes aggregated context previews in the background
and renders formatted markdown text. and renders formatted markdown text.
SSDL Shape: SSDL: `[I:preview_text] -> [B:clipboard_button] => [I:preview_box]`
`[I:preview_text] -> [B:clipboard_button] => [I:preview_box]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -4330,7 +4310,7 @@ def render_context_preview_window(app: App) -> None:
if exp: if exp:
if imgui.button("Close"): if imgui.button("Close"):
app.show_windows["Context Preview"] = False app.show_windows["Context Preview"] = False
app.ui_separate_context_preview = False app.ui_separate_context_preview = False
imgui.same_line() imgui.same_line()
if imgui.button("Copy to Clipboard"): if imgui.button("Copy to Clipboard"):
imgui.set_clipboard_text(app.context_preview_text) imgui.set_clipboard_text(app.context_preview_text)
@@ -4342,12 +4322,10 @@ def render_context_preview_window(app: App) -> None:
#region: Discussions #region: Discussions
def render_discussion_hub(app: App) -> None: def render_discussion_hub(app: App) -> None:
""" """Top-level hub for the Discussion panel. Houses tabs for Discussion, Context Composition,
Top-level hub for the Discussion panel. Houses tabs for Discussion, Context Composition,
Context Preview (inline or detached), Snapshot, and Takes. Context Preview (inline or detached), Snapshot, and Takes.
SSDL Shape: SSDL: `[B:pop_out_toggle] -> [I:tab_bar] => [I:active_tab_content]`
`[B:pop_out_toggle] -> [I:tab_bar] => [I:active_tab_content]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -4383,12 +4361,10 @@ def render_discussion_hub(app: App) -> None:
return return
def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_index: int, is_standalone: bool = False) -> None: def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_index: int, is_standalone: bool = False) -> None:
""" """Renders a collapsible thinking-trace section within a discussion entry bubble.
Renders a collapsible thinking-trace section within a discussion entry bubble.
Displays each reasoning segment labeled by its marker tag (e.g. [thinking]). Displays each reasoning segment labeled by its marker tag (e.g. [thinking]).
SSDL Shape: SSDL: `[I:segment_list] -> [B:read_pure_toggle] => [I:trace_child]`
`[I:segment_list] -> [B:read_pure_toggle] => [I:trace_child]`
ASCII Layout Map: ASCII Layout Map:
+----------------------------------------------------------+ +----------------------------------------------------------+
@@ -4426,22 +4402,18 @@ def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_ind
imgui.text_colored(C_TC(), f"[{marker}]") imgui.text_colored(C_TC(), f"[{marker}]")
if thinking_read_mode: if thinking_read_mode:
if app.ui_word_wrap: if app.ui_word_wrap:
with imscope.text_wrap(imgui.get_content_region_avail().x): with imscope.text_wrap(imgui.get_content_region_avail().x): imgui.text(content)
imgui.text(content) else: imgui.text(content)
else:
imgui.text(content)
else: else:
render_selectable_label(app, f"think_text_{entry_index}_{idx}", content, multiline=True, height=-1) render_selectable_label(app, f"think_text_{entry_index}_{idx}", content, multiline=True, height=-1)
imgui.separator() imgui.separator()
def render_discussion_entry(app: App, entry: dict, index: int) -> None: def render_discussion_entry(app: App, entry: dict, index: int) -> None:
""" """Renders a single discussion message entry as a colored visual bubble.
Renders a single discussion message entry as a colored visual bubble.
Supports collapsible states, read/edit mode toggles, role selection, Supports collapsible states, read/edit mode toggles, role selection,
input/output token metrics, and action footers. input/output token metrics, and action footers.
SSDL Shape: SSDL: `[I:header] -> [B:collapsed] => [I:preview] | [I:body] -> [I:footer]`
`[I:header] -> [B:collapsed] => [I:preview] | [I:body] -> [I:footer]`
ASCII Layout Map: ASCII Layout Map:
+-------------------------------------------------------------+ +-------------------------------------------------------------+
@@ -7139,13 +7111,13 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
imgui.end_combo() imgui.end_combo()
if imgui.button("Create"): if imgui.button("Create"):
new_ticket = { new_ticket = {
"id": app.ui_new_ticket_id, "id": app.ui_new_ticket_id,
"description": app.ui_new_ticket_desc, "description": app.ui_new_ticket_desc,
"status": "todo", "status": "todo",
"priority": app.ui_new_ticket_priority, "priority": app.ui_new_ticket_priority,
"assigned_to": "tier3-worker", "assigned_to": "tier3-worker",
"target_file": app.ui_new_ticket_target, "target_file": app.ui_new_ticket_target,
"depends_on": [d.strip() for d in app.ui_new_ticket_deps.split(",") if d.strip()] "depends_on": [d.strip() for d in app.ui_new_ticket_deps.split(",") if d.strip()]
} }
app.active_tickets.append(new_ticket) app.active_tickets.append(new_ticket)
app._show_add_ticket_form = False app._show_add_ticket_form = False