From 46f9370018a55beac7de60a309350f7a5c3e9b71 Mon Sep 17 00:00:00 2001 From: rays Date: Thu, 8 Oct 2026 13:51:19 +0000 Subject: [PATCH] Paint the bar around the Dynamic Island in the theme's background (#147) The page named no theme-color, so the status bar iOS draws around the Dynamic Island was its own black or white, a band across the top of every theme. The page and the admin page now carry one, set to the page's background whenever the theme or mode changes; Android's toolbar takes it too. Not seen on a phone yet: the browser test checks the colour matches, not what iOS does with it. A browser test at 360px covers this and #146's row of buttons. The stub DOM gains getComputedStyle, which every browser has. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + tests/dom-stub.js | 1 + tests/ui/app.spec.js | 17 +++++++++++++++++ web/admin.html | 1 + web/index.html | 1 + web/src/theme.ts | 3 +++ 6 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a7d00..6382391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- On an iPhone, the bar around the Dynamic Island is the theme's background, not the system's own black or white. - A feed's buttons stay on one row on a narrow phone, and the reader's buttons stay together instead of breaking across lines under a long feed name. - A moment's DNS failure no longer marks a feed as failing for a day or calls it gone: iPX tries it again in ten minutes. One failed lookup one morning left 205 podcasts unread, uncategorised and labelled gone until the next day. A name that does not resolve is said to be maybe gone, and only once it has been failing a day, on its Directory page as on its own. diff --git a/tests/dom-stub.js b/tests/dom-stub.js index 121dc21..742c286 100644 --- a/tests/dom-stub.js +++ b/tests/dom-stub.js @@ -66,6 +66,7 @@ function makeContext(html, script, { media = false } = {}) { EventSource: function () { this.close = () => {}; }, MediaMetadata: function () {}, Blob: function () {}, + getComputedStyle: () => ({ backgroundColor: '' }), setTimeout, clearTimeout, setInterval, clearInterval, confirm: () => false, prompt: () => null, alert(){}, Date, Math, JSON, Object, Array, String, Number, Promise, Error, FormData: function(){}, diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 7257b47..5d404f9 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1656,3 +1656,20 @@ test('a refresh button turns while what it checks is being checked', async ({ pa await page.evaluate(() => setScanning('multi-show', false)); expect(await spin('#scanAll .i')).toBe('none'); }); + +test.describe('on a narrow phone', () => { + test.use({ viewport: { width: 360, height: 760 } }); + + test("a feed's buttons stay on one row, and the bar round the island is the page's colour", async ({ page }) => { + // Seven 44px buttons did not fit 360px, and Unsubscribe went onto a row of its own (#146). + await page.locator('#burger').click(); + await page.locator('.feed', { hasText: 'Test Show' }).first().click(); + const tops = await page.locator('#content .acts .btn').evaluateAll(bs => bs.map(b => b.getBoundingClientRect().top)); + expect(tops.length).toBe(7); + expect(new Set(tops).size).toBe(1); + // iOS paints the status bar round the Dynamic Island in theme-color, its own black or white + // without one (#147). + expect(await page.evaluate(() => document.querySelector('meta[name=theme-color]').content)) + .toBe(await page.evaluate(() => getComputedStyle(document.body).backgroundColor)); + }); +}); diff --git a/web/admin.html b/web/admin.html index 25a3f89..d604c20 100644 --- a/web/admin.html +++ b/web/admin.html @@ -4,6 +4,7 @@ + iPX admin diff --git a/web/index.html b/web/index.html index a34441b..f5322cb 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,7 @@ + iPX diff --git a/web/src/theme.ts b/web/src/theme.ts index d56fd0c..0e488b0 100644 --- a/web/src/theme.ts +++ b/web/src/theme.ts @@ -38,6 +38,9 @@ function setTheme(name = theme.name, mode = theme.mode, save = false){ : theme.mode === 'auto' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme.mode; // The tab's icon in the same variant as the logo on the page. const fav = $('#favicon'); if(fav) fav.href = fav.dataset[root.dataset.mode]; + // The bar iOS draws round the Dynamic Island takes this colour, as Android's toolbar does. + // Without it the bar was the system's own black or white, a band across the top of the page. + const tc = $('meta[name=theme-color]'); if(tc) tc.content = getComputedStyle(document.body).backgroundColor; const sel = $('#stheme'); if(sel) sel.value = theme.name; const ms = $('#smode'); if(ms) ms.value = theme.mode; const mf = $('#smodefield'); if(mf) mf.hidden = !both;