fix(jetbrains): rescale Agent Manager list rows on IDE zoom - #13635
Merged
Conversation
The Agent Manager and session-history lists kept their pre-zoom geometry when the global IntelliJ interface zoom changed. Text grew because JBFont re-derives its size from "Label.font" on every read, but the theme insets, layout gaps and measured row heights around it are plain pixel counts that were never recomputed, so headers and row metrics drifted out of alignment. The shared row renderer never received the platform's Look-and-Feel pass. JList.updateUI only forwards to its renderer `if (renderer instanceof Component)`, and JBList wraps whatever it is given in a non-Component adapter, so the stamp was unreachable. The list now refreshes the stamp itself and drops its measured-height cache, which is otherwise keyed only on row data and list width and therefore compares equal across a zoom. Also fixes double scaling on the affected paths: JBUI.Borders scales what it is handed, so passing an already-scaled UiStyle.Gap value applied the factor twice. Gap gains unscaled constants for those call sites, Stack gains a settable base gap so a DPI-derived gap captured by a layout manager can be re-derived, and DiffStatBadge's `inset` becomes an unscaled step.
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (16 files)
Previous Review Summary (commit 037ae10)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 037ae10)Status: No Issues Found | Recommendation: Merge Files Reviewed (16 files)
Reviewed by grok-4.6 · Input: 210.2K · Output: 8.3K · Cached: 182.5K Review guidance: REVIEW.md from base branch |
The zoom helpers put a plain Font into the UI defaults. LookAndFeel's installColorsAndFont only replaces a component font that is null or a UIResource, so components adopted the zoomed font once and then refused every later update — the row height grew and never came back, failing the round-trip assertions on Linux CI while passing locally. LafManagerImpl.patchLafFonts installs FontUIResource values, so the helpers now do the same and the simulation matches the platform.
kirillk
enabled auto-merge
September 1, 2026 12:23
marius-kilocode
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
Fixes # — no filed issue. Reported directly while testing the JetBrains plugin: the Agent Manager list does not scale correctly when using the global IntelliJ interface zoom.
Context
Changing the global IntelliJ interface zoom (
View | Appearance | Zoom IDE In/Out) left the Agent Manager and session-history lists with their pre-zoom geometry.The text did scale, which made the breakage look partial rather than total: a
JBFontre-derives its size fromLabel.fonton every read (JBFont.getSize()→refreshScaledFont()), so fonts follow a zoom on their own. Everything else in a row — theme insets, layout gaps,BorderLayout.hgap, and the measured row height — is a plain pixel count resolved once throughJBUI.scale. Those stayed put, so the section header band and the row metrics drifted out of alignment with text that had grown.Implementation
Root cause: the shared row renderer never received the platform's Look-and-Feel pass.
javax.swing.JList.updateUIonly forwards to its renderer when the renderer is itself aComponent:JBList.setCellRendererwraps whatever it is given inExpandedItemListCellRendererWrapper, which implementsListCellRendererbut is not aComponent. The renderer is also not a child of the list, soIJSwingUtilities.updateComponentTreeUI(frame)never reaches it either.ActiveListRenderer.updateUI()was therefore never called on a zoom.ActiveListViewnow refreshes the stamp itself and drops its measured-height cache. That cache is keyed on(config, list.width, rows)— a zoom changes none of those, so it compared equal andsyncCellHeightkept the pre-zoom height.All DPI-derived values in the renderer moved into one
syncScale()applied from both the constructors andupdateUI(), since a layout manager captures its gap and an assigned border its insets at creation time.Also fixes double scaling on these paths.
JBUI.Bordersscales what it is handed (JBEmptyBorder→JBInsets, whose javadoc says "You should pass unscaled values only!"), so passing an already-scaledUiStyle.Gap.md()applied the factor twice — invisible at 100%, drifting as soon as the IDE is zoomed.UiStyle.Gapgains unscaled constants for those call sites, andDiffStatBadge'sinsetparameter becomes an unscaled step.Two supporting changes reviewers may want to look at:
Stackgains a settablespace, so a DPI-derived gap captured by its layout manager can be re-derived rather than frozen.rescale()measures twice: once inline and once viainvokeLater.IJSwingUtilities.updateComponentTreeUIvisits children before parents, so whenlist.updateUI()runs the list's ancestors still hold pre-zoom theme values. The deferred pass lets the tree settle at the new scale first — the same orderingHistoryPanel.bindTheme()already relies on.Scope note: the same
JBUI.Borders.empty(UiStyle.Gap.xx())double-scaling pattern exists at ~90 other call sites across the plugin. Only the ones on the list/Agent Manager path are fixed here; the plugin-wide sweep is mechanical, carries real visual-regression risk, and belongs in its own PR.Things I got wrong while investigating, in case it saves a reviewer the same detour: I initially assumed
font = JBFont.small()was frozen inChangesPanel,DiffStatBadge, andFilledBadgeIcon. It is not —JBFontself-rescales, and re-applying the font inupdateUI()would also have clobbered the font hosts push in throughPrHeaderView.applyStyle. Those files therefore only get their genuinely-frozen parts fixed: layout gaps,iconTextGap, and the double-scaled borders.Screenshots / Video
Before
After
How to Test
Manual/local verification
./gradlew typecheck test --rerun-tasksfrompackages/kilo-jetbrains/— green acrossshared,frontend, andbackend. Agent-executed.CI green on this branch, including
jetbrains / jetbrains(4593 tests). One unrelated flake,SessionUpdateQueueTest > test update hooks run on EDT around history and recovery, failed once and then passed on re-run with no code change; it does not touch this diff. The--rerun-tasksmatters: Gradle served a stale instrumented jar during intermediate runs and briefly reported false results, so every verification below was done with a forced rebuild.Instrumented the real
AgentManagerPanel(main row plus a worktree row withfiles=14, +742/-169, ahead=2, behind=41and a PR badge — the tallest row shape the panel renders) and drove it through repeated zooms, applying only what the platform does: raise the*.fontdefaults, raise the user scale, thenIJSwingUtilities.updateComponentTreeUI(panel). Agent-executed:Confirmed
test renderer layout gaps are re derived after a zoomfails without the renderer refresh and passes with it, each direction on a forced rebuild. Agent-executed.Reviewer test steps
./gradlew runIdefrompackages/kilo-jetbrains/).View | Appearance | Zoom IDE In(or set a zoom level inSettings | Appearance).N files -x +ymetrics all grow in proportion to the text.Blocked checks and substitute verification
Not fully verified. The reporter still sees row padding grow and not return after zooming back out, on a build that includes these changes. I could not reproduce that in-process: the faithful simulation above round-trips exactly, across two full zoom cycles, on the real panel.
What I ruled out: the platform ordering is correct (
LafManagerImpl.updateUIrunspatchLafFonts, which sets the font defaults andJBUIScale.setUserScaleFactor, at line 823 — beforenotifyLookAndFeelChangedat 843 and the tree walk at 844), andpatchHiDPIis idempotent for already-converted values.One detail argues for something accumulating rather than a single mis-scaled value: the reported rows are roughly 170px tall, while the instrumented panel measures 56px at 100% and 70px at 125%. Even 200% would only reach ~112px.
So this PR is a real, test-covered fix for a real defect on this path, but it may not be the whole story. Landing it separately from the remaining investigation keeps the verified part reviewable, and the new
ActiveListScaleTestgives the follow-up a place to add a failing case. Happy to hold this as a draft if a reviewer would rather wait for the full root cause.A promising lead for that follow-up, surfaced by CI. The first CI run failed the three "shrinks back" assertions on Linux with the height stuck at its zoomed value (
expected:<47> but was:<74>) while passing locally on macOS — the reporter's symptom exactly. The cause turned out to be in the test rather than the product: the zoom helper installed a plainFontinto the UI defaults, andLookAndFeel.installColorsAndFontonly replaces a component font that isnullor aUIResource, so components adopted the zoomed font once and then refused every later update.LafManagerImpl.patchLafFontsinstallsFontUIResourcevalues; the helpers now do the same (second commit) and CI is green.That mechanism is worth carrying into the follow-up. Any component in the row tree holding a non-
UIResource, non-self-rescaling font would be permanently pinned to the size it was assigned at — growing on one zoom and never shrinking.RelativeFont.deriveis a concrete candidate: handed aUIResourceit re-wraps its result in a plainFontUIResource(RelativeFont.java:179-181), losingJBFont's self-rescaling, andActiveListRenderer.syncHeaderderives the section caption font fromsep.fonton every render — so once that font is a plainFontUIResourcethe caption is pinned. Whethersep.fontever becomes aUIResourceis LaF-dependent, which would explain why this reproduces for the reporter and not for me. I did not change it here: I tried resetting that font earlier in this branch and it visibly altered the caption size (12px to 11px), so it needs a verified repro rather than another speculative edit.No screenshots of the visual result, for the reason given in that section.
Checklist
Get in Touch