fix: escape Rich markup in card/conclusion/message content, keyboard hints on welcome

This commit is contained in:
Erosika 2026-04-15 17:29:55 -04:00
parent ab0ff2f052
commit 109ff8d5a2
2 changed files with 33 additions and 19 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from rich.markup import escape as markup_escape
from textual.app import ComposeResult
from textual.widget import Widget
from textual.widgets import Static
@ -86,11 +87,11 @@ class PeerPanel(Widget):
scroll.mount(Static(f"[{ACCENT}]CARD[/{ACCENT}]", classes="section-label"))
if card and card.strip():
# Wrap long lines, show first 400 chars
preview = card.strip()[:400]
if len(card.strip()) > 400:
preview += f"\n[{FG_MUTED}]… {len(card.strip()) - 400} more chars[/{FG_MUTED}]"
scroll.mount(Static(preview, classes="card-body"))
raw = card.strip()
preview = markup_escape(raw[:400])
if len(raw) > 400:
preview += f"\n[{FG_MUTED}]… {len(raw) - 400} more chars[/{FG_MUTED}]"
scroll.mount(Static(preview, classes="card-body", markup=True))
else:
scroll.mount(Static(f"[{FG_DIM}]no card yet[/{FG_DIM}]", classes="card-body"))
@ -103,11 +104,14 @@ class PeerPanel(Widget):
else:
for i, c in enumerate(conclusions[:12]):
content = c.get("content", "")
# Truncate to fit panel width
preview = content[:55] + "" if len(content) > 55 else content
safe = markup_escape(content[:55] + "" if len(content) > 55 else content)
prefix = TREE_LAST if i == min(len(conclusions), 12) - 1 else TREE_MID
scroll.mount(
Static(f"[{FG_DIM}]{prefix}[/{FG_DIM}][{FG}]{preview}[/{FG}]", classes="conclusion-row")
Static(
f"[{FG_DIM}]{prefix}[/{FG_DIM}][{FG}]{safe}[/{FG}]",
classes="conclusion-row",
markup=True,
)
)
if count > 12:
scroll.mount(Static(f"[{FG_MUTED}] … {count - 12} more[/{FG_MUTED}]"))

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import datetime
from rich.markup import escape as markup_escape
from rich.text import Text
from textual.app import ComposeResult
from textual.message import Message
@ -72,21 +73,26 @@ class TranscriptPanel(Widget):
if self._streaming:
self._update_stream_cursor()
def _update_stream_cursor(self) -> None:
log = self.query_one("#log", RichLog)
cursor_char = CURSOR if self._cursor_on else " "
# Cursor line is updated via the streaming append path
_ = cursor_char # referenced during actual stream writes
# ── Public API ────────────────────────────────────────────────────────────
def show_welcome(self) -> None:
log = self.query_one("#log", RichLog)
log.clear()
t = Text()
t.append(" HONCHO\n", style=f"bold {ACCENT}")
t.append("\n HONCHO\n", style=f"bold {ACCENT}")
t.append(" memory that reasons\n\n", style=FG_MUTED)
t.append(f" [{FG_MUTED}]select a session or query the dialectic[/{FG_MUTED}]\n")
t.append(" select a session from the left panel\n", style=FG_DIM)
t.append(" or query the dialectic below\n\n", style=FG_DIM)
t.append(" bindings\n", style=f"bold {FG_DIM}")
binds = [
("ctrl+d", "focus dialectic input"),
("tab", "focus session list"),
("r", "refresh"),
("q", "quit"),
]
for key, desc in binds:
t.append(f" {key}", style=f"bold {ACCENT}")
t.append(f" {desc}\n", style=FG_MUTED)
log.write(t)
def show_no_workspace(self) -> None:
@ -153,10 +159,12 @@ class TranscriptPanel(Widget):
header.append("\n")
log.write(header)
# Content
# Content — escape raw text so Rich markup chars don't break rendering
body = Text()
for line in content.splitlines():
body.append(f" {line}\n", style=FG)
body.append(" ", style=FG)
body.append(line, style=FG)
body.append("\n")
if not content.strip():
body.append(" (empty)\n", style=FG_MUTED)
log.write(body)
@ -176,7 +184,9 @@ class TranscriptPanel(Widget):
resp = Text()
resp.append(" dialectic\n", style=f"bold {GREEN}")
for line in content.splitlines():
resp.append(f" {line}\n", style=FG)
resp.append(" ", style=FG)
resp.append(line, style=FG)
resp.append("\n")
resp.append("\n")
log.write(resp)