Skip to content

Finish public form polish: error stability, group a11y, mobile hints (#211) - #230

Merged
spizeck merged 3 commits into
masterfrom
feat/211-form-completion-polish
Oct 7, 2026
Merged

spizeck merged 3 commits into
masterfrom
feat/211-form-completion-polish

Conversation

@spizeck

@spizeck spizeck commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #211 (complements canonical #218, delivered in #226). Auditing both public forms against the #211 acceptance criteria showed the bulk was already in place — correct input types, autocomplete, inputMode, touched/blur validation timing, focus-to-first-invalid, pending-state announcements, idempotent resubmission, preserved values, and email/WhatsApp fallbacks. This PR closes the remaining gaps. No backend contract, validation rule, or analytics changes.

Forms audited

  • /contact inquiry form (mailto + WhatsApp handoff)
  • /donate support-request form (POST /api/support-requests with fallbacks)
  • /partners accommodation search (non-submission — already correct: type="search", autoComplete="off", enterKeyHint="search")
  • Booking widget (third-party Checkfront iframe — out of scope)

Changes

  • On-demand error rendering (support-request-form.tsx): FieldError conditionally mounts only when a validation message exists — untouched fields reserve no error space, keeping the long form compact and deliberate. aria-describedby is present only while its error node exists; correcting a field unmounts the error and drops the association. Error appearance causes only local field-group growth — the intentional tradeoff: a small bounded expansion when a message is actually shown instead of pre-reserved dead space under every field for errors that may never appear. (The short contact form keeps its established reserved-slot convention.)
  • Group error announcement: aria-describedby/aria-invalid sit on each support-type checkbox — failed-submission focus lands on a box, and a fieldset's description isn't announced for its children — so whichever option receives focus announces "Please choose at least one type of support."
  • Keyboard-only checkbox ring: support-type boxes use focus-visible so pointer users don't see a lingering focus halo on an already-checked control.
  • Mobile keyboard hints: enterKeyHint="next" on every single-line input in both forms (name, organization, email, phone, amount, beneficiaries, timing, reference URL, WhatsApp, dates, party size, certification, logged dives). Textareas and checkboxes deliberately keep natural Enter behavior.
  • Stale docstring (lib/support-request.ts): updated to reflect the actual contract — /api/support-requests is primary; the mailto/WhatsApp helpers are the explicit recoverable-failure fallback (keeping the Contact form: route inquiries through a respond.io Custom Channel to fix shared-contact identity collapse #104 shared-sender rationale).

Tests

  • donate.test.tsx: support-type group error is associated with every checkbox; error rendering is behavior-verified — an untouched form mounts no error nodes, a failed validation mounts the message under its control wired via aria-describedby, and correcting the field unmounts the error and drops the association; enterkeyhint coverage incl. negative textarea/checkbox checks.
  • accessibility.spec.ts (E2E): the donate error-state axe scan asserts the on-demand errors are visible before scanning, including the checkbox-group error.
  • contact-form.test.tsx: enterkeyhint on name/email, absent on the message textarea.
  • npm run lint, typecheck, check, build:test — clean. npx vitest run --coverage — 524/524 pass. Playwright accessibility spec — 29 pass, 0 violations.

Test plan

  • Untouched form stays compact — no reserved error space under valid or untouched fields
  • Failed submit: errors appear inline under their controls with only local field-group growth; focus lands on the first invalid field
  • Checkbox group error announced via the focused box's describedby
  • Correcting a field clears its error and drops the association
  • Valid submit → success panel with reference; recoverable failure preserves all entries and offers email/WhatsApp fallback
  • 16px inputs below lg (no iOS zoom), two-column → single-column grid
  • Autocomplete tokens verified: name/email/tel/organization
  • CI green

Generated with Devin

Summary by Sourcery

Polish the public forms’ validation presentation, accessibility announcements, and mobile keyboard navigation without changing submission contracts or validation rules.

Bug Fixes:

  • Render support-request validation errors on demand so the untouched form stays compact; appearing errors cause only local field-group growth.
  • Ensure support-type validation errors are announced when focus lands on an individual checkbox.

Enhancements:

  • Add mobile keyboard navigation hints to single-line fields across the contact and support-request forms.
  • Update support-request helper documentation to reflect the API-first submission flow and fallback handoffs.
  • Show the support-type checkbox focus ring only for keyboard focus.

Tests:

  • Add coverage for checkbox error associations, on-demand error mounting and resolution, and appropriate mobile keyboard hints.

Chores:

  • Add a browser verification script and screenshots for support-request error layout and accessibility behavior.

Summary by CodeRabbit

  • Accessibility
    • Name, email, and other single-line form fields provide a “Next” keyboard hint on mobile keyboards. Message fields and checkboxes do not use this hint.
    • Support-type validation errors are associated with the checkboxes in the group. Field errors appear when a message is available and are removed when the issue is corrected.
    • Checkbox focus styling is shown during keyboard navigation.
  • Bug Fixes
    • Validation errors for required fields and support type are visible after form submission when those fields need attention.

@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Completes the remaining public-form polish by preventing support-form layout shifts, making checkbox-group errors announce correctly on the focused controls, adding next-field mobile keyboard hints while preserving natural textarea and checkbox behavior, and updating stale submission-architecture documentation. Tests cover the accessibility and interaction changes, with the stated lint, typecheck, build, and full Vitest suite passing.

Sequence diagram for support request submission and fallback

sequenceDiagram
    actor Visitor
    participant SupportForm
    participant API as SupportRequestsAPI
    participant Handoff as MailtoOrWhatsApp

    Visitor->>SupportForm: Submit support request
    SupportForm->>API: POST /api/support-requests
    alt Submission succeeds
        API-->>SupportForm: Reference
        SupportForm-->>Visitor: Show success panel
    else Recoverable failure
        API-->>SupportForm: Request failure
        SupportForm-->>Visitor: Preserve entered values
        Visitor->>Handoff: Use mailto or WhatsApp fallback
    end
Loading

Flow diagram for accessible support-form validation

flowchart TD
    Blur[User blurs field or submits form] --> Validate[Validate support form]
    Validate --> Invalid{Invalid input?}
    Invalid -->|No| Continue[Continue submission]
    Invalid -->|Yes| Slots[Keep error slots mounted with reserved height]
    Slots --> Focus[Focus first invalid control]
    Focus --> Checkbox[Focused support checkbox]
    Checkbox --> Announce[Announce sr-types-error via aria-describedby]
Loading

File-Level Changes

Change Details Files
Stabilize inline validation layout by keeping support-form error slots mounted.
  • Render reserved-height error paragraphs even when no message is present.
  • Use invisible placeholder text until the corresponding validation error is active.
  • Add integration coverage for pre- and post-validation slot state.
components/donations/support-request-form.tsx
tests/integration/donate.test.tsx
Correct accessible announcement of support-type group validation errors.
  • Move conditional invalid and described-by state from the fieldset to every support-type checkbox.
  • Ensure focused checkboxes announce the shared group error when submission fails.
  • Test that all checkboxes reference the group error and receive invalid state.
components/donations/support-request-form.tsx
tests/integration/donate.test.tsx
Add mobile keyboard navigation hints to single-line fields across both public forms.
  • Set enterKeyHint="next" on single-line contact and support-request inputs.
  • Leave textareas and checkboxes without the hint to preserve their natural Enter behavior.
  • Add positive and negative attribute assertions in integration tests.
components/contact-form.tsx
components/donations/support-request-form.tsx
tests/integration/contact-form.test.tsx
tests/integration/donate.test.tsx
Align support-request helper documentation with the current primary-submission and fallback architecture.
  • Document POST /api/support-requests as the primary persistence path.
  • Describe mailto and WhatsApp as recoverable-failure fallbacks.
  • Retain the rationale against reintroducing a shared-sender server email endpoint.
lib/support-request.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#211 Ensure public forms use appropriate input metadata and mobile keyboard behavior, including correct autocomplete/input types and next-field hints for single-line inputs. ✅
#211 Make validation and error handling clear, stable, and accessible without premature or disruptive behavior, including persistent error slots and properly announced checkbox-group errors. ✅
#211 Ensure submissions provide immediate acknowledgement, preserve entered values across recoverable failures, expose accessible pending/success states, and avoid backend or business-rule changes. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
seasaba-web Ready Ready Preview Oct 7, 2026 12:29pm UTC

Request Review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-06T13:27:39.470703Z d3b3f33 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a524d45c-430d-415d-903b-a6347ad0c112
📥 Commits

Reviewing files that changed from the base of the PR and between 259e224 and a7d9e10.

⛔ Files ignored due to path filters (9)
  • review-shots/211-errors/desktop-invalid-form.png is excluded by !**/*.png
  • review-shots/211-errors/desktop-invalid.png is excluded by !**/*.png
  • review-shots/211-errors/desktop-partial-form.png is excluded by !**/*.png
  • review-shots/211-errors/desktop-types-error.png is excluded by !**/*.png
  • review-shots/211-errors/desktop-untouched-form.png is excluded by !**/*.png
  • review-shots/211-errors/desktop-untouched.png is excluded by !**/*.png
  • review-shots/211-errors/mobile-invalid.png is excluded by !**/*.png
  • review-shots/211-errors/mobile-types-error.png is excluded by !**/*.png
  • review-shots/211-errors/mobile-untouched.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • components/donations/support-request-form.tsx
  • tests/e2e/accessibility.spec.ts
  • tests/integration/donate.test.tsx
  • verify-211-errors.mjs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The contact and support request forms add keyboard hints to selected single-line inputs. The support request form conditionally renders validation errors and associates support-type errors with each checkbox. Tests and a Playwright script cover form validation states.

Changes

Public form updates

Layer / File(s) Summary
Contact form keyboard hints
components/contact-form.tsx, tests/integration/contact-form.test.tsx
The contextual, name, and email inputs now specify enterKeyHint="next". Tests verify the name and email hints and confirm that the message textarea has no hint.
Support request keyboard hints and validation
components/donations/support-request-form.tsx, tests/integration/donate.test.tsx, tests/e2e/accessibility.spec.ts, verify-211-errors.mjs, lib/support-request.ts
Selected single-line inputs specify enterKeyHint="next". Field errors render after validation and are removed when corrected. Each support-type checkbox receives conditional error attributes, and its focus styling uses focus-visible rings. Tests check error visibility, associations, and keyboard hints. The Playwright script records form metrics and screenshots at desktop and mobile sizes. The module comment describes the persistence and fallback paths.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to a7d9e

No actionable issue is established for the public-form changes; they are mergeable after normal checks.

Architecture Summary

Architecture risk: 🔵 Low · up to b283b

The change affects 3 systems.

Changed systems: tests, components, lib

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — tests (service) was modified; 3 changed files map to changed impact.
  • observed — components (service) was modified; 2 changed files map to changed impact.
  • observed — lib (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in components/contact-form.tsx: The contextual input now sets its virtual-keyboard action hint to next.
  • observed — Modified behavior in components/contact-form.tsx: The name input now sets its virtual-keyboard action hint to next.
  • observed — Modified behavior in components/contact-form.tsx: The email input now sets its virtual-keyboard action hint to next.
  • observed — Modified behavior in lib/support-request.ts: The module comment now identifies POST /api/support-requests as the primary persistence path and describes mailto/WhatsApp as fallback handoffs when that boundary cannot be reached. It adds that the email draft is sent by the visitor and explicitly cautions against restoring a shared-sender server endpoint; the prior comment described client-email handoff as the submission mechanism and said server-side sending had been removed site-wide.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the main form accessibility and keyboard-hint changes, but it states that errors reserve space. The implementation summary and commit message state that errors mount only when a… Update the pull request description to describe mount-on-error behavior, or change the implementation and tests to reserve error space.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The changes address the linked issue's relevant remaining gaps: accessible validation errors, support-type group wiring, and mobile keyboard hints. The stated lack of backend and business-rule changes…
Out of Scope Changes check ✅ Passed The changes remain within the stated scope. They update public form behavior, tests, documentation, and a diagnostic Playwright script without changing backend contracts, validation rules, analytics, …
Title check ✅ Passed The title clearly identifies the primary changes: form error stability, group accessibility, and mobile keyboard hints.
Full details: Description check

Explanation

The description covers the main form accessibility and keyboard-hint changes, but it states that errors reserve space. The implementation summary and commit message state that errors mount only when a message exists.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="components/donations/support-request-form.tsx" line_range="48" />
<code_context>
   return (
-    <p id={id} className="text-xs text-destructive">
-      {message}
+    <p id={id} className={`min-h-4 text-xs text-destructive${message ? "" : " invisible"}`}>
+      {message || " "}
     </p>
</code_context>
<issue_to_address>
**Validation shifts later fields**

When a long validation message wraps onto multiple lines at a narrow viewport, `min-h-4` reserves only one line, so `FieldError` grows when the message appears and pushes later fields down, shifting the layout.

Reserve enough height for wrapped validation messages so the error slot does not grow when they appear.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: components/donations/support-request-form.tsx:48


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread components/donations/support-request-form.tsx Outdated
spizeck and others added 2 commits October 6, 2026 12:31
…211)

The bulk of #211 already landed via #218 (input types, autocomplete,
touched/blur validation, focus-to-first-invalid, pending announcements,
idempotent resubmission, preserved values, fallbacks). This closes the
remaining gaps found auditing both public forms against the acceptance
criteria:

- Support-request form: reserve inline error slots (min-h + invisible)
  like the contact form, so errors appearing on blur/submit no longer
  push every field below them down mid-read
- Support-type checkbox group: the group error was described on the
  fieldset, but focus lands on a checkbox — aria-describedby/aria-invalid
  now sit on each box so the error is actually announced in context
- enterKeyHint="next" on single-line inputs in both forms so mobile
  keyboards offer field-to-field traversal; textareas/checkboxes keep
  their natural Enter behavior
- lib/support-request.ts: fix stale docstring — it still described the
  pre-#189 mailto-only architecture; the boundary POST is primary and
  these helpers are the explicit recoverable-failure fallback

No backend contract, validation rule, or analytics changes.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
focus:ring also paints on mouse click — a pointer user sees a lingering
focus halo on a control that is already visibly checked. focus-visible
keeps the indicator for keyboard users where it is needed.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ompact (#211)

The reserved-slot approach (min-h-4 + invisible text, copied from the
much shorter contact form) padded every untouched field with ~22px of
dead space, stretching the long request form in the owner preview.
FieldError now mounts only when a message exists, restoring the compact
two-column rhythm; a field group grows locally when its error appears,
which is the accepted tradeoff. The touched-gated aria-describedby /
aria-invalid wiring, checkbox-group association, focus-to-first-invalid,
and all validation semantics are unchanged.

Tests: replaced the mounted-slot assertion with behavior coverage —
untouched form mounts no error nodes, the message mounts wired to its
control, and both clear on correction. The donate axe scan now asserts
the on-demand errors are visible instead of checking an invisible class.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@spizeck
spizeck merged commit 3f765ad into master Oct 7, 2026
11 checks passed
@spizeck
spizeck deleted the feat/211-form-completion-polish branch October 7, 2026 15:38

This branch was successfully deployed

1 active deployment
Preview — a7d9e101 Deployed Oct 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Polish public forms for faster, clearer completion

1 participant