From a26e84b761c4ca255ed65be3005fc4c8c4a915d6 Mon Sep 17 00:00:00 2001 From: Valentin Millet Date: Fri, 18 Sep 2026 12:58:48 +0200 Subject: [PATCH] Draw the badge whole, with one ring around the selected one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both halves came from the hairline #196 gave the badge, and neither had the cause I wrote in the ticket. The crop was not the extra two pixels. The band was 17px holding a 19px child, which is not something a flex container sized on its content does: .lang-tag is a span with no display, so it is inline, and an inline box's padding and border hang outside the line box its host is sized to. The band is overflow hidden, so they were cut. It already overflowed by about a pixel and a half before the hairline existed. The ring is the chip drawing a box of its own around a badge that now has one, two pixels further out and at a different radius. The chip keeps its padding — that is what it is clickable by — and the badge's own border takes the amber. The hairline could not simply go: --bg-3 sits at 1.1:1 against every light surface, and without it the badge is bare text again. Co-Authored-By: Claude Opus 5 --- e2e/pageobjects/canvas.page.ts | 43 +++++++++++++++++++ e2e/specs/06-search-and-filters.e2e.ts | 19 ++++++++ .../language-rail.component.scss | 8 +++- .../language-badge.component.scss | 5 +++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/e2e/pageobjects/canvas.page.ts b/e2e/pageobjects/canvas.page.ts index 37904149..596ceb46 100644 --- a/e2e/pageobjects/canvas.page.ts +++ b/e2e/pageobjects/canvas.page.ts @@ -137,6 +137,49 @@ export const canvas = { ); }, + /** + * What the language badge is drawn outside its band, and how many boxes the selected chip + * in the rail draws around it. + * + * ⚠️ Both come from the badge having a border of its own. The band is `overflow: hidden`, + * so a badge taller than its line box is cut; and a chip that draws its own outline around + * a badge that already has one reads as two rings rather than as a selection. + */ + badgeBoxes(): Promise<{ cutByBand: number; outlines: number } | null> { + return browser.execute(() => { + const band = document.querySelector('.card-head'); + const onCard = band?.querySelector('.lang-tag'); + const chip = document.querySelector('.language-chip.on'); + const inChip = chip?.querySelector('.lang-tag'); + if (!band || !onCard || !chip || !inChip) return null; + + // A border nobody can see is not an outline, whatever its width says. ⚠️ `transparent` + // computes to `rgba(…, 0)`, so the alpha is what decides — read by splitting rather + // than by matching, which is one escaping mistake fewer in a string sent to the page. + const drawn = (element: Element) => { + const style = getComputedStyle(element); + const channels = style.borderTopColor + .slice(style.borderTopColor.indexOf('(') + 1, style.borderTopColor.lastIndexOf(')')) + .split(','); + const alpha = channels.length > 3 ? Number(channels[3]) : 1; + + return alpha > 0 && Number.parseFloat(style.borderTopWidth) > 0 ? 1 : 0; + }; + + const bandBox = band.getBoundingClientRect(); + const badgeBox = onCard.getBoundingClientRect(); + + return { + cutByBand: Math.max( + 0, + Math.round(bandBox.top - badgeBox.top), + Math.round(badgeBox.bottom - bandBox.bottom), + ), + outlines: drawn(chip) + drawn(inChip), + }; + }); + }, + async waitForCard(title: string): Promise { await browser.waitUntil(async () => (await canvas.titles()).includes(title), { timeout: 15_000, diff --git a/e2e/specs/06-search-and-filters.e2e.ts b/e2e/specs/06-search-and-filters.e2e.ts index 68d02961..47fbdf60 100644 --- a/e2e/specs/06-search-and-filters.e2e.ts +++ b/e2e/specs/06-search-and-filters.e2e.ts @@ -126,6 +126,25 @@ describe('Search, filters and facets', () => { expect(await $(testid('search-matched')).isExisting()).toBe(false); }); + /** + * ⚠️ Both halves of #201, and both came from the hairline #196 gave the badge. On a card + * the band is `overflow: hidden` and a `` is inline, so the badge's border hung + * outside the line box its host was sized to and was cut. In the rail the chip drew a + * second outline two pixels further out, at a different radius — two rings around one + * badge rather than a selection. + */ + it('draws the badge whole, with one outline around the selected one', async () => { + await canvas.toggleLanguage('sh'); + + const boxes = await canvas.badgeBoxes(); + + expect(boxes).not.toBeNull(); + expect(boxes?.cutByBand).toBe(0); + expect(boxes?.outlines).toBe(1); + + await canvas.toggleLanguage('sh'); + }); + it('drops the search, the tag and the language in one click', async () => { await canvas.search('Docker'); await canvas.toggleTag('ops'); diff --git a/src/app/notes/header/language-rail/language-rail.component.scss b/src/app/notes/header/language-rail/language-rail.component.scss index 3e4a9ced..dd1be54e 100644 --- a/src/app/notes/header/language-rail/language-rail.component.scss +++ b/src/app/notes/header/language-rail/language-rail.component.scss @@ -31,11 +31,15 @@ flex-shrink: 0; } -.language-chip:hover { +// ⚠️ The chip's own box stays invisible: the badge carries a hairline of its own since +// #196, and a second outline two pixels further out — at a different radius — read as two +// rings around one badge rather than as a selection. The padding stays, because it is what +// the chip is clickable by. +.language-chip:hover .lang-tag { border-color: var(--line); } -.language-chip.on { +.language-chip.on .lang-tag { border-color: var(--amber); background: var(--bg-2); } diff --git a/src/app/notes/ui/language-badge/language-badge.component.scss b/src/app/notes/ui/language-badge/language-badge.component.scss index 30c52bc2..82d71610 100644 --- a/src/app/notes/ui/language-badge/language-badge.component.scss +++ b/src/app/notes/ui/language-badge/language-badge.component.scss @@ -1,6 +1,11 @@ @use 'mixins' as *; +// ⚠️ A box of its own, not an inline run. A `` is inline by default, so its padding +// and its border hang outside the line box its host is sized to — and `.card-head` is +// `overflow: hidden`, which cut the badge by three pixels once #196 gave it a hairline. .lang-tag { + display: inline-flex; + align-items: center; font-family: var(--font-mono), serif; font-size: 9.5px; padding: 2px 6px;