feat(response-viewer): ASCII diagram wrap toggle, mobile code blocks, chrome-stripping fallback (#75)

* fix: restore clear message separation + proper table layout in response viewer

* fix: capture Claude CLI's real session ID + robust ANSI/CLI-chrome stripping in response viewer fallback

Session constructor seeded _claudeSessionId with Codeman's session.id as a
placeholder, and the message-driven update was gated on !_claudeSessionId —
meaning Claude CLI's actual session UUID was never adopted. This broke
/api/sessions/:id/last-response JSONL lookups, silently falling through to
the terminal-buffer path whose ANSI regex missed \x1b[>c / \x1b[>q queries.

- session.ts: update _claudeSessionId whenever a message's session_id differs
  from current (covers placeholder and stale-resume cases)
- app.js: extract _cleanTerminalBuffer with proper CSI regex (param bytes
  0x30-0x3F now covers > ? < =) plus a chrome filter for status bar,
  progress bar, spinner, shell prompt, and hint lines

* fix: wrap regular code blocks on mobile, keep ASCII diagrams rigid with scroll hint

* feat: add per-block wrap toggle on ASCII-diagram code blocks

* fix: wrap by default, pin toggle button outside scroll container

* fix: narrow diagram detection to box-drawing + block elements only

* feat: show last-response viewer eye icon on desktop too

The response viewer button was mobile-only via a display:none default with a
mobile.css override. Flip the default to inline-flex and drop the override so
the eye icon appears in the header on every form factor — desktop users get
the same quick "Last Response" pane as mobile.

* fix(response-viewer): restore HTML sanitizer + fix undefined `src` in _renderMarkdown

- `_renderMarkdown` referenced an undefined `src` (should be `text`),
  causing a ReferenceError on every markdown render. The try/catch
  swallowed it, so the new table-wrap and ASCII-diagram features
  never actually ran — output silently fell through to plain-text.
  app.js is excluded from ESLint, so this wasn't caught at lint time.
- `_sanitizeHtml` was removed when refactoring the response viewer,
  leaving `marked.parse()` output going straight into `innerHTML`
  without sanitization (XSS regression vs. master). Restored the
  helper and re-applied it before any post-processing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Teigen <teigen@TeigendeMac-mini.local>
Co-authored-by: arkon <arkon.85@hotmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tenggan Zhang
2026-04-28 02:58:42 +02:00
committed by GitHub
co-authored by Claude Opus 4.7 Teigen arkon
parent d07b59db4e
commit e549e15cb8
4 changed files with 376 additions and 55 deletions
+6 -3
View File
@@ -1614,11 +1614,14 @@ export class Session extends EventEmitter {
this._messages = this._messages.slice(-Math.floor(MAX_MESSAGES * 0.8));
}
// Extract Claude session ID from messages (can be in any message type)
// Support both sessionId (camelCase) and session_id (snake_case)
// Extract Claude session ID from messages (can be in any message type).
// Support both sessionId (camelCase) and session_id (snake_case).
// The constructor seeds _claudeSessionId with this.id as a placeholder;
// once Claude CLI emits its real session ID, adopt it so JSONL lookups
// (e.g. /api/sessions/:id/last-response) can find the transcript file.
const msgSessionId =
((msg as unknown as Record<string, unknown>).sessionId as string | undefined) ?? msg.session_id;
if (msgSessionId && !this._claudeSessionId) {
if (msgSessionId && msgSessionId !== this._claudeSessionId) {
this._claudeSessionId = msgSessionId;
}