Skip to content

Conversation

@kevinansfield
Copy link
Member

closes https://linear.app/ghost/issue/BER-3092/

  • using type="email" improves usability by allowing browsers to auto-fill and display an email-focused keyboard on mobile
  • added noValidate={true} to disable browsers built-in validation as we already have custom validation handling

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

FormView now generates an errorMessageId via React.useId and passes it into the Form component. FormProps and the Form signature accept an optional errorMessageId?: string. The form element uses noValidate={true}. The email input type changed from text to email, gained autoComplete="email", and conditionally includes aria-describedby={errorMessageId} and aria-invalid when an error exists. Error paragraphs render with id={errorMessageId} and retain data-testid="error-message". E2E tests were updated to use toHaveText() for three error-message assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Check apps/signup-form/src/components/pages/form-view.tsx for correct React.useId usage and that the generated id is passed to Form.
  • Verify FormProps typing and Form signature accept and forward errorMessageId without breaking existing props.
  • Confirm aria-describedby and aria-invalid are applied only when errors exist and reference the correct id.
  • Validate noValidate={true} aligns with intended validation flow.
  • Ensure changing input type to email and autoComplete="email" does not conflict with validation or tests.
  • Review apps/signup-form/test/e2e/form.test.ts changes to toHaveText() to ensure assertions target the same elements and semantics.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: converting the email input from text type to email type, which is the primary focus of the PR.
Description check ✅ Passed The description is directly related to the changeset, explaining the rationale for using type='email' and the addition of noValidate to disable browser validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch signup-form-email-input

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca876fb and 278ee2f.

📒 Files selected for processing (2)
  • apps/signup-form/src/components/pages/form-view.tsx (4 hunks)
  • apps/signup-form/test/e2e/form.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/signup-form/test/e2e/form.test.ts
  • apps/signup-form/src/components/pages/form-view.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Signup-form tests
  • GitHub Check: Build & Push

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@kevinansfield kevinansfield enabled auto-merge (squash) December 8, 2025 11:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/signup-form/src/components/pages/form-view.tsx (1)

62-68: Email input semantics and validation configuration look correct

Adding noValidate={true} on the form while switching the field to type="email" cleanly achieves the PR goal (better autofill/mobile keyboards) without letting native browser validation interfere with your existing custom validation flow in submitHandler/onSubmit. As an optional follow‑up, consider wiring the input to the error message via something like aria-invalid={!!error} and aria-describedby on the input plus an id on the error <p> so that, with native validation disabled, assistive tech still gets a clear association between the field and its error state.

apps/signup-form/test/e2e/form.test.ts (1)

168-172: Switch to toHaveText strengthens error message assertions

Using await expect(errorMessage).toHaveText(...) for the invalid‑email, network‑error, and 4xx cases is a solid improvement over asserting on innerText(): you get Playwright’s auto‑waiting and clearer diagnostics while keeping the intent of the tests unchanged. If these strings are likely to evolve, you could optionally relax them to toContainText or a regex, but the current exact matches are perfectly fine if the copy is stable. Based on learnings, this aligns well with the recommended Playwright‑style assertions for e2e tests.

Also applies to: 205-208, 228-232

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6606ab7 and fe54b07.

📒 Files selected for processing (2)
  • apps/signup-form/src/components/pages/form-view.tsx (1 hunks)
  • apps/signup-form/test/e2e/form.test.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-11-26T11:05:59.314Z
Learning: For new UI components, prioritize comprehensive Storybook stories; add focused unit tests where they provide real value (e.g., hooks, utils, logic-heavy parts)
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use `getByTestId()` only when semantic locators are unavailable, and suggest adding `data-testid` to Ghost codebase when needed
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Prefer semantic locators (`getByRole`, `getByLabel`, `getByText`) over test IDs and never use CSS selectors, XPath, nth-child, or class names
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Never use CSS/XPath selectors - only use semantic locators or data-testid
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Prefer semantic locators (`getByRole`, `getByLabel`, `getByText`) over test IDs and never use CSS selectors, XPath, nth-child, or class names

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Test names should be lowercase and follow the format 'what is tested - expected outcome'

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Never use CSS/XPath selectors - only use semantic locators or data-testid

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use `getByTestId()` only when semantic locators are unavailable, and suggest adding `data-testid` to Ghost codebase when needed

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use factory pattern for all test data creation instead of hard-coded data or direct database manipulation

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Always follow ADRs in `../adr/` folder (ADR-0001: AAA pattern, ADR-0002: Page Objects)

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Follow the AAA (Arrange, Act, Assert) pattern in test structure

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Manual login should not be used - authentication is automatic via fixture

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Never use hard-coded waits like `waitForTimeout()`

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use Playwright's auto-waiting capabilities and run tests multiple times to ensure stability

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/helpers/pages/**/*.ts : Use `waitFor()` for guards in Page Objects, never use `expect()` in page objects

