# ADR-113: Email and Indian Mobile Inputs Validate Format by Default — Platform-Native Pattern + checkValidity, No Validation Library

## Status

Accepted, 2026-07-12.

## Status History

```yaml
status_history:
  - date: 2026-07-12
    status: Accepted
    changed_by: hkl
    reason: Pattern already implemented in activity PWA; establishing as platform default for all PWAs
    changed_via: adr-kit (360lm)
```

## Context

360lm PWAs collect email addresses and Indian mobile phone numbers across many domains:
- **Activity:** Respondent contact info (client survey feedback)
- **Sales:** Customer email, agent mobile contact
- **HR:** Employee personal contact
- **Custodian:** Payee email for transfer notifications
- **Recce:** Agent and supervisor mobile numbers
- **Contacts:** Vendor/client email and phone

**Problem:** Native HTML input types provide inconsistent validation:
- `<input type="tel">` performs NO validation (keyboard hint only); users can enter text strings or international numbers.
- `<input type="email">` accepts TLD-less addresses (`a@b` is valid native; causes SMTP failures downstream).

**Current state:** No standard pattern for format validation across PWAs; some PWAs skip validation entirely and rely on server errors; others use ad-hoc regex or validation libraries.

**Constraint:** Per ADR-013 (single-file PWA, no build pipeline), third-party validation libraries introduce file-size overhead and deployment complexity. A 92KB validator.js or 80KB+ libphonenumber-js bundle is excessive for two simple format checks.

## Decision

Every `<input type="email">` and `<input type="tel">` in any 360lm PWA MUST carry a `pattern` attribute. Format validation is enforced at save/submit time via the native Constraint Validation API (`checkValidity()` method), with a user-facing error toast naming the field. Applies to both new-entry forms and edit forms.

**Canonical patterns (copy verbatim as JS string constants into each single-file PWA):**

```javascript
const PATTERN_TEL   = '(?:0|\\+?91)?[6-9][0-9]{9}';       // TRAI: 10 digits starting 6-9, optional 0/91/+91 prefix
const PATTERN_EMAIL = '[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}';  // native email check + require a TLD (rejects a@b)
```

**Validation helper (inline in each PWA):**

```javascript
const patternAttr = t =>
  t === 'tel'   ? ` pattern="${PATTERN_TEL}"` :
  t === 'email' ? ` pattern="${PATTERN_EMAIL}"` : '';

function fieldFormatError(f, el) {
  if (!el || !el.value.trim()) return null;  // Empty non-required fields pass
  el.value = el.value.trim();
  if (el.checkValidity()) return null;
  if (f.type === 'tel')   return `${f.label}: enter a valid 10-digit mobile (starts 6-9, +91 optional)`;
  if (f.type === 'email') return `${f.label}: enter a valid email (name@domain.tld)`;
  return `${f.label}: invalid format`;
}
```

**Enforcement at submit/save time:**

1. Loop over form fields before fetch.
2. Call `fieldFormatError(field, element)` for each tel/email field.
3. If error, display toast with field name, focus the element, and abort submit.
4. If valid, proceed to POST/PATCH.

**Draft forms:** Deliberately do NOT validate drafts (users may save incomplete drafts); validate only on final submit.

**Key technical fact:** `type="tel"` performs no validation natively (it only changes the mobile keyboard on Android/iOS). The pattern attribute is what enforces the format check.

**Decision Maker:** hkl

## Implementation Notes

- **Files:** Every PWA that has a `<input type="email">` or `<input type="tel">` field.
- **Reference implementation:** `/var/www/360lm/activity/index.html` — search for `/* ─── Field format validation (ADR-113) ──────────────── */` (lines 1133–1146) for the constant definitions; lines 1139–1146 for the `fieldFormatError()` helper; lines 1485–1486 in `submitResponse()` for enforcement in new-entry form; lines 2565–2566 in `saveEditResponse()` for enforcement in edit form.
- **HTML generation:** When building `<input type="email">` or `<input type="tel">` in HTML templates (including dynamically generated forms), append `${patternAttr(f.type)}` to the input tag. See activity index.html line 1196: `${patternAttr(f.type)}`.
- **Copy-paste snippet:**
  ```bash
  grep -n "PATTERN_TEL\|PATTERN_EMAIL\|fieldFormatError\|patternAttr" /var/www/360lm/activity/index.html | head -20
  ```

## Alternatives Considered

