diff --git a/docs/user_guide.md b/docs/user_guide.md index e354502..e40a851 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -229,6 +229,11 @@ the dialog. Opening settings again starts at the menu. Phone settings use a screen-width view while open. Closing the dialog restores the dashboard's existing scaling. Desktop settings keep their side menus and expand/collapse sections. +Settings switch to the compact layout when the window is at most 700 CSS pixels +wide. Before phone scaling is enabled, a valid screen width of at most 700 CSS +pixels also selects it when the browser reports a coarse (touch-style) pointer. +A small or unavailable screen-width report alone does not select phone settings +in a wide desktop window, including the Raspberry Pi pywebview desktop. ### General Settings Pane diff --git a/sensorius/__init__.py b/sensorius/__init__.py index 3627405..a15fd26 100644 --- a/sensorius/__init__.py +++ b/sensorius/__init__.py @@ -4,4 +4,4 @@ release notes, and supporting tooling can report a consistent build identity. """ -__version__ = "v0.26.258.2" +__version__ = "v0.26.259.1" diff --git a/tests/desktop-settings.spec.js b/tests/desktop-settings.spec.js new file mode 100644 index 0000000..915dd22 --- /dev/null +++ b/tests/desktop-settings.spec.js @@ -0,0 +1,47 @@ +import { test, expect } from '@playwright/test'; + +// Screen geometry can disagree with the available desktop window geometry. +for (const screenWidth of [1, 600]) { + test(`wide desktop settings ignore a ${screenWidth}px screen report`, async ({ browser, baseURL }) => { + const page = await browser.newPage({ + baseURL, + viewport: { width: 1280, height: 900 }, + screen: { width: screenWidth, height: 900 }, + hasTouch: false, + isMobile: false, + }); + try { + await page.route('https://cdn.jsdelivr.net/**', route => route.fulfill({ contentType: 'application/javascript', body: '' })); + await page.goto('/', { waitUntil: 'domcontentloaded' }); + // Confirm the browser really matches the old, faulty screen-only fallback. + expect(await page.evaluate(() => matchMedia('(max-device-width:700px)').matches)).toBe(true); + await page.getByRole('link', { name: 'Open General Settings' }).click(); + const general = page.locator('#setupPiModal'); + await expect(general).toBeVisible(); + await expect(general).not.toHaveClass(/(^|\s)mobile-settings(\s|$)/); + await expect(general.locator('.mobile-settings-nav')).toBeHidden(); + await expect(general.locator('.system-settings-menu')).toBeVisible(); + await expect(general.locator('.system-settings-content')).toBeVisible(); + const display = general.locator('[data-runtime-section="system-display"]'); + await display.locator(':scope > summary').click(); + await expect(display.locator('#metric_set')).toBeVisible(); + await general.getByRole('button', { name: 'Close General Settings' }).click(); + for (const kind of ['Sensor', 'Switch']) { + await page.evaluate(kind => kind === 'Sensor' + ? window.editSensorSettings('aht-pr-check') + : window.editSwitchSettings('switch-pr-check'), kind); + const root = page.locator(`#${kind.toLowerCase()}SettingsModal`); + await expect(root).toBeVisible(); + await expect(root).not.toHaveClass(/(^|\s)mobile-settings(\s|$)/); + await expect(root.locator('.mobile-settings-nav')).toBeHidden(); + await expect(root.locator('.mobile-settings-menu')).toBeVisible(); + await expect(root.locator('.mobile-settings-content')).toBeVisible(); + await expect(root.locator('#location')).toBeVisible(); + await page.goto('/', { waitUntil: 'domcontentloaded' }); + } + await expect(page.locator('meta[name="viewport"]')).toHaveCount(0); + } finally { + await page.close(); + } + }); +} diff --git a/ui_static/css/mobile_settings.css b/ui_static/css/mobile_settings.css index b86b0b8..f764743 100644 --- a/ui_static/css/mobile_settings.css +++ b/ui_static/css/mobile_settings.css @@ -1,7 +1,8 @@ /* Drill-down settings apply only to phone-sized viewports. */ .mobile-settings-nav, .mobile-settings-chevron { display:none; } .mobile-settings-chevron::after { content:'›'; } -@media (max-width:700px), (max-device-width:700px) { +/* Keep the screen fallback aligned with phoneQuery in mobile_settings.js. */ +@media (max-width:700px), (min-device-width:1px) and (max-device-width:700px) and (pointer:coarse) { .modal-backdrop:has(.mobile-settings), #setupPiModal.mobile-settings { padding:.35rem; box-sizing:border-box; diff --git a/ui_static/js/mobile_settings.js b/ui_static/js/mobile_settings.js index 81008db..857680e 100644 --- a/ui_static/js/mobile_settings.js +++ b/ui_static/js/mobile_settings.js @@ -3,7 +3,10 @@ (() => { if (window.initMobileSettings) return; -const phoneQuery = "(max-width: 700px), (max-device-width: 700px)"; +// Phones need the screen fallback before their settings viewport is installed. +// A missing/small screen report alone must not classify desktop WebKit as mobile. +// Keep this query aligned with mobile_settings.css. +const phoneQuery = "(max-width: 700px), (min-device-width: 1px) and (max-device-width: 700px) and (pointer: coarse)"; const viewportOwners = new Set(); let settingsViewport = null; window.setMobileWorkspaceViewport = function setMobileWorkspaceViewport(root, open, query = phoneQuery) {