Skip to content

The Fluent UI demo's viewport disables pinch-zoom (WCAG 2.1 1.4.4) #341

Description

@phmatray

Problem / motivation

FormCraft.DemoFluentApp ships a viewport meta that disables pinch-zoom:

<!-- FormCraft.DemoFluentApp/wwwroot/index.html:6 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

The established sibling demo does not:

<!-- FormCraft.DemoBlazorApp/wwwroot/index.html:6 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Both verified on dev. So the newer demo app — added alongside the Fluent UI adapter (#278/#291) —
regressed against the reference demo rather than making a deliberate choice: this is the stock
Blazor WASM template line, carried in unedited.

maximum-scale=1.0, user-scalable=no prevents a low-vision user from pinch-zooming on iOS Safari and
older Android browsers. That is a zoom-suppression failure of WCAG 2.1 SC 1.4.4 Resize Text
(Level AA)
, and it lands in a repository that has repeatedly treated accessibility conformance as a
hard requirement rather than a nicety — #199 (required-field annotation, Level A), #262 (file-upload
required state), #281 (focus after clearing an upload), #285 (ARIA state in the demo). A showcase app
that cannot be zoomed undercuts the thing it is showcasing.

Modern engines increasingly ignore user-scalable=no, which is mitigation, not a fix: Safari on iOS
respects maximum-scale, and the repo cannot control which engine a visitor uses.

Proposed solution

Drop the two scale clauses so the Fluent demo matches the MudBlazor demo:

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

One line, no behavioural risk — the layout is responsive either way; this only restores the user's
ability to zoom.

Then decide whether to guard it. The repo has the idiom for exactly this kind of
silently-regressing invariant (FormCraft.UnitTests/Ci/GitignoreTests,
FormCraft.UnitTests/Ci/ClaudeMdTestCommandsTests): a static assertion that no demo app's viewport
meta contains user-scalable=no or maximum-scale costs almost nothing and catches the next app
scaffolded from the same template. The plan below includes it, since a second demo app already
reproduced the defect once.

Alternatives considered

  • Fix the line, no guard. Smallest change. Rejected as the sole measure: this exact regression
    arrived by copying a template, and a third demo app would copy it again — the failure is invisible
    in review because the line looks like boilerplate.
  • Keep user-scalable=no and rely on modern browsers ignoring it. Rejected: iOS Safari honours
    maximum-scale, so the barrier is real for a substantial audience, and shipping a known
    accessibility defect in a showcase is the wrong signal for this repo.
  • Guard every accessibility attribute of the demo shells. Over-broad for a demo-only concern, and
    hard to keep meaningful; the viewport is the one that silently disables a whole user capability.

Area

Demo applications — FormCraft.DemoFluentApp shell markup; accessibility


Follow-up from #328. Related: #285, #281, #262, #199

🧠 Brainstorm

Problem / context

FormCraft.DemoFluentApp is the showcase for the Fluent UI adapter, added with #278/#291. Its
index.html came from the Blazor WASM template, whose default viewport meta suppresses zoom — a
long-standing template wart that predates the accessibility guidance now in WCAG.