- **validator.js (npm package, 92KB minified).** Rejected: exceeds ponytail YAGNI threshold; adds build-step dependency; ADR-013 prohibits external node_modules. Provides 140+ validators; we need 2.
- **Pristine.js (4KB, vanilla JS).** Rejected: still an external dependency; adds complexity when the native Constraint Validation API covers the need in <20 lines. No advantage over inline helpers.
- **Just-validate (5KB, vanilla JS).** Rejected: same reasoning — adds a dependency layer for a trivial problem.
- **libphonenumber-js (80KB+ depending on build configuration).** Rejected: requires bundler for tree-shaking; complex locale handling overkill for a 10-digit-only Indian platform; native pattern attribute is simpler.
- **Server-side validation only.** Rejected: forces users to wait for network round-trip to discover format errors (poor UX); client-side format check is nearly free and catches obvious typos immediately.

## Consequences

**Positive:**
- No external dependency — pattern and helper are inline in each PWA (supports ADR-013, ADR-100 vanilla JS aesthetic).
- Zero file-size overhead — regexes and validation logic are <20 lines per PWA.
- Native Constraint Validation API is part of the HTML5 standard, supported on all modern browsers and Android WebView (100% coverage on 360lm's field-team devices).
- Fast feedback: validation runs in the browser with no network latency.
- Consistent error messages across all PWAs (field name + clear instructions).
- Trimming (`.trim()`) removes accidental leading/trailing whitespace before format check.

**Negative / Trade-offs:**
- **Client-side validation is NOT a security boundary.** Format validation must be duplicated on the server (via DB CHECK constraints, triggers, or RPC input validation) to enforce data quality. This ADR only covers client-side UX; server-side enforcement remains the source of truth (per ADR-083).
- **Pattern is too simple for some edge cases.** TRAI tel pattern accepts valid Indian mobiles but cannot validate:
  - Ported numbers (sometimes violate start-digit rules)
  - Special operator-reserved prefixes (rare in practice)
  - Landlines vs. mobile-only distinction
  - For high-assurance flows, recommend SMS OTP verification at the application layer (not in this ADR).
- **Retrofit of pre-existing PWAs — DONE 2026-07-12 (same day):**
  - `/sales/` — offer client email/phone, invoice client email/phone, sender-profile email. Sender-profile *phone* deliberately exempt: its placeholder documents foreign country codes, outside the Indian pattern's scope.
  - `/sales/customers/` — customer form, contact modal (phone + WhatsApp), lead modal.
  - `/vrs/` — hired-vehicle owner contact.
  - `/rentveh/` — vehicle owner phone.
  - `/recce-client/` — login email regex aligned to the canonical pattern (was TLD-length-1 lenient).
  - `/contacts/` — **removed from scope**: audit row was a false positive; neither the main app nor `/contacts/upload/` has any tel/email input (data arrives via bulk file import).

**Risks and mitigations:**

| Risk | Mitigation |
|---|---|
| Regex pattern is too strict, rejects valid numbers. | TRAI standard is 10 digits starting 6–9; grandfathered landlines are rare in field workflow. Test against 5–10 real field-team phone numbers before rollout. |
| Users get confused by error message. | Message is field-specific: `"Mobile: enter a valid 10-digit mobile (starts 6-9, +91 optional)"` — repeats rule in plain language. |
| Server also needs to validate (duplication). | Server validation uses DB CHECK + RPC input guards (separate ADR scope). This ADR covers client-side only. |

## Related Decisions

- **ADR-013:** Each PWA Is a Single Self-Contained HTML File — No Framework, No Build Pipeline. Motivates the inline-helper approach (no npm dependencies).
- **ADR-076:** Mobile-First Viewport and Meta Tag Standard. Related: Android mobile keyboards (inputmode="tel" + inputmode="email") work with pattern attributes to show context-aware keyboards alongside validation.
- **ADR-083:** Form Validation Strategy — Client-Side Feedback, Server-Side Enforcement. ADR-113 is a specific refinement: format validation is purely client UX; business logic validation (balance checks, status transitions) remains server-enforced.

## References

- **Live implementation:** `/var/www/360lm/activity/index.html` lines 1133–1146 (constants and helpers), 1196 (HTML generation), 1485–1486 (submitResponse enforcement), 2565–2566 (saveEditResponse enforcement).
- **TRAI regulation:** Indian telecom numbering follows 10-digit format (2G digit + 8 subscriber digits) with 6–9 start rule per TRAI allotment.
- **validator.js benchmark:** https://github.com/validatorjs/validator.js — 92KB minified; `isMobilePhone('en-IN')` source validates the same 10-digit + prefix pattern internally.
- **Constraint Validation API:** https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constraint-validation — native `checkValidity()` and `validity` state object (95%+ mobile browser support).