Applied to files:

  • apps/signup-form/test/e2e/form.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Signup-form tests
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Build & Push

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/signup-form/src/components/pages/form-view.tsx (1)

16-18: Fix inconsistent aria-describedby wiring in minimal layout

In non-minimal mode the id="error-message" on the paragraph at Line 33 correctly matches aria-describedby={error ? 'error-message' : undefined} on the input, but in the minimal branch (Line 17) the error <p> lacks this id. When isMinimal is true and error is set, the input gets aria-describedby="error-message" but there is no element with that id, which is an accessibility issue.

You can align both layouts by adding the same id in the minimal error paragraph:

-                {error && <p className='text-red-500' data-testid="error-message">{error}</p>}
+                {error && <p className='text-red-500' data-testid="error-message" id="error-message">{error}</p>}

Also applies to: 31-35, 62-65

🧹 Nitpick comments (1)
apps/signup-form/src/components/pages/form-view.tsx (1)

59-66: Form/input changes look consistent; consider minor ARIA refinements

  • noValidate={true} on the <form> plus type="email" on the input correctly gives you email-specific UX (keyboard, heuristics) while leaving validation to your custom logic.
  • aria-describedby and aria-invalid wiring is sound once the error id is present in both layouts.

Optionally, you could:

  • Set aria-invalid only when there is an error, e.g. aria-invalid={!!error} (React will emit "true" only when needed).
  • If this form might ever be rendered multiple times on a page, derive a unique errorId via React.useId() and use that instead of a hard-coded "error-message".

Also applies to: 70-71

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe54b07 and 0880949.

📒 Files selected for processing (1)
  • apps/signup-form/src/components/pages/form-view.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-11-26T11:05:59.314Z
Learning: For new UI components, prioritize comprehensive Storybook stories; add focused unit tests where they provide real value (e.g., hooks, utils, logic-heavy parts)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Signup-form tests
  • GitHub Check: Build & Push

@kevinansfield kevinansfield force-pushed the signup-form-email-input branch 2 times, most recently from 0c0034e to ca876fb Compare December 8, 2025 12:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/signup-form/src/components/pages/form-view.tsx (1)

67-68: Well-implemented accessibility and input type change.

The changes correctly implement the PR objectives:

  • type="email" enables browser autofill and presents an email-focused keyboard on mobile devices
  • aria-describedby properly links the input to its error message only when an error exists
  • aria-invalid accurately reflects the error state for screen readers

Optional enhancement: Consider adding autoComplete="email" to further improve the autofill experience:

 <input
     aria-describedby={error && errorMessageId ? errorMessageId : undefined}
     aria-invalid={!!error}
+    autoComplete="email"
     className={`w-full px-2 py-1 focus-visible:outline-none disabled:bg-white xs:p-2`}

Also applies to: 73-73

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c0034e and ca876fb.

📒 Files selected for processing (1)
  • apps/signup-form/src/components/pages/form-view.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/signup-form/src/components/pages/form-view.tsx (1)
apps/signup-form/src/utils/helpers.tsx (1)
  • isMinimal (12-14)
🔇 Additional comments (4)
apps/signup-form/src/components/pages/form-view.tsx (4)

13-14: Excellent use of React.useId() for accessibility!

Generating a stable unique ID for the error message enables proper ARIA linkage between the input field and its error description. This improves screen reader support.


18-19: Proper error message linking in both rendering modes.

Both minimal and standard views correctly propagate errorMessageId to the Form component and apply it as the id for error paragraphs. This establishes the necessary linkage for aria-describedby in the input field. The different rendering approaches (conditional vs. visibility toggle) both work correctly with the accessibility attributes.

Also applies to: 34-35


49-49: Type definitions correctly updated.

The FormProps type and Form component signature properly include errorMessageId to support the accessibility enhancements.

Also applies to: 53-53


65-65: Correctly disables browser validation.

Adding noValidate={true} appropriately disables HTML5 validation since the form implements custom validation handling, as noted in the PR objectives.

closes https://linear.app/ghost/issue/BER-3092/

- using `type="email"` improves usability by allowing browsers to auto-fill and display an email-focused keyboard on mobile
- added `noValidate={true}` to disable browsers built-in validation as we already have custom validation handling
no issue

- improves markup for assistive tech
@kevinansfield kevinansfield force-pushed the signup-form-email-input branch from ca876fb to 278ee2f Compare December 9, 2025 08:04
Copy link
Contributor

@rob-ghost rob-ghost left a comment

Choose a reason for hiding this comment

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

Nice 🙌🏻

@kevinansfield kevinansfield merged commit 4a2e59c into main Dec 9, 2025
36 checks passed
@kevinansfield kevinansfield deleted the signup-form-email-input branch December 9, 2025 09:29
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.

3 participants