The repo holds a high bar here: several of its recent fixes (#199, #262, #281, #285) exist purely to
correct accessibility defects, and CLAUDE.md records the reasoning for required-field annotation in
detail. Against that standard, shipping a demo that cannot be pinch-zoomed is an outlier — and it is
one nobody would spot in review, because the line reads as scaffolding.

Approaches

A. Fix the line. Delete maximum-scale=1.0, user-scalable=no. Pros: one line, removes the
barrier immediately, brings the app in line with its sibling. Cons: nothing stops the next
template-scaffolded app from reintroducing it.

B. Fix the line and add a Ci/ guard asserting no demo app's viewport disables scaling. Pros:
the repo already uses static Ci/ guards for invariants that regress silently, and this one has
already regressed once by exactly the mechanism a guard catches. Cheap: a string assertion over two
files. Cons: one more test to maintain; arguably heavyweight for a demo.

C. Fix it and note it in CLAUDE.md. Pros: documents intent. Cons: documentation does not
fail a build, and the whole point is that this is invisible to reviewers.

Recommendation

B. A alone is the fix, but the evidence for the guard is unusually direct: there are two demo
apps, one has the defect and one does not, and the difference arrived by copying a template. That is
the precise situation the repo's existing Ci/ guards were written for. C adds a sentence nothing
enforces.

📋 Spec

Goal

No demo application ships a viewport that disables user scaling, and a future one cannot silently
reintroduce it.

Scope

  • FormCraft.DemoFluentApp/wwwroot/index.html — the viewport meta.
  • A Ci/ guard asserting demo viewport metas do not suppress zoom.

Non-goals

Behaviour

flowchart TD
    A["DemoFluentApp index.html<br/>maximum-scale=1.0, user-scalable=no"] --> B["iOS Safari honours maximum-scale"]
    B --> C["low-vision user cannot pinch-zoom<br/>WCAG 2.1 SC 1.4.4 ❌"]
    D["DemoBlazorApp index.html<br/>width=device-width, initial-scale=1.0"] --> E["zoom works ✅"]
    F["Ci guard: no demo viewport may carry<br/>user-scalable=no / maximum-scale"] --> E
Loading

Key files

  • FormCraft.DemoFluentApp/wwwroot/index.html (line 6) — the file changed.
  • FormCraft.DemoBlazorApp/wwwroot/index.html (line 6) — the correct reference; not modified.
  • FormCraft.UnitTests/Ci/GitignoreTests.cs — the guard idiom to copy (repo-root resolution via
    WorkflowSource.RepoRoot).

Validation rules

Edge cases

  • Match case-insensitively and tolerate whitespace variations (user-scalable = no).
  • A future demo app must be picked up automatically — glob the demo wwwroot/index.html files rather
    than hard-coding two paths, or the guard silently ignores app number three.

Assumptions

  • Both demo apps keep a wwwroot/index.html shell (true for Blazor WASM).

🛠️ Implementation plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: restore pinch-zoom in the Fluent demo, and guard it so a scaffolded app cannot remove it again.

Architecture: demo shell markup plus one static Ci/ invariant test. No library code changes.

Tech stack: .NET 10 SDK pinned by global.json (10.0.302), Blazor WASM, xUnit + Shouldly.

Global constraints:

  • Base branch dev; commit as Philippe Matray <phmatray@gmail.com>; conventional commits.
  • ⛔ Do not modify library markup or FormCraft.DemoBlazorApp — it is already correct and serves as the reference.
  • TreatWarningsAsErrors=true is deliberate — do not relax it.
  • dotnet test --filter is inert here (MTP). Use dotnet test <csproj> -c Release -- --filter-class <FQN>, or the built host directly; see .claude/skills/repo-profile.mdBuild & test.

Task 1: Guard the invariant (failing test first)

Files: create FormCraft.UnitTests/Ci/DemoViewportTests.cs; read FormCraft.UnitTests/Ci/GitignoreTests.cs for the repo-root pattern.

Interfaces: xUnit facts over every demo wwwroot/index.html found under the repo root.

  • Step 1: Reuse WorkflowSource.RepoRoot (as GitignoreTests does) and glob every */wwwroot/index.html under the repo root, so a future demo app is covered without editing the test.
  • Step 2: Assert the scan found at least the two known shells — an empty glob must fail loudly rather than pass vacuously (the vacuity hole fixed in ClaudeMdTestCommandsTests under CLAUDE.md still teaches VSTest test commands that Microsoft.Testing.Platform silently ignores #299).
  • Step 3: Write the failing assertion: no viewport meta may contain user-scalable=no or maximum-scale (case-insensitive, whitespace-tolerant), naming the offending file.
  • Step 4: Run it → FAIL, naming FormCraft.DemoFluentApp/wwwroot/index.html. Record the output.
  • Step 5: Commit: test(ci): pin that no demo viewport disables user scaling.

Task 2: Restore pinch-zoom and verify

Files: modify FormCraft.DemoFluentApp/wwwroot/index.html (line 6).

Interfaces: none.

  • Step 1: Replace the viewport with <meta name="viewport" content="width=device-width, initial-scale=1.0" />, matching FormCraft.DemoBlazorApp.
  • Step 2: Re-run Task 1's test → PASS.
  • Step 3: Run ./build.sh Test (CI gate) → all suites green under TreatWarningsAsErrors.
  • Step 4: Commit: fix(demo): allow pinch-zoom in the Fluent UI demo.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions