diff --git a/.qwen/e2e-tests/electron-desktop/conversation-surface-fidelity.md b/.qwen/e2e-tests/electron-desktop/conversation-surface-fidelity.md new file mode 100644 index 000000000..a66a49361 --- /dev/null +++ b/.qwen/e2e-tests/electron-desktop/conversation-surface-fidelity.md @@ -0,0 +1,69 @@ +# Conversation Surface Prototype Fidelity + +- Slice date: 2026-04-26 +- Executable harness: `packages/desktop/scripts/e2e-cdp-smoke.mjs` +- Command: + `cd packages/desktop && npm run e2e:cdp` +- Result: pass +- Artifact directory: + `.qwen/e2e-tests/electron-desktop/artifacts/2026-04-26T01-26-44-948Z/` + +## Scenario + +1. Launch the real Electron app with isolated HOME, runtime, user-data, and a + fake dirty Git workspace. +2. Open the fake project through the desktop directory picker path. +3. Send the first composer prompt and approve the fake command request. +4. Wait for the dense fake ACP assistant response, file-reference chips, + assistant actions, and changed-files summary. +5. Capture computed styles and geometry for the assistant message, user prompt, + changed-files summary, action buttons, timeline, and document width. +6. Save `conversation-surface-fidelity.json` and + `conversation-surface-fidelity.png`, then continue the compact conversation, + compact review, review safety, commit, settings, and terminal workflows. + +## Assertions + +- Assistant message border widths are all `0`. +- Assistant message background alpha is `0`, so assistant prose is not a + framed card. +- User prompt remains a compact right-aligned bubble with a visible border and + background alpha of `0.1`. +- Changed-files summary remains an inline supporting surface with background + alpha `0.024`, border alpha `0.11`, and height `153.5` px. +- Changed-file rows do not render as nested cards. +- Changed-files `Review Changes` action height is `30` px. +- Assistant action buttons remain compact at `28x28` px. +- Document scroll width stayed equal to the `1240` px viewport. +- Console errors: 0. +- Failed local network requests: 0. + +## Artifacts + +- `conversation-surface-fidelity.json` +- `conversation-surface-fidelity.png` +- `assistant-message-actions.json` +- `assistant-message-actions.png` +- `compact-dense-conversation.json` +- `compact-dense-conversation.png` +- `compact-review-drawer.json` +- `compact-review-drawer.png` +- `review-drawer-layout.json` +- `terminal-expanded-layout.json` +- `electron.log` +- `summary.json` + +## Failed Run Fixed + +Iteration 15 produced a partial artifact directory at +`.qwen/e2e-tests/electron-desktop/artifacts/2026-04-26T01-19-16-242Z/`. +That run stopped before `conversation-surface-fidelity.json` or `summary.json` +were written. Iteration 16 reran the full CDP smoke successfully with the same +slice changes and produced the passing artifact directory above. + +## Known Uncovered Risk + +The harness validates deterministic fake ACP content at the default and compact +desktop sizes. Long branch names, long model names, and unusually long project +names with the review drawer open still need a focused truncation and overflow +assertion. diff --git a/design/qwen-code-electron-desktop-implementation-plan.md b/design/qwen-code-electron-desktop-implementation-plan.md index 0694d3825..f201772a9 100644 --- a/design/qwen-code-electron-desktop-implementation-plan.md +++ b/design/qwen-code-electron-desktop-implementation-plan.md @@ -22,6 +22,109 @@ execution order, verification, decisions, and remaining work. ## Codex Alignment Progress +### Completed Slice: Conversation Surface Prototype Fidelity + +Status: completed in iteration 16. + +Goal: reduce the remaining boxed/dashboard treatment in the conversation +timeline so assistant prose reads as the main workbench surface, while changed +files and tool/activity summaries remain compact supporting surfaces. + +User-visible value: the first viewport moves closer to `home.jpg`: the +conversation feels like a coding-agent timeline instead of stacked cards, with +less border noise and a lighter inline review entry point. + +Expected files: + +- `packages/desktop/src/renderer/styles.css` +- `packages/desktop/scripts/e2e-cdp-smoke.mjs` +- `.qwen/e2e-tests/electron-desktop/conversation-surface-fidelity.md` +- `design/qwen-code-electron-desktop-implementation-plan.md` + +Acceptance criteria: + +- Assistant messages no longer render as visibly framed cards. +- User prompts remain readable as compact right-aligned bubbles. +- Changed-files summary and tool/activity surfaces retain accessible + landmarks/actions but use subtler borders, backgrounds, and tighter density. +- Existing assistant file-reference, action-row, changed-files, review, + settings, and terminal flows continue to pass. +- Real Electron CDP coverage records computed surface styles and geometry, and + fails if assistant messages regain a visible card frame or if the changed + files summary becomes visually heavy again. + +Verification: + +- Unit/component test command: no component logic change expected. +- Syntax command: `node --check packages/desktop/scripts/e2e-cdp-smoke.mjs` +- Build/typecheck/lint commands: + `cd packages/desktop && npm run typecheck && npm run lint && npm run build` +- Real Electron harness: + `cd packages/desktop && npm run e2e:cdp` +- Harness path: `packages/desktop/scripts/e2e-cdp-smoke.mjs` +- E2E scenario steps: launch real Electron with isolated HOME/runtime/user-data + and fake ACP, open the fake project, send a prompt, approve the fake command + request, wait for the dense assistant response and changed-files summary, + assert assistant action/file chips, assert conversation surface computed + styles and geometry, capture screenshot/JSON artifacts, then continue the + existing compact, review, settings, and terminal workflow. +- E2E assertions: assistant message border widths are zero and background is + transparent, user message remains a compact bubble, changed-files summary + uses subtle border/background alpha and stays shorter than the previous + dashboard-like card, action buttons remain compact, and console + errors/failed local requests are absent. +- Diagnostic artifacts: `conversation-surface-fidelity.json`, + `conversation-surface-fidelity.png`, plus existing CDP screenshots, Electron + log, and summary JSON under `.qwen/e2e-tests/electron-desktop/artifacts/`. +- Required skills applied: `frontend-design` for prototype-constrained visual + hierarchy and density; `electron-desktop-dev` for renderer/CDP real Electron + verification; `brainstorming` applied by choosing the smallest fidelity + slice from the recorded next-work items instead of introducing new product + scope. + +Notes and decisions: + +- `frontend-design` was applied with the extra Ralph constraint that + `home.jpg` wins over inventing a new visual direction. The slice removes the + remaining assistant message frame instead of adding a new card treatment, and + keeps changed-files/tool summaries as quieter supporting inline surfaces. +- `electron-desktop-dev` was applied by extending the real Electron CDP smoke + path with computed-style and geometry assertions, not just visual inspection. +- The unframed assistant message still uses compact action icon buttons and + file chips so the timeline remains actionable without becoming a dashboard. +- Changed-files summary rows now read more like a compact inline table: no + nested row cards, subtler background, and a 30 px review action. +- Iteration 15 left this slice uncommitted after starting a CDP run that did + not reach the new fidelity assertion. Iteration 16 reran the full verification + and recorded the passing artifacts below. + +Verification results: + +- `node --check packages/desktop/scripts/e2e-cdp-smoke.mjs` passed. +- `cd packages/desktop && npm run typecheck` passed. +- `cd packages/desktop && npm run lint` passed. +- `cd packages/desktop && npm run build` passed. +- `cd packages/desktop && npm run e2e:cdp` passed after launch through real + Electron over CDP, including the new conversation surface fidelity assertion + and the existing compact conversation, compact review, settings, terminal, + review safety, and commit workflows. +- Passing artifacts: + `.qwen/e2e-tests/electron-desktop/artifacts/2026-04-26T01-26-44-948Z/`. +- Key recorded metrics: assistant message border widths were all `0`, + assistant background alpha was `0`, changed-files summary background alpha + was `0.024`, changed-files summary border alpha was `0.11`, changed-files + summary height was `153.5`, and the document scroll width stayed equal to the + `1240` px viewport. + +Next work: + +- Continue prototype fidelity by reducing remaining visual heaviness in tool + result cards and topbar/sidebar typography visible in the current CDP + screenshots. +- Add focused long branch/model/project-name CDP coverage with review open, + since compact review and composer chips now rely on truncation to avoid + overflow. + ### Completed Slice: Compact Review Drawer CDP Coverage Status: completed in iteration 14. diff --git a/packages/desktop/scripts/e2e-cdp-smoke.mjs b/packages/desktop/scripts/e2e-cdp-smoke.mjs index 0b56dce23..fd95a358e 100644 --- a/packages/desktop/scripts/e2e-cdp-smoke.mjs +++ b/packages/desktop/scripts/e2e-cdp-smoke.mjs @@ -86,6 +86,8 @@ async function main() { await saveScreenshot('resolved-tool-activity.png'); await assertAssistantMessageActions('assistant-message-actions.json'); await saveScreenshot('assistant-message-actions.png'); + await assertConversationSurfaceFidelity('conversation-surface-fidelity.json'); + await saveScreenshot('conversation-surface-fidelity.png'); await setElectronWindowBounds(target.id, compactWindowBounds); await assertCompactDenseConversationLayout( 'compact-dense-conversation.json', @@ -1133,6 +1135,242 @@ async function assertAssistantMessageActions(fileName) { } } +async function assertConversationSurfaceFidelity(fileName) { + await waitForSelector('[data-testid="conversation-changes-summary"]'); + const snapshot = await evaluate(`(() => { + const rectFor = (element) => { + if (!element) { + return null; + } + const rect = element.getBoundingClientRect(); + return { + top: rect.top, + right: rect.right, + bottom: rect.bottom, + left: rect.left, + width: rect.width, + height: rect.height + }; + }; + const alphaFromColor = (color) => { + if (!color || color === 'transparent') { + return 0; + } + + const match = color.match(/rgba?\\(([^)]+)\\)/u); + if (!match) { + return 1; + } + + const parts = match[1].split(',').map((part) => part.trim()); + if (parts.length < 4) { + return 1; + } + + const alpha = Number(parts[3]); + return Number.isFinite(alpha) ? alpha : 1; + }; + const numberFromPixel = (value) => { + const number = Number.parseFloat(value); + return Number.isFinite(number) ? number : 0; + }; + const styleFor = (element) => { + if (!element) { + return null; + } + + const style = window.getComputedStyle(element); + return { + backgroundColor: style.backgroundColor, + backgroundAlpha: alphaFromColor(style.backgroundColor), + borderColor: style.borderTopColor, + borderAlpha: alphaFromColor(style.borderTopColor), + borderTopWidth: numberFromPixel(style.borderTopWidth), + borderRightWidth: numberFromPixel(style.borderRightWidth), + borderBottomWidth: numberFromPixel(style.borderBottomWidth), + borderLeftWidth: numberFromPixel(style.borderLeftWidth), + borderRadius: style.borderTopLeftRadius + }; + }; + const assistantMessage = [ + ...document.querySelectorAll('[data-testid="assistant-message"]') + ].find((candidate) => + candidate.innerText.includes('E2E fake ACP response received') + ); + const userMessage = document.querySelector('.chat-message-user'); + const summary = document.querySelector( + '[data-testid="conversation-changes-summary"]' + ); + const summaryAction = summary?.querySelector( + 'button[aria-label="Review Changes"]' + ); + const firstSummaryRow = summary?.querySelector( + '.conversation-changes-list li' + ); + const timeline = document.querySelector('.chat-timeline'); + const actionButtons = assistantMessage + ? [ + ...assistantMessage.querySelectorAll( + '[data-testid="assistant-message-actions"] button' + ) + ] + : []; + + return { + assistant: { + rect: rectFor(assistantMessage), + style: styleFor(assistantMessage) + }, + user: { + rect: rectFor(userMessage), + style: styleFor(userMessage) + }, + summary: { + rect: rectFor(summary), + style: styleFor(summary), + actionRect: rectFor(summaryAction), + rowStyle: styleFor(firstSummaryRow) + }, + timeline: rectFor(timeline), + actionButtons: actionButtons.map((button) => ({ + label: button.getAttribute('aria-label') || '', + rect: rectFor(button), + style: styleFor(button) + })), + document: { + viewportWidth: window.innerWidth, + scrollWidth: document.documentElement.scrollWidth + } + }; + })()`); + + await writeFile( + join(artifactDir, fileName), + `${JSON.stringify(snapshot, null, 2)}\n`, + 'utf8', + ); + + if ( + !snapshot.assistant.rect || + !snapshot.assistant.style || + !snapshot.user.rect || + !snapshot.user.style || + !snapshot.summary.rect || + !snapshot.summary.style || + !snapshot.summary.actionRect || + !snapshot.summary.rowStyle || + !snapshot.timeline + ) { + throw new Error( + `Conversation surface fidelity metrics are missing: ${JSON.stringify( + snapshot, + )}`, + ); + } + + const assistantBorders = [ + snapshot.assistant.style.borderTopWidth, + snapshot.assistant.style.borderRightWidth, + snapshot.assistant.style.borderBottomWidth, + snapshot.assistant.style.borderLeftWidth, + ]; + if (assistantBorders.some((width) => width > 0)) { + throw new Error( + `Assistant message should render as unframed timeline prose: ${JSON.stringify( + snapshot.assistant.style, + )}`, + ); + } + + if (snapshot.assistant.style.backgroundAlpha > 0.02) { + throw new Error( + `Assistant message background is too card-like: ${JSON.stringify( + snapshot.assistant.style, + )}`, + ); + } + + if ( + snapshot.assistant.rect.left < snapshot.timeline.left || + snapshot.assistant.rect.right > snapshot.timeline.right + 1 + ) { + throw new Error('Assistant message escaped the conversation timeline.'); + } + + if ( + snapshot.user.rect.width > 620 || + snapshot.user.style.backgroundAlpha < 0.05 || + snapshot.user.style.borderTopWidth < 1 + ) { + throw new Error( + `User prompt bubble lost compact bubble treatment: ${JSON.stringify( + snapshot.user, + )}`, + ); + } + + if ( + snapshot.summary.style.borderAlpha > 0.14 || + snapshot.summary.style.backgroundAlpha > 0.045 + ) { + throw new Error( + `Changed-files summary surface is too visually heavy: ${JSON.stringify( + snapshot.summary.style, + )}`, + ); + } + + if (snapshot.summary.rect.height > 158) { + throw new Error( + `Changed-files summary should stay compact: ${JSON.stringify( + snapshot.summary.rect, + )}`, + ); + } + + if (snapshot.summary.rowStyle.backgroundAlpha > 0.04) { + throw new Error( + `Changed-files rows should not look like nested cards: ${JSON.stringify( + snapshot.summary.rowStyle, + )}`, + ); + } + + if (snapshot.summary.actionRect.height > 34) { + throw new Error( + `Changed-files action is too tall: ${JSON.stringify( + snapshot.summary.actionRect, + )}`, + ); + } + + for (const button of snapshot.actionButtons) { + if (!button.rect || !button.style) { + throw new Error( + `Assistant action button metrics are missing: ${JSON.stringify( + button, + )}`, + ); + } + + if (button.rect.width > 32 || button.rect.height > 32) { + throw new Error( + `Assistant action button should remain compact: ${JSON.stringify( + button, + )}`, + ); + } + } + + if (snapshot.document.scrollWidth > snapshot.document.viewportWidth + 4) { + throw new Error( + `Conversation surface introduced horizontal document overflow: ${JSON.stringify( + snapshot.document, + )}`, + ); + } +} + async function assertCompactDenseConversationLayout(fileName) { await waitFor( 'compact dense conversation viewport', diff --git a/packages/desktop/src/renderer/styles.css b/packages/desktop/src/renderer/styles.css index 4957afc98..7d2bac34b 100644 --- a/packages/desktop/src/renderer/styles.css +++ b/packages/desktop/src/renderer/styles.css @@ -678,7 +678,7 @@ summary:focus-visible { display: flex; min-height: 0; flex-direction: column; - gap: 12px; + gap: 14px; overflow: auto; padding: 10px 18px 18px; } @@ -688,10 +688,10 @@ summary:focus-visible { display: grid; width: min(860px, 100%); gap: 8px; - padding: 12px 14px; - border: 1px solid var(--line); - border-radius: var(--radius); - background: rgba(238, 244, 239, 0.04); + padding: 0; + border: 0; + border-radius: 0; + background: transparent; } .chat-message { @@ -699,15 +699,23 @@ summary:focus-visible { } .chat-message-user { - width: min(620px, 86%); + width: min(560px, 82%); align-self: flex-end; - border-color: rgba(240, 182, 110, 0.38); - background: var(--warning-soft); + padding: 10px 12px; + border: 1px solid rgba(240, 182, 110, 0.26); + border-radius: var(--radius); + background: rgba(240, 182, 110, 0.1); +} + +.chat-message-assistant { + padding: 2px 0 0; } .chat-message-thinking { - border-color: rgba(85, 166, 255, 0.32); - background: var(--accent-soft); + padding: 10px 12px; + border: 1px solid rgba(85, 166, 255, 0.22); + border-radius: var(--radius); + background: rgba(85, 166, 255, 0.08); } .chat-message p, @@ -795,6 +803,8 @@ summary:focus-visible { .chat-plan { align-self: center; + padding: 8px 0 8px 14px; + border-left: 2px solid rgba(85, 166, 255, 0.28); } .chat-plan ol { @@ -831,31 +841,29 @@ summary:focus-visible { width: min(860px, 100%); align-self: center; gap: 10px; - padding: 12px 14px; - border: 1px solid rgba(117, 217, 156, 0.22); + padding: 10px 12px; + border: 1px solid var(--line); + border-left: 2px solid rgba(117, 217, 156, 0.34); border-radius: var(--radius); - background: - linear-gradient(180deg, rgba(117, 217, 156, 0.07), transparent), - rgba(238, 244, 239, 0.035); + background: rgba(238, 244, 239, 0.025); color: var(--text-soft); } .conversation-tool-card-running { - border-color: rgba(85, 166, 255, 0.26); - background: - linear-gradient(180deg, rgba(85, 166, 255, 0.08), transparent), - rgba(238, 244, 239, 0.035); + border-color: var(--line); + border-left-color: rgba(85, 166, 255, 0.42); + background: rgba(85, 166, 255, 0.045); } .conversation-tool-card-complete { - border-color: rgba(117, 217, 156, 0.26); + border-color: var(--line); + border-left-color: rgba(117, 217, 156, 0.38); } .conversation-tool-card-danger { - border-color: rgba(255, 127, 105, 0.34); - background: - linear-gradient(180deg, rgba(255, 127, 105, 0.08), transparent), - rgba(238, 244, 239, 0.035); + border-color: var(--line); + border-left-color: rgba(255, 127, 105, 0.46); + background: rgba(255, 127, 105, 0.055); } .conversation-tool-heading { @@ -949,13 +957,11 @@ summary:focus-visible { display: grid; width: min(860px, 100%); align-self: center; - gap: 10px; - padding: 12px; - border: 1px solid rgba(85, 166, 255, 0.2); + gap: 6px; + padding: 10px; + border: 1px solid var(--line); border-radius: var(--radius); - background: - linear-gradient(180deg, rgba(85, 166, 255, 0.08), transparent), - rgba(238, 244, 239, 0.035); + background: rgba(238, 244, 239, 0.025); color: var(--text-soft); } @@ -1013,18 +1019,24 @@ summary:focus-visible { .conversation-changes-list { display: grid; - gap: 1px; + gap: 0; margin: 0; padding: 0; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); list-style: none; } .conversation-changes-list li { - min-height: 30px; + min-height: 27px; justify-content: space-between; - padding: 5px 8px; - border-radius: 6px; - background: rgba(8, 10, 11, 0.24); + padding: 4px 4px; + border-radius: 0; + background: transparent; +} + +.conversation-changes-list li + li { + border-top: 1px solid rgba(226, 232, 222, 0.07); } .conversation-changes-list span { @@ -1050,6 +1062,11 @@ summary:focus-visible { justify-content: flex-end; } +.conversation-changes-actions .secondary-button { + min-height: 30px; + padding: 0 10px; +} + .chat-scroll-anchor { width: 1px; height: 1px; @@ -2017,7 +2034,8 @@ textarea:focus { padding: 8px 10px 12px; } - .workspace-grid-review-open .chat-message, + .workspace-grid-review-open .chat-message-user, + .workspace-grid-review-open .chat-message-thinking, .workspace-grid-review-open .chat-plan, .workspace-grid-review-open .conversation-tool-card, .workspace-grid-review-open .conversation-changes-card, @@ -2025,6 +2043,10 @@ textarea:focus { padding: 10px; } + .workspace-grid-review-open .chat-message-assistant { + padding: 2px 0 0; + } + .workspace-grid-review-open .composer { width: calc(100% - 20px); gap: 6px;