From 4b9e1bba1c7d61c74865ff23b3713dfdfd50c8b6 Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 13 Oct 2026 15:11:21 +1300 Subject: [PATCH] fix(mobile): allow zoom, stop sticky hover, calm touch and motion - one viewport meta for every page: zoom is no longer blocked (user-scalable=no also stopped pinch zoom on Android; WCAG 1.4.4) and interactive-widget=resizes-content makes Android resize for the keyboard. iOS already ignored the old tags. - viewport.js does not pin the scroll while the page is pinch-zoomed, or it would fight the user's panning - 38 pure :hover rules moved into @media (hover: hover): iOS emulates hover on a tap and left the tapped toolbar button or row highlighted. Reveal-on-hover rules with touch fallbacks are untouched, and a test fails if a bare :hover rule is added - touch block: no tap flash, selection or callout on controls (long- pressing a row no longer opens the loupe), touch-action: manipulation so a quick second tap does not zoom; fields and the editor keep selection - prefers-reduced-motion block (loading ring keeps turning) - /diag defaults now mirror the app, with ?zoom=0 / ?iw=0 to compare Full mobile and desktop suites pass. --- AGENT_GUIDE.md | 3 + app/routes/resources.js | 3 +- app/templates/diag.js | 16 +- app/templates/pages.js | 11 +- app/templates/settings.js | 3 +- app/templates/shared.js | 7 + .../mobile-toolbar-keyboard.spec.js | 14 +- public/styles.css | 260 ++++++++++++++---- public/viewport.js | 18 +- tests/diagPage.test.js | 16 +- tests/touchCss.test.js | 84 ++++++ tests/viewport.test.js | 11 +- 12 files changed, 365 insertions(+), 81 deletions(-) create mode 100644 tests/touchCss.test.js diff --git a/AGENT_GUIDE.md b/AGENT_GUIDE.md index e566673..519dcc1 100644 --- a/AGENT_GUIDE.md +++ b/AGENT_GUIDE.md @@ -523,6 +523,9 @@ Desktop and mobile do not have separate editor implementations. Both use the sam ### PWA shell Cache shell/static assets only. Do not cache note/resource/API responses in ways that break shared-browser safety. +### Zoom, touch and hover CSS +One `viewportMeta` constant (`app/templates/shared.js`) is used by every page: `width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content`. Zoom is allowed (WCAG 1.4.4; `user-scalable=no` also blocks pinch zoom on Android; iOS Safari ignores it anyway). Double-tap zoom on controls is off through `touch-action: manipulation`, and `public/viewport.js` does not pin the page scroll while the user is pinch-zoomed (`shouldPin`). `interactive-widget=resizes-content` only affects Android Chrome. The end of `styles.css` has the touch block (`@media (hover: none) and (pointer: coarse)`: no text-size inflation, tap flash, selection or callout on controls; selection stays on fields and `.cm-content`) and a `prefers-reduced-motion` block (the loading ring keeps turning). Pure `:hover` feedback rules must live inside `@media (hover: hover)`: iOS emulates hover on tap and leaves it stuck (a tapped toolbar button stayed highlighted). Reveal-on-hover affordances that mix `:hover` with another state have `(hover: none)` fallbacks and are exempt. `tests/touchCss.test.js` lints this. `/diag` can reproduce the old meta (`?zoom=0`, `?iw=0`) for comparison. + ### Service worker policy (`public/service-worker.js`) Deliberately small. Navigations go to the network; if that fails the worker serves the static `/offline.html` (inline styles, a Try again button that checks `/health` and reloads, auto-retry on the `online` event). Versioned `.js`/`.css` URLs (`?v=`, immutable by construction) are cache-first in a cache named `joplock-shell-`; any other Joplock cache is deleted on activate, other apps' caches are left alone. Nothing else is intercepted: **no HTML page, API response, fragment, note or attachment is ever cached** (shared-browser safety; the app has no offline notes). The install step precaches only `/offline.html` and an icon. It used to precache bare URLs that the `?v=` requests never matched (so assets were stored twice) and never cached `app.js`/TinyMCE. Covered by `tests/serviceWorker.test.js` (the real file in a sandbox) and `playwright-tests/pwa-offline.spec.js` (real browser: cached assets, offline launch, recovery). Not done: an in-app "new version, reload" prompt; the new worker takes over immediately (`skipWaiting` + `claim`). diff --git a/app/routes/resources.js b/app/routes/resources.js index 2f6050a..d6d5b7c 100644 --- a/app/routes/resources.js +++ b/app/routes/resources.js @@ -1,5 +1,6 @@ 'use strict'; +const { viewportMeta } = require('../templates/shared'); const { send, sendJson, readRawBody, parseMultipart, contentDispositionFilename, shouldInlineResource } = require('./_helpers'); // Joplin Server rejects files over its formidable maxFileSize (200MB) with an @@ -82,7 +83,7 @@ const handle = async (url, request, response, ctx) => { - + ${title}