Skip to content

Add hex view for message body#2907

Open
ramonsmits wants to merge 8 commits intomasterfrom
feature/hex-view
Open

Add hex view for message body#2907
ramonsmits wants to merge 8 commits intomasterfrom
feature/hex-view

Conversation

@ramonsmits
Copy link
Copy Markdown
Member

@ramonsmits ramonsmits commented Mar 20, 2026

Screencast From 2026-03-20 15-54-40

Summary

  • Adds a hex view tab alongside the formatted view in the message body panel
  • Virtual-scrolled hex dump with offset, hex bytes grouped in 8-byte lanes, and ASCII representation
  • Responsive layout that adjusts bytes-per-row based on available width
  • Click-drag selection across hex and ASCII columns with copy to clipboard
  • Auto-switches to hex view when message body parsing fails (invalid JSON/XML), showing a warning banner
  • Wraps JSON/XML formatting in try/catch so parse failures surface a parse_failed flag instead of crashing
  • Mock scenario recoverability-available updated with a test message for hex view

How to test

Run VITE_MOCK_SCENARIO=recoverability-available npm run dev:mocks to test with mock data

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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 HexView component with virtual scrolling, responsive bytes-per-row, and selection/copy support.
  • Update BodyView to 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 a parse_failed flag; 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.

Comment on lines 228 to +235
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);

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +233 to +242
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 });
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Comment on lines 14 to +34
@@ -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 }
);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@johnsimons
Copy link
Copy Markdown
Member

@ramonsmits what does the "copy to clipboard" button does when in hex view mode?
image

Does it copy the hex output?

@ramonsmits
Copy link
Copy Markdown
Member Author

@ramonsmits what does the "copy to clipboard" button does when in hex view mode?

@johnsimons it copied HEX, the whole message or the selected bytes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature Type: Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants