text inputs trigger iOS Safari focus-zoom on every app with a web frontend #13
Owner
> **Originally filed:** 2026-05-10 in ~/bugs.md, block #9.
> **Cross-project companion issues:** brendan/authd, brendan/buchinese, brendan/inventory, brendan/movement
<!-- Resolved 2026-05-11 — PR https://gitea.bchen.dev/brendan/nanodrop/pulls/6 (merge_commit 4df874b695b98d0abae77cb4c5470feb17426011) — final bullet (nanodrop) closed; all 5 projects (authd PR #11, buchinese PR #5, inventory PR #18, movement PR #15, nanodrop PR #6) now satisfy the standing 16px rule. Standing rule remains in `~/.claude/CLAUDE.md` for new code.
**2026-05-10 — cross-project: text inputs trigger iOS Safari focus-zoom on every app with a web frontend; ensure `<input>` / `<textarea>` / `<select>` render at ≥16px so iOS doesn't auto-zoom on tap**
> **Standing rule (added 2026-05-10):** the user has elevated this from a one-off chore to a permanent rule for all projects going forward. The rule is now codified in `~/.claude/CLAUDE.md` under the "Web form input font-size" section (loads globally for every project session). This bug item is the retrofit pass for the 5 already-affected projects audited below. Going forward, any new CSS work in any project must satisfy the rule from the start.
User directive (spawn-host, 2026-05-10): "for all apps, ensure that text fields don't cause a zoom in on iOS when clicked" + "make this a general rule for all projects going forward." This is the well-known iOS Safari behavior where focusing an input whose computed `font-size` is < 16px triggers an automatic viewport zoom-in (intended as a readability aid, but jarring on mobile-first sites). The fix is to ensure form controls compute to 16px on the iOS code path. Apple's behavior is documented and stable across iOS versions; the 16px threshold is the industry-standard escape hatch.
**Affected projects (audited 2026-05-10):**
- ~~`~/authd/public/style.css:101-116`~~ — **DONE 2026-05-10** in PR https://gitea.bchen.dev/brendan/authd/pulls/11 (merge_commit `ca06bc520b081928aca959a91e7eef79c6799bb7`). Approach A applied: added `input, textarea, select { font-size: 16px; }` and bumped textarea mono override 12px → 16px. +2 regression tests in `tests/integration/style.test.ts`; 201/201 green; tsc clean; refactor noop. Needs manual iOS verification by user post-deploy.
- ~~`~/buchinese/public/style.css:55-57`~~ — **DONE 2026-05-10** in PR https://gitea.bchen.dev/brendan/buchinese/pulls/5 (merge_commit `637f143f44d00f477c269b2b880de9fad1bd6715`). Approach A applied: universal rule's `font-size: inherit` → `font-size: 16px`; removed redundant `.login-form input { font-size: 16px }` (login.html post-authd has no `<input>`). Form-control inventory at exec time: 8 affected controls across `articles-new.html`, `articles-list.html`, `flashcards-list.html` (includes one `<select>` — `#sort-select`). +1 CSS-shape regression test in `tests/unit/style-css.test.ts` asserting the universal rule body has `font-size >= 16px`; 131/131 vitest green; refactor noop. Needs manual iOS verification by user post-deploy.
- ~~`~/inventory/public/style.css:211-213`~~ — **DONE 2026-05-10** in PR https://gitea.bchen.dev/brendan/inventory/pulls/18 (merge_commit `74173180891624db44c0eb05ea15bb4fed9fd3cc`). Approach A applied: bumped the `input, textarea` rule from 13px → 16px (no `<select>` exists in inventory's UI; checked). +1 CSS-shape regression test in `tests/integration/style.test.ts` asserting the rule body has `font-size >= 16px`; refactor pass tightened it to match the file's existing `expect(css).toMatch(/regex/s)` style. 196/196 vitest + tsc clean. Needs manual iOS verification by user post-deploy.
- ~~`~/movement/public/chrome.css:145-150`~~ — **DONE 2026-05-10** in PR https://gitea.bchen.dev/brendan/movement/pulls/15 (merge_commit `21b5eaedb8159898ac9a013b252e1f242e07e5f2`). Approach A2 (minimum-diff) applied: bumped `input[type="text"], input[type="password"]` block's `font-size: 13px` → `16px`. No `<textarea>`/`<select>` in movement, so the universal selector would add no extra coverage. +1 CSS-shape regression test in `tests/integration/routes/chrome.test.ts` asserting the served `/public/chrome.css` matches `font-size: 1[6-9]px` inside the input block. 192/192 vitest + tsc clean; refactor noop. Needs manual iOS verification by user post-deploy.
- ~~`~/nanodrop/public/style.css:147-153`~~ — **DONE 2026-05-11** in PR https://gitea.bchen.dev/brendan/nanodrop/pulls/6 (merge_commit `4df874b695b98d0abae77cb4c5470feb17426011`). Approach A2 (minimum-diff) applied: bumped the `input[type="text"], input[type="password"], input[type="file"]` block's `font-size: 13px` → `16px` (one line). `file` is exempt from iOS focus-zoom (no soft keyboard) but harmlessly bumped along with the others; splitting was rejected as added noise without value. `.share-box input[readonly]` inherits the bump and is incidentally fine (iOS does not focus-zoom readonly inputs anyway). +1 CSS-shape regression test in `tests/integration/style.test.ts` asserting the rule body has font-size ≥16px. 126/126 vitest + tsc clean; refactor noop. Needs manual iOS verification by user post-deploy.
**Excluded from this chore (verified — no affected text inputs):**
- `~/dashcam` — only has `<input type="file">` (visible upload buttons + hidden folder picker, ` ~/dashcam/src/views/upload.ts:20,24`); file inputs don't trigger iOS focus-zoom because they don't summon a soft keyboard. No other text inputs in the codebase.
- `~/portman`, `~/tradebot` — no web frontend at all (no `public/`, no client-side HTML).
**Recommended fix (uniform across affected projects):**
Add a single rule that ensures form controls render at exactly 16px on touch viewports. The simplest, most reliable form is **universal 16px on form controls** (Approach A):
```css
input, textarea, select { font-size: 16px; }
```
Place near the existing `input, textarea` rule in each project's main stylesheet. This adds 2-4px to inputs on desktop (14px → 16px or 13px → 16px), which is a barely-noticeable visual increase and matches Apple's HIG default. The `.login-form input { font-size: 16px }` rule that already exists in buchinese is exactly this pattern — generalize it.
**Alternative (Approach B — viewport-scoped):** wrap in a media query so desktop keeps its tighter type:
```css
@media (max-width: 768px) {
input, textarea, select { font-size: 16px; }
}
```
Use this if the bug-fixer thinks the desktop visual rhythm of any specific project would suffer at 16px. The downside is one extra rule to remember, and that hybrid sizes (the iPad in landscape, foldables, etc.) sometimes fall awkwardly between breakpoints. **Default to Approach A** unless a specific project's design clearly prefers tighter inputs on desktop — in which case use Approach B for that project only and note it in the PR description.
**For inputs explicitly typed as `text`/`password`/`email`/`tel`/`url`/`search`/`number`** (i.e. anywhere the codebase uses attribute selectors like `input[type="text"]`), apply the 16px rule to the same selector list (or simplify to bare `input` if the project doesn't need to exempt `file`/`checkbox`/`radio`/`range`).
**Per-project specifics for the bug-fixer:**
- ~~**authd**~~ — **DONE 2026-05-10** (PR #11, merge_commit `ca06bc520b081928aca959a91e7eef79c6799bb7`). Approach A applied across the board (universal 16px including the admin-page textarea mono override). 1 of 5 projects done; buchinese, inventory, movement, nanodrop remain.
- ~~**buchinese**~~ — **DONE 2026-05-10** (PR #5, merge_commit `637f143f44d00f477c269b2b880de9fad1bd6715`). Approach A applied: universal `input, textarea, select { font-size: 16px }`; redundant `.login-form input { font-size: 16px }` removed (login.html has no `<input>` after the authd migration). +1 CSS-shape regression test (`tests/unit/style-css.test.ts`); 131/131 vitest green; refactor noop. Lands first → the separate `#import-form` layout bug (below) can drop its `#import-form input, textarea { font-size: 16px }` line per the coordination note. 3 of 5 projects done; movement+nanodrop remain.
- ~~**inventory**~~ — **DONE 2026-05-10** (PR #18, merge_commit `74173180891624db44c0eb05ea15bb4fed9fd3cc`). Approach A applied (universal 16px on `input, textarea`; no `<select>` in inventory's UI). 2 of 5 projects done; buchinese, movement, nanodrop remain.
- **movement** (`~/movement/public/chrome.css`): change line 150's `font-size: 13px` (inside `input[type="text"], input[type="password"] {…}`) to `font-size: 16px`. Smallest fix — only one rule, only the login page is affected.
- ~~**nanodrop**~~ — **DONE 2026-05-11** (PR #6, merge_commit `4df874b695b98d0abae77cb4c5470feb17426011`). Approach A2 (minimum-diff) applied: bumped existing `input[type="text"], input[type="password"], input[type="file"]` block 13px → 16px (file input incidentally bumped, harmless; readonly share-box input inherits, incidentally fine). +1 CSS-shape regression test (`tests/integration/style.test.ts`); 126/126 vitest + tsc clean; refactor noop. **5 of 5 projects done — cross-project iOS focus-zoom retrofit complete.**
**Approach (recommended PR sequencing):**
1. **One PR per project**, five PRs total. Each PR diff is a single CSS rule change (or two, where applicable), with no other unrelated changes. Independent so any can be reverted in isolation if a particular app's design needs adjustment.
2. Branch prefix: `chore/ios-input-no-zoom` (per global rule for cross-project consistency refactors).
3. Verification per PR — bug-fixer **cannot test on a real iPhone** in the sandbox. Acceptance is computed-style verification only:
- Open each affected page in a browser, inspect a visible text input, confirm `getComputedStyle(input).fontSize === '16px'`. The bug-fixer can do this via Playwright / `chromium-headless` or in any local browser DevTools console.
- User verifies on a real iPhone post-merge. Note "needs manual iOS verification" in each PR description.
4. **Do not test by triggering a deploy.** Local computed-style checks are sufficient. Each project has its own deploy cadence; landing the PR triggers the auto-deploy via the standard workflow (per the related deploy-alignment chore further down the queue).
**Acceptance:**
- For each of the 5 affected projects, `getComputedStyle(input).fontSize === '16px'` (or ≥16px) on at least one visible text/password/email input on a representative page (login, plus one form page if available).
- For projects where the textarea visual style is load-bearing (e.g. authd's mono-12px textarea), Approach B (media query) is acceptable — call it out explicitly in the PR description.
- All five PRs land on `main` via the standard bug-fixer pipeline (sync-from-main, mergeable check, no SHA pin per `~/CLAUDE.md`).
- No regressions in existing tests for any project (this is CSS-only — vitest/playwright suites should be unaffected, but run them per project's `CLAUDE.md` to confirm).
- User performs manual iOS verification on each app's primary form post-merge; if any app still zooms on input focus, file a follow-up.
**Out-of-scope notes for the bug-fixer:**
- Do not redesign forms, change copy, change layout, or touch markup — CSS-only fix per project (with the one exception that buchinese can drop the now-redundant `.login-form input { font-size: 16px }` rule since the new generic rule covers it).
- Do not "audit and fix" any other mobile/responsive issues found during this work — file those separately as new bug items. The user has been explicit before that bug fixes shouldn't grow into broader refactors.
- Do not add `<meta name="viewport">` adjustments (e.g. `user-scalable=no`, `maximum-scale=1`). That's the *wrong* fix — it harms accessibility (users who genuinely need to zoom in for vision can't) and is rejected by accessibility guidelines. The 16px font-size fix is the accessibility-friendly one and is what the user asked for.
- Skip dashcam, portman, tradebot per the audit above. If a future dashcam page adds a text input, file a single-project follow-up at that time.
Source: user directive via spawn-host (2026-05-10). Audit confirmed by reading each project's primary stylesheet and grepping the source for `<input>`/`<textarea>`/`<select>` usage (only dashcam came back as exclusively `type="file"`, and portman/tradebot have no web frontend at all). The 16px iOS-zoom threshold is well-documented Apple Safari behavior; the fix is mechanical.
-->
persistent "stay signed in" session cookies across every bchen.dev app (both #18
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?