fix(web): align brief response viewer formatting

This commit is contained in:
codeman-local
2026-08-06 10:16:15 +08:00
parent 12a5f5919e
commit b982c5d0e0
3 changed files with 52 additions and 19 deletions
@@ -0,0 +1,5 @@
---
"aicodeman": patch
---
Keep the brief Response Viewer output inside the same message card and Markdown wrapper used by the full conversation view, so opening the viewer without clicking More preserves the same readable formatting.
+38 -19
View File
@@ -1923,6 +1923,37 @@ class CodemanApp {
}
}
/** Build one response-viewer message so the brief and full views share markup and CSS. */
_buildResponseViewerMessage(text, role, agentLabel) {
const div = document.createElement('div');
const isUser = role === 'user';
div.className = 'rv-message ' + (isUser ? 'rv-msg-user' : 'rv-msg-assistant');
const roleBadge = document.createElement('div');
roleBadge.className = 'rv-role ' + (isUser ? 'rv-role-user' : 'rv-role-assistant');
roleBadge.textContent = isUser ? 'You' : agentLabel;
div.appendChild(roleBadge);
const renderedText = document.createElement('div');
renderedText.className = 'rv-text';
renderedText.innerHTML = this._renderMarkdown(text);
div.appendChild(renderedText);
return div;
}
_getResponseViewerAgentLabel() {
const mode = this.sessions.get(this.activeSessionId)?.mode;
return mode === 'codex'
? 'Codex'
: mode === 'gemini'
? 'Gemini'
: mode === 'antigravity'
? 'Antigravity'
: mode === 'opencode'
? 'OpenCode'
: 'Claude';
}
async toggleResponseViewer() {
const viewer = document.getElementById('responseViewer');
const backdrop = document.getElementById('responseViewerBackdrop');
@@ -1958,7 +1989,11 @@ class CodemanApp {
const body = document.getElementById('responseViewerBody');
if (lastResponse) {
body.innerHTML = this._renderMarkdown(lastResponse);
// Keep the brief view inside the same message wrapper as the full
// conversation view. The wrapper supplies the card, role badge and
// descendant markdown styles that direct body children do not get.
body.innerHTML = '';
body.appendChild(this._buildResponseViewerMessage(lastResponse, 'assistant', this._getResponseViewerAgentLabel()));
this._bindResponseViewerInteractions(body);
} else {
body.textContent =
@@ -1998,26 +2033,10 @@ class CodemanApp {
}
// Render conversation thread
const mode = this.sessions.get(this.activeSessionId)?.mode;
const agentLabel =
mode === 'codex' ? 'Codex' : mode === 'gemini' ? 'Gemini' : mode === 'antigravity' ? 'Antigravity' : mode === 'opencode' ? 'OpenCode' : 'Claude';
const agentLabel = this._getResponseViewerAgentLabel();
body.innerHTML = '';
for (const msg of messages) {
const div = document.createElement('div');
const isUser = msg.role === 'user';
div.className = 'rv-message ' + (isUser ? 'rv-msg-user' : 'rv-msg-assistant');
const role = document.createElement('div');
role.className = 'rv-role ' + (isUser ? 'rv-role-user' : 'rv-role-assistant');
role.textContent = isUser ? 'You' : agentLabel;
div.appendChild(role);
const text = document.createElement('div');
text.className = 'rv-text';
text.innerHTML = this._renderMarkdown(msg.text);
div.appendChild(text);
body.appendChild(div);
body.appendChild(this._buildResponseViewerMessage(msg.text, msg.role, agentLabel));
}
this._bindResponseViewerInteractions(body);
+9
View File
@@ -20,6 +20,15 @@ describe('frontend public asset tooling', () => {
expect(appJs.includes(0)).toBe(false);
});
it('uses the same message wrapper for brief and full response views', () => {
const appJs = readFileSync(resolve(repoRoot, 'src/web/public/app.js'), 'utf8');
expect(appJs).toContain("body.appendChild(this._buildResponseViewerMessage(lastResponse, 'assistant'");
expect(appJs).toContain('body.appendChild(this._buildResponseViewerMessage(msg.text, msg.role, agentLabel));');
expect(appJs).toContain("div.className = 'rv-message ' + (isUser ? 'rv-msg-user' : 'rv-msg-assistant');");
expect(appJs).toContain("renderedText.className = 'rv-text';");
});
it('runs the public asset check script', () => {
expect(() => {
execFileSync('npm', ['run', 'check:public-assets', '--silent'], {