The imgui-bundle 1.92.5 API changed:
OLD: imgui.get_style().colors[imgui.Col_.text]
NEW: imgui.get_style().color_(imgui.Col_.text)
The Style object no longer has a 'colors' dict; it has a 'color_'
method that takes a Col_ enum and returns the ImVec4 color.
Updated all 9 call sites in md_renderer_py.py and the test mocks
in test_md_renderer_py.py, test_markdown_helper_bullets.py, and
test_markdown_render_robust.py.
42/42 tests pass.
ADD src/md_renderer_py.py: Full port of mekhontsev/imgui_md to pure Python.
- Uses markdown-it-py (already a transitive dep) for AST parsing.
- Walks the token tree, calling imgui primitives directly.
- Mirrors the C++ API surface: MarkdownOptions, MarkdownCallbacks,
MarkdownRenderer.render(), render_unindented().
- Code blocks delegated via set_external_code_block_handler callback.
- All other content (paragraphs, headings, lists, code, tables, hr,
emphasis, strong, links, blockquotes) rendered natively.
ROOT CAUSE OF BULLET OVERLAP (now fixed at the source):
imgui-md C++ BLOCK_P guards NewLine() behind 'if (!m_list_stack.empty())'
(imgui_md.cpp line ~145). Inside lists, paragraph transitions don't
advance the cursor Y. The Python port calls imgui.new_line() explicitly
between paragraphs in a list item, eliminating the overlap.
ROOT CAUSE OF '*' BULLET Y-OVERLAP (now fixed at the source):
imgui-md C++ BLOCK_LI for '*' delim calls ImGui::Bullet() without
ImGui::SameLine() (imgui_md.cpp line ~95). The Python port calls
imgui.bullet() + imgui.same_line() for all markers uniformly.
REMOVED in src/markdown_helper.py:
- _normalize_bullet_delimiters (no longer needed)
- _normalize_nested_list_endings (no longer needed)
- _normalize_list_continuations (no longer needed)
- parse_tables / render_table (renderer handles tables natively)
- All 'imgui_md' body rendering (replaced by Python port)
TESTS:
- tests/test_md_renderer_py.py (new): 16 unit tests for the Python port
covering paragraphs, headings, lists, nested lists, emphasis, strong,
code, links, tables, hr, unindented.
- tests/test_markdown_helper_bullets.py (rewritten): 13 tests for the
integration with the public MarkdownRenderer class.
- tests/test_markdown_render_robust.py (updated): 2 tests verifying
table content is routed through the new Python renderer (not imgui_md).
- tests/test_markdown_table.py / _render.py / _columns.py / _wrapped.py:
unchanged (test the standalone render_table which is still used by
the new renderer as a fallback for any unhandled cases).
42/42 markdown tests pass. 1-space indentation. 1 C++ dependency
removed (imgui_md is no longer used at runtime).
NOT FIXED (known limitations of the new renderer):
- Inline code rendering uses a tinted small_button (not monospace)
- Heading fonts use the default font (no separate bold/large fonts)
- Image rendering shows a placeholder text
- These can be improved by subclassing MarkdownRenderer
ROOT CAUSE: src/markdown_helper.py:render() used a 'mask text with placeholders
then re.split' approach that failed when AI responses contained CRLF or when
the same table content appeared twice. The replace() either didn't match
(CRLF mismatch) or only replaced the first occurrence, leaving the second
table as raw markdown for imgui_md to render badly. Result: the same table
appeared twice (bad rendering via imgui_md, good rendering via my new code).
FIX: rewrite render() to walk lines directly. Per-line, decide whether to
buffer for imgui_md, skip into a table renderer, or accumulate into a
code-block renderer. No text replacement needed.
- src/markdown_helper.py: new render() walks lines, handles code fences
and table intervals inline via lookup dicts.
- src/gui_2.py: render_log_management now calls load_registry() on the
newly-created LogRegistry when _log_registry was None. Previously the
initial construction populated an empty table, AND the 'Refresh Registry'
button was inside the else branch, so users had no way to load data.
User re-indented the surrounding block during debugging.
Tests:
- test_markdown_render_robust.py: 2 tests (CRLF text, duplicate content)
- test_log_management_first_open.py: 1 test (registry populated on open)
40/40 broad regression pass.