Skip to content

Set color decorator CSS variables only when an editor shows colors - #342235

Draft
Karthik Nadig (karthiknadig) wants to merge 1 commit into
mainfrom
karthiknadig/bug-swarm/c2-f1-code-block-editor-layout
Draft

Karthik Nadig (karthiknadig) wants to merge 1 commit into
mainfrom
karthiknadig/bug-swarm/c2-f1-code-block-editor-layout

Conversation

@karthiknadig

Copy link
Copy Markdown
Member

Refs #341282

Problem

Switching between two finished sessions in the Agents window, each with one large response containing 45 code blocks, takes a median of 2,728 ms on main and 2,379 ms with this PR (−349 ms, −13%), measured over 12 switches in 3 runs for each build.

Part of that time is spent restyling every code-block editor a second time, right after it first renders, even though none of these editors shows a color. This PR removes that second pass. Switching is still slow for other reasons, so #341282 is only partly addressed.

Root cause

ColorDetector is an editor contribution that is created for every code editor after its first render (colorPickerContribution.ts#L30). Since #332340, its constructor writes two CSS custom properties, --vscode-colorPicker-colorDecoratorWidth and --vscode-colorPicker-colorDecoratorMargin, on the editor's container element (colorDetector.ts#L64-L70).

Custom properties are inherited, so changing them invalidates the computed style of the whole editor subtree. The next layout read then recalculates style for every rendered line. The only reader of these properties is the .colorpicker-color-decoration rule (colorPicker.css#L18-L19), which only matches when color decorations exist. Editors that never show a color still pay for the restyle.

In the Agents window every rendered chat code block is its own code editor, so each session switch performs 90 of these writes (45 editors × 2 properties).

Fix

Set the two properties the first time ColorDetector shows at least one color decoration, just before the decorations are applied, and remove them when the contribution is disposed. This keeps the swatch size and margin unchanged for editors with colors, including when an editor switches from a model without colors to one with colors. It also skips the restyle for editors that have no colors.

Nothing else reads these variables. I searched the repo, and outside this file they appear only in colorPicker.css and the stylelint list of known variables. The change is in the shared editor contribution, so it also applies to other embedded editors that usually show no colors, such as notebook cells and diff editors. I only measured the Agents window case.

Before / after

Both recordings switch between the same two sessions four times. After the switches, the overlay shows the medians: how many code-block editors that had already rendered got their custom properties changed again, the large style recalculations (20,000 elements or more), total style recalculation, and switch time. These recordings were taken with tracing on, so their switch times are higher than the headline numbers above.

Before

Before: four switches between two large sessions on main. Each switch restyles 45 code-block editors again, with about 1.4 s of style recalculation per switch

After

After: the same four switches with this PR. No code-block editor is restyled again, and style recalculation drops to about 1.1 s per switch

Regular editor check with this PR: a CSS file still shows color swatches at the expected size after switching from a file without colors in the same editor.

CSS editor with this PR showing color swatches next to hex, rgb, hsl and named colors

Measurements

The setup was a Code OSS dev build from this branch and from main @ 1e81fc75bb5, on Windows with VSCODE_DEV_DISABLE_DISPOSABLE_TRACKING=1. The Agents window was connected to a local scripted Agent Host, with no model traffic. It served two finished sessions. Each session holds one ~100 KB markdown response: 50 sections with a list, a 4-row table and a 20-line TypeScript code block. The transcript renders 45 code-block editors.

What is measured How main This PR
Switch time (headline) Wall time from clicking the session in the list until its request and a section near the end of its response are visible (polled every animation frame). This run did not use tracing. There were 2 warm-up switches, then 4 measured switches per run, over 3 runs. median 2,728 ms (run medians 2,714 / 2,728 / 2,672) median 2,379 ms (run medians 2,374 / 2,398 / 2,376)
Custom-property writes on code-block editors that already rendered lines A MutationObserver on style attributes inside chat code blocks counts changes to --* properties on elements that contain .view-line. 4 switches per run, over 3 runs. 90 per switch in all 12 switches (45 editors × 2) None in any of the 12 switches, because the color detector no longer writes the properties on these editors
Style recalculation per switch Sum of UpdateLayoutTree durations on the renderer main thread in a Chrome trace (devtools.timeline). Measured from the click to 1.5 s after the transcript appears. Same 12 switches. median 1,431 ms median 1,104 ms (−327 ms, −23%)
Large style recalculations (≥ 20,000 elements) per switch Same trace, UpdateLayoutTree events with elementCount ≥ 20,000 3 per switch, median 897 ms 2 per switch, median 587 ms
Switch time with tracing on Same as the headline, while tracing run medians 2,811 / 2,712 / 2,720 ms run medians 2,437 / 2,393 / 2,574 ms

How to test

  1. Run a dev build and open a CSS file with colors, for example a { color: #007acc; background: rgb(220, 53, 69); }. The color swatches look the same as on main: same size and spacing before the color value.
  2. Open a file without colors, then switch to the CSS file in the same editor group. The swatches appear at the same size.
  3. In Developer Tools, an editor without colors no longer has the two properties on its container:
    document.querySelector('.monaco-editor').style.getPropertyValue('--vscode-colorPicker-colorDecoratorWidth') // '' without colors, '0.8em' with colors
  4. In the Agents window, open a long finished response with many code blocks and switch away and back. In a Performance recording, the switch has one fewer large "Recalculate Style" pass.
Snippet to count code-block restyles in the Agents window

Paste in Developer Tools before switching sessions. window.__restyle then gives the number of custom-property writes on code-block editors that had already rendered lines. On main it shows 2 per code block for each switch. With this PR it reports none.

window.__restyle = 0;
const custom = s => (s || '').split(';').map(x => x.trim()).filter(x => x.startsWith('--')).sort().join(';');
new MutationObserver(records => {
	for (const r of records) {
		const t = r.target;
		if (t.closest?.('.interactive-result-code-block') && t.querySelector('.view-line') && custom(r.oldValue) !== custom(t.getAttribute('style'))) {
			window.__restyle++;
		}
	}
}).observe(document.body, { subtree: true, attributes: true, attributeFilter: ['style'], attributeOldValue: true });

Validation

Check Command Result
New unit tests on main @ 1e81fc7 (source change reverted, new test kept) scripts\test.bat --run src/vs/editor/contrib/colorPicker/test/browser/colorDetector.test.ts ❌ Fails as expected. 2 of the 3 tests fail because the container already has 0.8em / 0.2em while the editor shows no colors. The dispose test passes on main too.
Unit tests with this PR scripts\test.bat --run src/vs/editor/contrib/colorPicker/test/browser/colorDetector.test.ts --run src/vs/editor/contrib/colorPicker/test/browser/defaultDocumentColorProvider.test.ts ✅ 9 passing, 0 failing
Type check npm run typecheck-client ✅ No errors
Lint npm run eslint -- src/vs/editor/contrib/colorPicker/browser/colorDetector.ts src/vs/editor/contrib/colorPicker/test/browser/colorDetector.test.ts ✅ No problems
Agents window switch time, main vs this PR Automated switch scenario over CDP, 3 runs × 4 switches per build (see Measurements) Median 2,728 ms on main, 2,379 ms with this PR
Code-block restyles and style recalculation, main vs this PR The same scenario with a Chrome trace and the MutationObserver counter, 3 runs × 4 switches per build On main: 90 property writes per switch and a 1,431 ms style-recalc median. With this PR: no such writes and a 1,104 ms median.
Chat code blocks behave the same with this PR Automated checks in the Agents window ✅ Text, syntax colors, editor heights, horizontal scrolling, the Apply/Insert toolbar, focus and selection, input drafts and scroll restore all match. No heap or DOM-node growth across switch cycles: heap 153.7 → 153.5 MB, DOM nodes 34,623 → 34,623.
CSS editor with colors, this PR Dev build. Open plain.css, split it into a new editor, then open colors.css in that same editor. ✅ While showing plain.css, the container has neither property. After switching to colors.css it has 0.8em / 0.2em, and the editor shows 6 swatches. Each is 11.2 px wide with 2.8 px margins at a 14 px font size, which is 0.8 em / 0.2 em as on main.

Found and fixed with automated exploratory testing. Draft until reviewed by a human.

Copilot AI balanced review requested due to automatic review settings October 17, 2026 07:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The narrow change preserves swatch sizing and cleanup, with focused regression coverage and no unresolved findings.

No open issues 🎉

What changed in this PR

Defers color-decorator CSS properties until an editor displays color swatches, avoiding unnecessary restyling and partially addressing slow Agents window session switching.

Changes:

  • Initializes sizing properties once, when decorations first appear, while preserving disposal cleanup.
  • Adds regression tests for colorless editors, model switching, and cleanup.
File Description
src/​vs/​editor/​contrib/​colorPicker/​test/​browser/​colorDetector.test.ts Tests deferred initialization, model switching, and disposal.
src/​vs/​editor/​contrib/​colorPicker/​browser/​colorDetector.ts Sets sizing properties only when color decorations first appear.

Review effort: Balanced

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Base: df835a71 Current: dcadbd69

No screenshot changes.

ColorDetector is created for every code editor after its first render and
wrote two inherited CSS custom properties on the editor container. That
restyled every rendered line, even in editors without colors such as chat
code blocks. Set them the first time color decorations are shown instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@karthiknadig
Karthik Nadig (karthiknadig) force-pushed the karthiknadig/bug-swarm/c2-f1-code-block-editor-layout branch from 2322b47 to c539df0 Compare October 18, 2026 05:09

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants