Skip to content

feat: render structured Claude Code session detail messages#198

Merged
vakovalskii merged 6 commits intovakovalskii:mainfrom
akolotov:feat/claude-structured-messages
Apr 23, 2026
Merged

feat: render structured Claude Code session detail messages#198
vakovalskii merged 6 commits intovakovalskii:mainfrom
akolotov:feat/claude-structured-messages

Conversation

@akolotov
Copy link
Copy Markdown
Contributor

@akolotov akolotov commented Apr 18, 2026

#197 must be merged first. After that, merge conflicts should be resolved before code review to reduce line churn.

Summary

Render known structured Claude Code session messages in the detail panel instead of showing raw XML-like wrappers.

It currently supports:

  • slash commands (command-name, command-message, command-args)
  • bash input (bash-input)
  • bash results (bash-stdout, bash-stderr)
  • local command stdout (local-command-stdout)
  • task notifications (task-notification) from both user and queue-operation entries

What Changed

  • extend structured-message parsing to support Claude Code hyphenated tags
  • add Claude-specific structured parsing in session detail loading
  • filter out local-command-caveat messages from the detail panel
  • keep queue-operation task notifications visible as structured queue entries
  • render recognized Claude structured messages with labeled sections and status metadata
  • preserve plain-text fallback for unknown or malformed payloads
  • keep existing Codex structured rendering behavior intact

Notes

  • scope is limited to the session detail panel
  • Claude system entries with subtype local_command are still ignored
  • duplicate task notifications from user and queue-operation are intentionally kept for now
  • no new tests were added in this change set

Examples

Slash Command

image

Bash Command

image

Task Notification

image

# Conflicts:
#	src/data.js
#	src/frontend/detail.js
#	src/frontend/styles.css
@akolotov akolotov marked this pull request as ready for review April 22, 2026 05:51
@akolotov
Copy link
Copy Markdown
Contributor Author

Merge conflicts have been addressed

@NovakPAai NovakPAai self-requested a review April 22, 2026 07:53
Copy link
Copy Markdown
Collaborator

@NovakPAai NovakPAai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Overall the approach is solid — extracting regex constants, using Map<tag, def> for hyphenated-tag-to-field mapping, and the cursor-based full-consumption check in parseStructuredFields are all good design decisions. The fallback between two descriptor sets for task_notification is pragmatic. A few things need attention before merge:


HIGH — must fix

[H1] XSS: fields.status embedded in HTML

fields.status originates from session JSONL content (arbitrary text), not from a controlled internal enum. Double-check it is run through escHtml() everywhere it appears in the rendered HTML — including any class attribute positions (e.g. class="status-badge status- + value). A session file with <status>ok" onmouseover="alert(1)</status> would be enough to trigger injection if escHtml is missing at even one call site.

[H2] No tests for the parsing layer

The PR adds ~255 lines of regex-heavy logic with non-trivial correctness requirements: hyphenated-tag-to-field mapping, required: false semantics, two-schema fallback. The project already has a node:test suite. Please add test/claude-structured-parse.test.js covering at minimum: each message type happy path, missing required field → null, optional field absent → success, malformed wrapper → null.

[H3] Nested usage parse failure drops the whole notification

In parseClaudeTaskNotification, if parsedFields.usage is non-null but fails parseStructuredFields (e.g. the usage format changes in a future Claude version), the entire notification is silently dropped even though status/summary/etc. parsed correctly. Usage should be treated as optional — return the notification without usage rather than returning null.


MEDIUM

[M1] The new RegExp(STRUCTURED_FIELD_RE.source, STRUCTURED_FIELD_RE.flags) clone inside parseStructuredFields is correct (resets lastIndex), but the intent is non-obvious — add a one-line comment explaining why, otherwise a future maintainer will "simplify" it and introduce a stale-lastIndex bug.

[M2] parseStructuredMessage signature changed from 3 to 4 params — please confirm with a grep that no other call site exists besides the two already updated.

[M3] isFilteredClaudeStructuredMessage checks a single hardcoded tag. Replace with a Set constant so future additions are a one-liner.


LOW / nits

  • getMessageRoleMeta silently falls through to msg-assistant for unknown roles — a console.warn in dev would surface future gaps early.
  • Unrecognized queue-operation entries are silently dropped — intentional, but worth a one-line comment so it's not mistaken for a bug.
  • data.js is already well past 4 000 lines; the new Claude-specific parsing functions (parseClaudeStructuredMessage, parseClaudeTaskNotification, etc.) are cohesive enough to live in a separate src/claude-message-parser.js. Not blocking, but worth tracking as tech debt.

Verdict: request changes. H1 (XSS) and H3 (silent notification drop) are the most important; H2 (tests) is important for long-term reliability given how often Claude's internal message format evolves.

@akolotov
Copy link
Copy Markdown
Contributor Author

Verdict: request changes. H1 (XSS) and H3 (silent notification drop) are the most important; H2 (tests) is important for long-term reliability given how often Claude's internal message format evolves.

Addressed in dbc3499

@NovakPAai
Copy link
Copy Markdown
Collaborator

Thanks for the quick turnaround on the fixes!

@vakovalskii vakovalskii merged commit 655a695 into vakovalskii:main Apr 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants