diff --git a/.changeset/brief-response-viewer-parity.md b/.changeset/brief-response-viewer-parity.md new file mode 100644 index 00000000..9aeaa3ce --- /dev/null +++ b/.changeset/brief-response-viewer-parity.md @@ -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. diff --git a/src/web/public/app.js b/src/web/public/app.js index f720e412..f463113e 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -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); diff --git a/test/frontend-public-tooling.test.ts b/test/frontend-public-tooling.test.ts index 0be11f45..945754ac 100644 --- a/test/frontend-public-tooling.test.ts +++ b/test/frontend-public-tooling.test.ts @@ -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'], {