diff --git a/CHANGELOG.md b/CHANGELOG.md index e35de89..aa16fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,15 @@ for 1.0.0 have something to be assembled from. ## Unreleased +## 1.8.5 + +- **No more grey slivers at the ends of the tab bars.** Tab bars fade at an edge + to show there are more tabs to scroll to. The fade was only partly hidden + when there was nothing to scroll, so a shadow always showed at both ends. It + was invisible on the dark theme and a grey sliver on Shire, on Administration + → Prompts, Settings and every other tabbed page. The fade now appears only + on the side where tabs are actually hidden. + ## 1.8.4 Two things that kept showing up after they should have gone away. diff --git a/src/lembas/__init__.py b/src/lembas/__init__.py index 8189519..85444f0 100644 --- a/src/lembas/__init__.py +++ b/src/lembas/__init__.py @@ -1,3 +1,3 @@ """LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints.""" -__version__ = "1.8.4" +__version__ = "1.8.5" diff --git a/src/lembas/web/static/css/admin.css b/src/lembas/web/static/css/admin.css index b1f6715..b548bf0 100644 --- a/src/lembas/web/static/css/admin.css +++ b/src/lembas/web/static/css/admin.css @@ -167,16 +167,24 @@ a.tabs__tab { text-decoration: none; } pinned to the scrollport with `background-attachment: local`, which is the old trick and works everywhere -- the `local` layers scroll with the content and cover the `scroll` ones exactly when there is nothing more to see. + + ⚠ "Cover" has to mean all of it. The covers used to be as wide as the + shadows and solid for only 40% of that width, so the other 60% of every + shadow always showed through, with nothing to scroll to. On Moria that is + near-black on near-black and nobody saw it. On Shire it was a grey sliver at + both ends of every tab bar. Each cover is now twice the shadow's width and + solid across the first half, which is the whole shadow. It fades only past + the shadow's end, so once content is scrolled the shadow shows as before. */ .tabs__bar { background-image: - linear-gradient(to right, var(--bg) 40%, transparent), - linear-gradient(to left, var(--bg) 40%, transparent), + linear-gradient(to right, var(--bg) 50%, transparent), + linear-gradient(to left, var(--bg) 50%, transparent), linear-gradient(to right, var(--scrim), transparent 1.5rem), linear-gradient(to left, var(--scrim), transparent 1.5rem); background-position: left center, right center, left center, right center; background-repeat: no-repeat; - background-size: 1.5rem 100%; + background-size: 3rem 100%, 3rem 100%, 1.5rem 100%, 1.5rem 100%; background-attachment: local, local, scroll, scroll; /* A tab is a destination, so a flick should land on one rather than between two. */ diff --git a/tests/test_ui_js.py b/tests/test_ui_js.py index a1ae14f..e67f160 100644 --- a/tests/test_ui_js.py +++ b/tests/test_ui_js.py @@ -201,6 +201,24 @@ def test_a_tab_switch_moves_only_the_container_that_scrolls(): assert "outer.scrollTop" in handler +def test_the_tab_bar_edge_fade_is_covered_when_nothing_overflows(): + """The covers were as wide as the shadows and solid for only 40% of that, + so 60% of each shadow showed through with nothing to scroll to. On Shire + that was a grey sliver at both ends of every tab bar. A cover has to be + solid across the whole shadow. Measured in headless Chromium on + /admin/prompts and /settings in both themes before and after. + """ + admin = (ROOT / "web/static/css/admin.css").read_text(encoding="utf-8") + start = admin.index("background-attachment: local, local, scroll, scroll") + block = admin[admin.rindex(".tabs__bar {", 0, start) : start] + cover = re.search(r"linear-gradient\(to right, var\(--bg\) (\d+)%, transparent\)", block) + shadow = re.search(r"var\(--scrim\), transparent ([\d.]+)rem", block) + sizes = re.search(r"background-size: ([\d.]+)rem 100%, [\d.]+rem 100%, ([\d.]+)rem 100%", block) + assert cover and shadow and sizes, "the edge-fade rule changed shape; re-check it by eye" + solid = float(sizes.group(1)) * int(cover.group(1)) / 100 + assert solid >= float(shadow.group(1)) == float(sizes.group(2)) + + def test_the_two_ends_of_the_shell_stay_level(): """The sidebar footer and the composer sit either side of the same vertical edge and are both content-sized, so without a common floor they end at