fix(settings): fit long versions in the sidebar and keep side panels off the window edge (#1875)

* fix(settings): fit long versions in the sidebar and keep side panels off the window edge

The About item's version was flex-shrink: 0 beside a zero-basis label, so a
nightly build string squeezed "About" to nothing and overflowed the panel.
The label now has an auto basis and the version yields first, truncating
with an ellipsis and keeping the full string in its tooltip.

The context panel used height: 100% plus padding without a border-box reset,
so it ran 24px past its host and the footer item sat on the window edge.
The host is now a flex column and the panel fills it as a flex item, which
restores the bottom padding for every side panel variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(settings): answer the GitHub releases check in the version layout E2E

The sidebar renders the version only after the releases request answers.
CI runners are rate-limited on api.github.com, so the tag never appeared
and the test failed on its first locator. Fulfil the request locally with
an older release so the version shows without an update badge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 authored and GitHub committed 2026-10-10 21:49:57 +02:00
1 parent e1d4f00d93
commit 91e277e9ba
6 files changed
+83 -4

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
---
type: fix
area: settings
---
A long nightly version no longer pushes "About" out of the settings sidebar:
the version shortens with an ellipsis and shows in full on hover. Side panels
also keep their bottom spacing instead of running into the window edge.
+50
View File
@@ -229,6 +229,56 @@ test.describe('Settings', () => {
).toHaveAttribute('aria-checked', 'true');
});
test('@settings @web About keeps its label and clears the window bottom', async ({
page,
}) => {
// The sidebar shows the version only once the GitHub releases check
// answers; CI runners are often rate-limited there, so answer locally
// with an older release (no update badge replaces the version).
await page.route('**/repos/4gray/iptvnator/releases*', (route) =>
route.fulfill({
json: [{ created_at: '2020-01-01T00:00:00Z', name: '0.0.1' }],
})
);
await openSettings(page);
const about = page.locator(
'.settings-nav__footer [data-test-id="settings-section-about"]'
);
const version = about.locator('[data-test-id="settings-nav-version"]');
await expect(version).toBeVisible();
// A nightly build string is far wider than the default panel.
await version.evaluate((node) => {
node.textContent = '0.25.0-nightly.20261010.3937';
});
const layout = await about.evaluate((link) => {
const label = link.querySelector('.nav-item-label') as HTMLElement;
const tag = link.querySelector(
'[data-test-id="settings-nav-version"]'
) as HTMLElement;
const panel = link.closest('.context-panel') as HTMLElement;
const linkBox = link.getBoundingClientRect();
return {
labelClipped: label.scrollWidth > label.clientWidth,
versionClipped: tag.scrollWidth > tag.clientWidth,
linkOverflows: link.scrollWidth > link.clientWidth,
linkBottom: linkBox.bottom,
panelBottom: panel.getBoundingClientRect().bottom,
viewportHeight: window.innerHeight,
};
});
expect(layout.labelClipped).toBe(false);
expect(layout.versionClipped).toBe(true);
expect(layout.linkOverflows).toBe(false);
// The panel ends at the window edge, and its bottom padding keeps the
// footer item off it.
expect(layout.panelBottom).toBeLessThanOrEqual(layout.viewportHeight);
expect(
layout.viewportHeight - layout.linkBottom
).toBeGreaterThanOrEqual(12);
});
test('@settings @web Esc leaves settings like the header Back', async ({
page,
}) => {
@@ -112,8 +112,10 @@
}
}
// Auto basis, not `flex: 1`: a zero basis gives the label no claim on the
// row, so any wide trailing element squeezed it to nothing.
.nav-item-label {
flex: 1;
flex: 1 1 auto;
min-width: 0;
line-height: 1.2;
white-space: nowrap;
@@ -138,8 +140,16 @@
background: var(--app-selection-color);
}
// The version yields first when the panel narrows: a nightly build string
// (0.25.0-nightly.YYYYMMDD.N) is wider than the default panel. The large
// shrink factor makes it give way before the label starts to; the full string
// stays in the tooltip.
.nav-item-version {
flex-shrink: 0;
flex: 0 1000 auto;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 11px;
color: var(--app-muted-color, var(--mat-sys-on-surface-variant));
@@ -163,6 +163,12 @@ describe('WorkspaceSettingsContextPanelComponent', () => {
link('about').querySelector('[data-test-id="settings-nav-version"]')
?.textContent
).toBe('0.25.0');
// A long nightly string is truncated; the tooltip keeps all of it.
expect(
link('about')
.querySelector('[data-test-id="settings-nav-version"]')
?.getAttribute('title')
).toBe('0.25.0');
expect(link('general').querySelector('.nav-item-version')).toBeNull();
ctx.setUpdateAvailable(true);
@@ -133,6 +133,7 @@ interface SettingsNavGroupView {
<span
class="nav-item-version"
data-test-id="settings-nav-version"
[attr.title]="version"
>{{ version }}</span
>
}
@@ -1,5 +1,6 @@
:host {
display: block;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
}
@@ -16,7 +17,10 @@
display: flex;
flex-direction: column;
gap: 6px;
height: 100%;
// Fills the host as a flex item rather than with `height: 100%`: without
// a border-box reset that height excludes the padding, so the panel ran
// its bottom padding past the window edge and items sat on it.
flex: 1 1 auto;
padding: 12px;
width: 300px;
min-width: 0;