diff --git a/desktop/frontend/package.json b/desktop/frontend/package.json index 23408d0cdf..3ffee6ee5a 100644 --- a/desktop/frontend/package.json +++ b/desktop/frontend/package.json @@ -11,9 +11,9 @@ "test:todo-visibility": "node scripts/test-todo-visibility.mjs", "typecheck": "tsc --noEmit", - "test": "node scripts/test-todo-visibility.mjs && tsx src/__tests__/anchored-popover-scroll.test.tsx && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/font-availability.test.ts && tsx src/__tests__/theme-auto-background.test.ts && tsx src/__tests__/typography-overflow-contract.test.ts && tsx src/__tests__/ask-card-layout.test.ts && tsx src/__tests__/provider-model-refresh.test.ts && tsx src/__tests__/composer-profile.test.ts && tsx src/__tests__/composer-history.test.ts && tsx src/__tests__/composer-keyboard.test.ts && tsx src/__tests__/composer-goal-toggle.test.tsx && tsx src/__tests__/composer-session-draft.test.tsx && tsx src/__tests__/use-controller-meta.test.ts && tsx src/__tests__/use-controller-cancel-reconcile.test.tsx && tsx src/__tests__/new-session-load-race.test.tsx && tsx src/__tests__/tab-switch-hydration.test.tsx && tsx src/__tests__/workspace-layout.test.ts && tsx src/__tests__/workspace-changes-errors.test.tsx && tsx src/__tests__/workspace-split.test.ts && tsx src/__tests__/workspace-preview-css.test.ts && tsx src/__tests__/resize-drag.test.ts && tsx src/__tests__/tool-approval-mode.test.ts && tsx src/__tests__/heartbeat-next-run.test.ts && tsx src/__tests__/keyboard-shortcuts.test.ts && tsx src/__tests__/send-failed.test.ts && tsx src/__tests__/edit-replay.test.ts && tsx src/__tests__/history-tool-status.test.ts && tsx src/__tests__/message-selection-copy.test.ts && tsx src/__tests__/reasoning-display.test.ts && tsx src/__tests__/attachment-display.test.ts && tsx src/__tests__/crash-reporting.test.ts && tsx src/__tests__/bridge-drag-rejection.test.ts && tsx src/__tests__/startup-settings-contract.test.ts && tsx src/__tests__/bundle-contract.test.ts && tsx src/__tests__/command-palette-css.test.ts && tsx src/__tests__/command-palette-interactions.test.tsx && tsx src/__tests__/context-panel-breakdown.test.ts && tsx src/__tests__/statusbar-workspace.test.tsx && tsx src/__tests__/settings-refresh-snapshot.test.tsx && tsx src/__tests__/capabilities-panel-actions.test.ts && tsx src/__tests__/tool-data-archive.test.ts && tsx src/__tests__/diff-rendering.test.ts && tsx src/__tests__/render-optimization.test.ts && tsx src/__tests__/transcript-grouping.test.ts && tsx src/__tests__/project-tree-runtime.test.ts && tsx src/__tests__/app-chrome-tabs.test.ts && tsx src/__tests__/open-topic-coalescing.test.tsx && tsx src/__tests__/memory-compiler-display.test.ts", + "test": "node scripts/test-todo-visibility.mjs && tsx src/__tests__/anchored-popover-scroll.test.tsx && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/font-availability.test.ts && tsx src/__tests__/theme-auto-background.test.ts && tsx src/__tests__/typography-overflow-contract.test.ts && tsx src/__tests__/ask-card-layout.test.ts && tsx src/__tests__/provider-model-refresh.test.ts && tsx src/__tests__/composer-profile.test.ts && tsx src/__tests__/composer-history.test.ts && tsx src/__tests__/composer-keyboard.test.ts && tsx src/__tests__/composer-goal-toggle.test.tsx && tsx src/__tests__/composer-session-draft.test.tsx && tsx src/__tests__/use-controller-meta.test.ts && tsx src/__tests__/use-controller-cancel-reconcile.test.tsx && tsx src/__tests__/new-session-load-race.test.tsx && tsx src/__tests__/tab-switch-hydration.test.tsx && tsx src/__tests__/workspace-layout.test.ts && tsx src/__tests__/workspace-changes-errors.test.tsx && tsx src/__tests__/workspace-split.test.ts && tsx src/__tests__/workspace-preview-css.test.ts && tsx src/__tests__/resize-drag.test.ts && tsx src/__tests__/tool-approval-mode.test.ts && tsx src/__tests__/heartbeat-next-run.test.ts && tsx src/__tests__/keyboard-shortcuts.test.ts && tsx src/__tests__/send-failed.test.ts && tsx src/__tests__/edit-replay.test.ts && tsx src/__tests__/history-tool-status.test.ts && tsx src/__tests__/message-selection-copy.test.ts && tsx src/__tests__/code-block-copy-position.test.tsx && tsx src/__tests__/reasoning-display.test.ts && tsx src/__tests__/attachment-display.test.ts && tsx src/__tests__/crash-reporting.test.ts && tsx src/__tests__/bridge-drag-rejection.test.ts && tsx src/__tests__/startup-settings-contract.test.ts && tsx src/__tests__/bundle-contract.test.ts && tsx src/__tests__/command-palette-css.test.ts && tsx src/__tests__/command-palette-interactions.test.tsx && tsx src/__tests__/context-panel-breakdown.test.ts && tsx src/__tests__/statusbar-workspace.test.tsx && tsx src/__tests__/settings-refresh-snapshot.test.tsx && tsx src/__tests__/capabilities-panel-actions.test.ts && tsx src/__tests__/tool-data-archive.test.ts && tsx src/__tests__/diff-rendering.test.ts && tsx src/__tests__/render-optimization.test.ts && tsx src/__tests__/transcript-grouping.test.ts && tsx src/__tests__/project-tree-runtime.test.ts && tsx src/__tests__/app-chrome-tabs.test.ts && tsx src/__tests__/open-topic-coalescing.test.tsx && tsx src/__tests__/memory-compiler-display.test.ts", "test:typecheck": "tsc --noEmit -p tsconfig.test.json", - "test:all": "tsc --noEmit -p tsconfig.test.json && node scripts/test-todo-visibility.mjs && tsx src/__tests__/anchored-popover-scroll.test.tsx && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/font-availability.test.ts && tsx src/__tests__/theme-auto-background.test.ts && tsx src/__tests__/typography-overflow-contract.test.ts && tsx src/__tests__/ask-card-layout.test.ts && tsx src/__tests__/provider-model-refresh.test.ts && tsx src/__tests__/composer-profile.test.ts && tsx src/__tests__/composer-history.test.ts && tsx src/__tests__/composer-keyboard.test.ts && tsx src/__tests__/composer-goal-toggle.test.tsx && tsx src/__tests__/composer-session-draft.test.tsx && tsx src/__tests__/use-controller-meta.test.ts && tsx src/__tests__/use-controller-cancel-reconcile.test.tsx && tsx src/__tests__/new-session-load-race.test.tsx && tsx src/__tests__/tab-switch-hydration.test.tsx && tsx src/__tests__/workspace-layout.test.ts && tsx src/__tests__/workspace-changes-errors.test.tsx && tsx src/__tests__/workspace-split.test.ts && tsx src/__tests__/workspace-preview-css.test.ts && tsx src/__tests__/resize-drag.test.ts && tsx src/__tests__/tool-approval-mode.test.ts && tsx src/__tests__/heartbeat-next-run.test.ts && tsx src/__tests__/keyboard-shortcuts.test.ts && tsx src/__tests__/send-failed.test.ts && tsx src/__tests__/edit-replay.test.ts && tsx src/__tests__/history-tool-status.test.ts && tsx src/__tests__/message-selection-copy.test.ts && tsx src/__tests__/reasoning-display.test.ts && tsx src/__tests__/attachment-display.test.ts && tsx src/__tests__/crash-reporting.test.ts && tsx src/__tests__/bridge-drag-rejection.test.ts && tsx src/__tests__/startup-settings-contract.test.ts && tsx src/__tests__/bundle-contract.test.ts && tsx src/__tests__/command-palette-css.test.ts && tsx src/__tests__/command-palette-interactions.test.tsx && tsx src/__tests__/context-panel-breakdown.test.ts && tsx src/__tests__/statusbar-workspace.test.tsx && tsx src/__tests__/settings-refresh-snapshot.test.tsx && tsx src/__tests__/capabilities-panel-actions.test.ts && tsx src/__tests__/tool-data-archive.test.ts && tsx src/__tests__/diff-rendering.test.ts && tsx src/__tests__/render-optimization.test.ts && tsx src/__tests__/transcript-grouping.test.ts && tsx src/__tests__/project-tree-runtime.test.ts && tsx src/__tests__/app-chrome-tabs.test.ts && tsx src/__tests__/open-topic-coalescing.test.tsx" + "test:all": "tsc --noEmit -p tsconfig.test.json && node scripts/test-todo-visibility.mjs && tsx src/__tests__/anchored-popover-scroll.test.tsx && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/font-availability.test.ts && tsx src/__tests__/theme-auto-background.test.ts && tsx src/__tests__/typography-overflow-contract.test.ts && tsx src/__tests__/ask-card-layout.test.ts && tsx src/__tests__/provider-model-refresh.test.ts && tsx src/__tests__/composer-profile.test.ts && tsx src/__tests__/composer-history.test.ts && tsx src/__tests__/composer-keyboard.test.ts && tsx src/__tests__/composer-goal-toggle.test.tsx && tsx src/__tests__/composer-session-draft.test.tsx && tsx src/__tests__/use-controller-meta.test.ts && tsx src/__tests__/use-controller-cancel-reconcile.test.tsx && tsx src/__tests__/new-session-load-race.test.tsx && tsx src/__tests__/tab-switch-hydration.test.tsx && tsx src/__tests__/workspace-layout.test.ts && tsx src/__tests__/workspace-changes-errors.test.tsx && tsx src/__tests__/workspace-split.test.ts && tsx src/__tests__/workspace-preview-css.test.ts && tsx src/__tests__/resize-drag.test.ts && tsx src/__tests__/tool-approval-mode.test.ts && tsx src/__tests__/heartbeat-next-run.test.ts && tsx src/__tests__/keyboard-shortcuts.test.ts && tsx src/__tests__/send-failed.test.ts && tsx src/__tests__/edit-replay.test.ts && tsx src/__tests__/history-tool-status.test.ts && tsx src/__tests__/message-selection-copy.test.ts && tsx src/__tests__/code-block-copy-position.test.tsx && tsx src/__tests__/reasoning-display.test.ts && tsx src/__tests__/attachment-display.test.ts && tsx src/__tests__/crash-reporting.test.ts && tsx src/__tests__/bridge-drag-rejection.test.ts && tsx src/__tests__/startup-settings-contract.test.ts && tsx src/__tests__/bundle-contract.test.ts && tsx src/__tests__/command-palette-css.test.ts && tsx src/__tests__/command-palette-interactions.test.tsx && tsx src/__tests__/context-panel-breakdown.test.ts && tsx src/__tests__/statusbar-workspace.test.tsx && tsx src/__tests__/settings-refresh-snapshot.test.tsx && tsx src/__tests__/capabilities-panel-actions.test.ts && tsx src/__tests__/tool-data-archive.test.ts && tsx src/__tests__/diff-rendering.test.ts && tsx src/__tests__/render-optimization.test.ts && tsx src/__tests__/transcript-grouping.test.ts && tsx src/__tests__/project-tree-runtime.test.ts && tsx src/__tests__/app-chrome-tabs.test.ts && tsx src/__tests__/open-topic-coalescing.test.tsx" }, "dependencies": { diff --git a/desktop/frontend/src/__tests__/code-block-copy-position.test.tsx b/desktop/frontend/src/__tests__/code-block-copy-position.test.tsx new file mode 100644 index 0000000000..aef3ff6ca2 --- /dev/null +++ b/desktop/frontend/src/__tests__/code-block-copy-position.test.tsx @@ -0,0 +1,86 @@ +// Run: tsx src/__tests__/code-block-copy-position.test.tsx + +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { JSDOM } from "jsdom"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import HljsCode from "../components/editors/HljsCode"; +import { LocaleProvider } from "../lib/i18n"; + +let passed = 0; +let failed = 0; + +function ok(value: unknown, label: string) { + if (value) { + process.stdout.write(` PASS ${label}\n`); + passed += 1; + } else { + process.stdout.write(` FAIL ${label}\n`); + failed += 1; + } +} + +function notOk(value: unknown, label: string) { + ok(!value, label); +} + +function renderCodeBlock() { + return renderToStaticMarkup( + createElement( + LocaleProvider, + null, + createElement(HljsCode, { + value: "const veryLongLine = 'x'.repeat(240);", + language: "ts", + maxHeight: 120, + }), + ), + ); +} + +function selectorPresent(css: string, selector: string): boolean { + return css.includes(selector); +} + +console.log("\ncode block copy position"); + +const markup = renderCodeBlock(); +const dom = new JSDOM(markup); +const document = dom.window.document; +const wrap = document.querySelector(".code-block__wrap"); +const pre = document.querySelector("pre.code.hljs"); +const copy = document.querySelector(".code-block__copy"); + +ok(wrap, "HljsCode renders a positioned copy wrapper"); +ok(pre, "HljsCode renders the highlighted pre"); +ok(copy, "HljsCode renders the copy button"); +ok(pre?.parentElement === wrap, "scrollable pre is inside the wrapper"); +ok(copy?.parentElement === wrap, "copy button is a wrapper child"); +notOk(pre?.contains(copy ?? null), "copy button is outside the scrollable pre"); +ok(copy?.previousElementSibling === pre, "copy button follows pre as a sibling"); + +const stylesPath = fileURLToPath(new URL("../styles.css", import.meta.url)); +const css = readFileSync(stylesPath, "utf8"); + +ok(selectorPresent(css, ".code-block__wrap:hover .code-block__copy"), "hover reveal targets the wrapper"); +notOk(selectorPresent(css, ".code:hover .code-block__copy"), "hover reveal no longer depends on pre descendants"); +ok( + selectorPresent(css, ".app--creation .msg--assistant .md .code:not([data-lang]) + .code-block__copy"), + "creation unlabelled code button styles target sibling copy buttons", +); +notOk( + selectorPresent(css, ".app--creation .msg--assistant .md .code:not([data-lang]) .code-block__copy"), + "creation unlabelled code styles do not target descendants inside pre", +); +ok( + selectorPresent(css, '.app--creation .msg--assistant .md .code[data-lang="bash"] + .code-block__copy'), + "creation shell code button styles target sibling copy buttons", +); +notOk( + selectorPresent(css, '.app--creation .msg--assistant .md .code[data-lang="bash"] .code-block__copy'), + "creation shell code styles do not target descendants inside pre", +); + +console.log(`\n${passed} passed, ${failed} failed`); +if (failed > 0) process.exit(1); diff --git a/desktop/frontend/src/components/editors/HljsCode.tsx b/desktop/frontend/src/components/editors/HljsCode.tsx index 88a914283b..1db4c6279a 100644 --- a/desktop/frontend/src/components/editors/HljsCode.tsx +++ b/desktop/frontend/src/components/editors/HljsCode.tsx @@ -10,10 +10,12 @@ import { CopyButton } from "../CopyButton"; const HljsCode = memo(function HljsCode({ value, language, maxHeight }: EditorProps) { const html = useMemo(() => highlightToHtml(value, language), [value, language]); return ( -
-++); }); diff --git a/desktop/frontend/src/styles.css b/desktop/frontend/src/styles.css index 86be9bddc6..b5cddc7a56 100644 --- a/desktop/frontend/src/styles.css +++ b/desktop/frontend/src/styles.css @@ -4495,7 +4495,7 @@ a[href] { transition: opacity 0.12s; } .code-block__copy:hover, -.code:hover .code-block__copy { +.code-block__wrap:hover .code-block__copy { opacity: 1; } .code-block__wrap { @@ -24888,8 +24888,8 @@ a[href] { word-break: break-word; } -.app--creation .msg--assistant .md .code:not([data-lang]) .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code:not([data-lang]) .code-block__copy { +.app--creation .msg--assistant .md .code:not([data-lang]) + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code:not([data-lang]) + .code-block__copy { top: 2px; right: 2px; color: color-mix(in srgb, var(--fg-faint) 58%, var(--bg)); @@ -24957,16 +24957,16 @@ a[href] { content: ">"; } -.app--creation .msg--assistant .md .code[data-lang="bash"] .code-block__copy, -.app--creation .msg--assistant .md .code[data-lang="shell"] .code-block__copy, -.app--creation .msg--assistant .md .code[data-lang="sh"] .code-block__copy, -.app--creation .msg--assistant .md .code[data-lang="powershell"] .code-block__copy, -.app--creation .msg--assistant .md .code[data-lang="pwsh"] .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="bash"] .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="shell"] .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="sh"] .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="powershell"] .code-block__copy, -:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="pwsh"] .code-block__copy { +.app--creation .msg--assistant .md .code[data-lang="bash"] + .code-block__copy, +.app--creation .msg--assistant .md .code[data-lang="shell"] + .code-block__copy, +.app--creation .msg--assistant .md .code[data-lang="sh"] + .code-block__copy, +.app--creation .msg--assistant .md .code[data-lang="powershell"] + .code-block__copy, +.app--creation .msg--assistant .md .code[data-lang="pwsh"] + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="bash"] + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="shell"] + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="sh"] + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="powershell"] + .code-block__copy, +:root[data-theme-style] .app--creation .msg--assistant .md .code[data-lang="pwsh"] + .code-block__copy { top: 5px; right: 5px; color: color-mix(in srgb, var(--fg-faint) 58%, var(--bg));++- +