Conversation
Add a hex dump viewer to the message body view alongside the existing JSON/XML formatted view. Features: - Virtualized rendering for large payloads (only visible rows in DOM) - Responsive layout: bytes per row adapts to container width (8-48) - 8-byte lane grouping with dotted vertical separators - Selection sync between hex bytes and ASCII text panels - Null bytes (0x00) and non-printable chars styled in gray - Encoding label (US-ASCII) shown in toolbar - Copy to clipboard (selection or first 64KB hex dump) - Raw bytes stored alongside formatted text in MessageStore
Replace the 0.5ch-wide inline-block separator between 8-char groups in the ASCII column with a thin dotted left-border on the first char of each lane. This prevents misleading spaces in the text (e.g. "orderI d" instead of "orderId") while keeping the visual grouping.
Wrap JSON/XML formatting in try/catch so parse failures are surfaced via a `parse_failed` flag instead of crashing. When detected, the UI automatically switches to hex view and shows a warning banner.
Remove min-height and flex sizing that caused either excessive whitespace below small data or zero-height collapse.
There was a problem hiding this comment.
Pull request overview
Adds a hex dump viewer for message bodies in the frontend, allowing users to inspect raw bytes (with selection/copy) and automatically falling back to hex when JSON/XML formatting fails.
Changes:
- Add
HexViewcomponent with virtual scrolling, responsive bytes-per-row, and selection/copy support. - Update
BodyViewto provide a Formatted/Hex toggle and auto-switch to Hex on parse failures. - Extend
MessageStore.downloadBody()to fetch raw bytes, decode text with charset, and surface aparse_failedflag; update mock scenario to include a body suitable for hex testing.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Frontend/test/mocks/scenarios/recoverability/recoverability-available.ts | Updates mock scenario to include a failed message with a body for manual testing of the new hex view. |
| src/Frontend/src/stores/MessageStore.ts | Adds raw byte fetching/decoding and parse-failure signaling to support hex view + safer formatting. |
| src/Frontend/src/components/messages/HexView.vue | New component implementing the hex dump UI (offset/hex/ASCII) with virtualization and copy support. |
| src/Frontend/src/components/messages/BodyView.vue | Adds view-mode toggle and auto-switch behavior based on parse failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const contentType = response.headers.get("content-type"); | ||
| body.value.data.content_type = contentType ?? "text/plain"; | ||
| body.value.data.value = await response.text(); | ||
|
|
||
| if (contentType === "application/json") { | ||
| body.value.data.value = stringify(parse(body.value.data.value), null, 2) ?? body.value.data.value; | ||
| } | ||
| if (contentType === "text/xml") { | ||
| body.value.data.value = xmlFormat(body.value.data.value, { indentation: " ", collapseContent: true }); | ||
| const arrayBuffer = await response.arrayBuffer(); | ||
| body.value.data.rawBytes = new Uint8Array(arrayBuffer); | ||
| const charset = contentType?.match(/charset=([^\s;]+)/i)?.[1] ?? "utf-8"; | ||
| body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); | ||
|
|
There was a problem hiding this comment.
downloadBody() doesn’t clear previously-set body state (e.g., not_found, no_content, parse_failed, rawBytes, and value) before loading a new body. This can leave the UI stuck showing “not found”/“no content”/parse warning (or displaying stale bytes) when navigating between messages. Reset these flags/fields at the start of downloadBody() (before the status checks/early returns) so each message load starts from a clean state.
| const charset = contentType?.match(/charset=([^\s;]+)/i)?.[1] ?? "utf-8"; | ||
| body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); | ||
|
|
||
| try { | ||
| if (contentType === "application/json") { | ||
| body.value.data.value = stringify(parse(body.value.data.value), null, 2) ?? body.value.data.value; | ||
| } | ||
| if (contentType === "text/xml") { | ||
| body.value.data.value = xmlFormat(body.value.data.value, { indentation: " ", collapseContent: true }); | ||
| } |
There was a problem hiding this comment.
The JSON/XML formatting + parse_failed detection uses exact contentType === "application/json" / === "text/xml". If the server returns a parameterized content-type (common: application/json; charset=utf-8) or application/*+json, formatting won’t run and invalid JSON/XML won’t set parse_failed (so the hex auto-switch won’t trigger). Normalize the media type (e.g., split on ; and trim) and/or reuse the existing parseContentType logic to decide when to format and when to set parse_failed.
| const arrayBuffer = await response.arrayBuffer(); | ||
| body.value.data.rawBytes = new Uint8Array(arrayBuffer); | ||
| const charset = contentType?.match(/charset=([^\s;]+)/i)?.[1] ?? "utf-8"; | ||
| body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); |
There was a problem hiding this comment.
new TextDecoder(charset) can throw for unknown/unsupported charset labels. In that case this code will fall into the outer catch and set failed_to_load, which prevents showing the hex view even though rawBytes were fetched. Consider wrapping the decode in its own try/catch and falling back to a safe decoder (e.g., utf-8) while still keeping rawBytes available for hex display.
| body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); | |
| try { | |
| body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); | |
| } catch { | |
| // Fallback to UTF-8 if the specified charset is unsupported or invalid | |
| body.value.data.value = new TextDecoder("utf-8").decode(arrayBuffer); | |
| } |
| @@ -18,6 +22,16 @@ watch( | |||
| ); | |||
| const contentType = computed(() => parseContentType(bodyState.value.data.content_type)); | |||
| const body = computed(() => bodyState.value.data.value); | |||
| const rawBytes = computed(() => bodyState.value.data.rawBytes); | |||
| const parseFailed = computed(() => bodyState.value.data.parse_failed); | |||
|
|
|||
| watch( | |||
| parseFailed, | |||
| (failed) => { | |||
| if (failed) viewMode.value = "hex"; | |||
| }, | |||
| { immediate: true } | |||
| ); | |||
There was a problem hiding this comment.
viewMode is only forced to "hex" when parseFailed becomes truthy, but it’s never reset when navigating to a different message or when a subsequent body parses successfully. This means the UI can stay in Hex view across messages even when parse_failed is false. Reset viewMode to "formatted" on body_url change (or when parseFailed becomes falsy) and then re-apply the auto-switch when parse fails.
|
@ramonsmits what does the "copy to clipboard" button does when in hex view mode? Does it copy the hex output? |
@johnsimons it copied HEX, the whole message or the selected bytes |

Summary
parse_failedflag instead of crashingrecoverability-availableupdated with a test message for hex viewHow to test
Run
VITE_MOCK_SCENARIO=recoverability-available npm run dev:mocksto test with mock data