Skip to content

Conversation

@ntatoud
Copy link
Member

@ntatoud ntatoud commented Nov 12, 2025

Proposal

This PR introduces a new FormField abstraction built on top of React Hook Form’s Controller, inspired by [TanStack Form].

It replaces the current FormFieldController (type-switch–based) with a render-props, composition-first API that’s easier to extend and customize.

Note

At this stage we’re just aiming to validate the pattern.
a11y helpers, ...other passthroughs, and sugar components will follow.


Why a new abstraction ?

The current FormFieldController works, but it becomes costly as our field catalog grows:

  • Rigid type-switch: each field adds boilerplate across prop types + switch cases.
  • Direct RHF dependence: components are tightly coupled to RHF types and the controller shape.
  • Circular dependency: field components depend on FormFieldController and vice versa, making the module graph brittle.
  • Limited layout flexibility: fixed structure around label/helper/error; deviations require custom code paths.

The new FormField focuses on composition and layering:

  • A thin RHF layer exposes { props, state } and a registry of field components.
  • Consumers compose UI inline (labels, descriptions, errors, custom layouts).
  • New fields register once in a central fieldComponents map. No core edits.

It’s also closer to industry-standard patterns (e.g., shadcn UI Field composables) and aligns with TanStack Form’s ergonomics, which keeps a future switch feasible.


Comparison

Aspect Current (FormFieldController) Proposal (FormField)
API style Config via type + central switch Render props + composition
Boilerplate High: new union entries, props, switch case Low: export field, add to registry
Coupling to RHF Direct: components tightly bound to RHF Layered: thin RHF wrapper, composables on top
Deps graph Circular risk (fields ↔ controller) Flat: registry consumed by FormField
Type safety Discriminated unions narrow by type Generics + typed render props
Scalability Switch grows with catalog Registry scales without touching core
Standards alignment Custom pattern Closer to shadcn/TanStack abstractions

Pros & Cons

✅ Pros (Proposal)

  • Composition-first: inline control over structure, branching, and complex layouts.
  • Less boilerplate: add fields via the registry; no switch/union churn.
  • Layered design: field components aren’t hardwired to RHF internals.
  • Cleaner module graph: avoid circular references => Would make it easier to export components form separate use.
  • Standards-friendly: aligns with shadcn composables and TanStack Form patterns (easier future migration).

⚠️ Cons / Trade-offs

  • Slightly more verbose per callsite (render function).
  • Team conventions required to avoid UI drift (we’ll ship official Label/Description/Error pieces).
  • Migration effort from FormFieldController.

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive form system with reusable, composable field components for building validated forms
    • Added field components for text input, select dropdowns, labels, descriptions, and error handling
    • Integrated form field context and controller for managing field state and metadata
  • Chores

    • Added Radix UI label and separator dependencies

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

Walkthrough

This PR introduces a comprehensive form system built on react-hook-form, including UI field components (Field, FieldLabel, FieldError, FieldDescription), Form and FormController wrappers, form-bound field components (FieldText, FieldSelect), context-based helpers, custom hooks, and new Radix UI dependencies for Label and Separator.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added @radix-ui/react-label (2.1.8) and @radix-ui/react-separator (1.1.8)
Core Form Components
src/components/new-form/form.tsx, src/components/new-form/form-controller/index.tsx
New Form component wrapping react-hook-form's FormProvider with optional HTML form element; new FormController component with render-prop workflow and context provisioning
Form Controller Context
src/components/new-form/form-controller/context.tsx
New FormControllerContext with typed value, context creation, and useFormControllerContext hook for form field components to access field state and metadata
UI Field Components
src/components/ui/field.tsx, src/components/ui/label.tsx
Comprehensive Field component suite (Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle) with orientation variants; Label wrapper around Radix UI's LabelPrimitive
Form-bound Field Components
src/components/new-form/field-text/index.tsx, src/components/new-form/field-select/index.tsx, src/components/new-form/form-field-label.tsx, src/components/new-form/form-field-description.tsx, src/components/new-form/form-field-error.tsx
Context-aware field components that bind Input/Select to form state, wire ARIA attributes, render validation errors, and forward handlers
Hooks & Utilities
src/components/new-form/hooks.ts, src/components/new-form/types.ts, src/components/new-form/_field-components.ts
Custom hooks (useAppForm, useAppFormContext) extending react-hook-form; type definitions (FormFieldSize, FieldProps); field component mappings constant
TypeScript Utilities
src/types/utilities.d.ts
New WithRequired generic type for enforcing required keys on a type
Export Changes
src/components/new-form/index.ts, src/components/ui/select.tsx
New barrel export for Form and FormController; TValueBase now publicly exported from select
Documentation & Stories
src/components/new-form/docs.stories.tsx
Storybook story demonstrating Form usage with zod validation, field binding, and error handling

Sequence Diagram

sequenceDiagram
    actor User
    participant App as Application
    participant Form as Form Component
    participant FormProvider as FormProvider
    participant Controller as FormController
    participant Context as FormControllerContext
    participant Field as FieldText/FieldSelect
    participant RHF as React Hook Form

    User->>App: Initialize form with useAppForm
    App->>RHF: useForm + custom Controller
    RHF-->>App: form methods + Controller
    App->>Form: render Form with onSubmit
    Form->>FormProvider: wrap children
    App->>Controller: render FormController for each field
    Controller->>RHF: render RHF Controller
    RHF->>Context: provide FormControllerContextValue
    Context-->>Controller: context ready
    Controller->>Field: render field with context
    Field->>Context: useFormControllerContext
    Context-->>Field: field state, metadata (labelId, errorId)
    Field->>Field: wire onChange, onBlur to form state
    Field->>Field: render with ARIA attributes
    User->>Field: interact (type/select)
    Field->>RHF: update field value via onChange
    RHF->>Field: trigger re-render with new state
    User->>Form: submit
    Form->>RHF: handleSubmit
    RHF->>RHF: validate
    alt Validation passes
        RHF->>App: invoke onSubmit
    else Validation fails
        RHF->>Field: update fieldState.invalid
        Field->>Field: render FieldError
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • FormController render-prop and context memoization: Verify useMemo correctly tracks dependencies and context propagation is efficient
  • Field component integration patterns: Ensure FieldText and FieldSelect properly integrate with form context, chain handlers correctly, and handle edge cases (disabled, readonly states)
  • Error rendering and display logic: Review FieldError's error message aggregation and conditional rendering across all field variants
  • Generic type constraints: Verify type safety across FormController and Form generics (TFieldValues, TContext, TTransformedValues) and FieldProps usage
  • ARIA attributes and accessibility: Confirm labelId, descriptionId, errorId are correctly generated and applied to form elements

Possibly related PRs

  • PR #574: Introduces form controller/field context with labelId/descriptionId metadata for form field components
  • PR #637: Adds TTransformedValues generic parameter to Form and FormController types for transformed value handling

Suggested labels

enhancement, components

Suggested reviewers

  • DecampsRenan
  • ivan-dalmet

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title '[PROPOSAL] New React Hook Form Abtraction' contains a typo ('Abtraction' instead of 'Abstraction') and uses vague phrasing that does not clearly convey the main change. Correct the typo to 'Abstraction' and consider a more specific title that describes the core change, such as 'Introduce render-props FormController abstraction for React Hook Form' or 'Add composition-first form field API with React Hook Form integration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 feat/rhf-form-abstraction

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c23cfc and 5336a31.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • package.json (1 hunks)
  • src/components/new-form/_field-components.ts (1 hunks)
  • src/components/new-form/docs.stories.tsx (1 hunks)
  • src/components/new-form/field-select/index.tsx (1 hunks)
  • src/components/new-form/field-text/index.tsx (1 hunks)
  • src/components/new-form/form-controller/context.tsx (1 hunks)
  • src/components/new-form/form-controller/index.tsx (1 hunks)
  • src/components/new-form/form-field-description.tsx (1 hunks)
  • src/components/new-form/form-field-error.tsx (1 hunks)
  • src/components/new-form/form-field-label.tsx (1 hunks)
  • src/components/new-form/form.tsx (1 hunks)
  • src/components/new-form/hooks.ts (1 hunks)
  • src/components/new-form/index.ts (1 hunks)
  • src/components/new-form/types.ts (1 hunks)
  • src/components/ui/field.tsx (1 hunks)
  • src/components/ui/label.tsx (1 hunks)
  • src/components/ui/select.tsx (1 hunks)
  • src/types/utilities.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/components/new-form/types.ts
  • src/components/new-form/form-field-error.tsx
  • package.json
  • src/components/ui/select.tsx
  • src/components/new-form/field-select/index.tsx
  • src/components/new-form/form-field-description.tsx
  • src/components/new-form/index.ts
  • src/components/new-form/form.tsx
  • src/components/new-form/hooks.ts
  • src/components/ui/label.tsx
  • src/types/utilities.d.ts
  • src/components/new-form/form-controller/context.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-09-30T11:07:14.833Z
Learnt from: ivan-dalmet
Repo: BearStudio/start-ui-web PR: 532
File: src/features/auth/PageOAuthCallback.tsx:43-45
Timestamp: 2024-09-30T11:07:14.833Z
Learning: When suggesting changes to `useEffect` dependencies in React components, ensure that removing dependencies doesn't cause React Hook errors about missing dependencies.

Applied to files:

  • src/components/ui/field.tsx
  • src/components/new-form/form-controller/index.tsx
📚 Learning: 2024-10-11T14:57:53.600Z
Learnt from: DecampsRenan
Repo: BearStudio/start-ui-web PR: 537
File: src/features/devtools/EnvHintDevPopover.tsx:0-0
Timestamp: 2024-10-11T14:57:53.600Z
Learning: When using `React.cloneElement`, remember that it automatically merges the child's existing `props` with the new props provided, so manually spreading `children.props` is unnecessary.

Applied to files:

  • src/components/new-form/docs.stories.tsx
🧬 Code graph analysis (6)
src/components/new-form/form-field-label.tsx (2)
src/components/ui/field.tsx (1)
  • FieldLabel (246-246)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/new-form/field-text/index.tsx (4)
src/components/new-form/types.ts (1)
  • FieldProps (3-11)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/ui/field.tsx (2)
  • Field (241-241)
  • FieldError (244-244)
src/components/form/field-text/index.tsx (4)
  • e (71-74)
  • div (52-82)
  • TFieldValues (26-85)
  • e (75-78)
src/components/new-form/_field-components.ts (6)
src/components/new-form/form-field-label.tsx (1)
  • FormFieldLabel (4-8)
src/components/new-form/field-text/index.tsx (1)
  • FieldText (6-39)
src/components/new-form/field-select/index.tsx (1)
  • FieldSelect (7-46)
src/components/new-form/form-field-description.tsx (1)
  • FormFieldDescription (4-10)
src/components/new-form/form-field-error.tsx (1)
  • FormFieldError (4-16)
src/components/form/field-text/index.tsx (1)
  • TFieldValues (26-85)
src/components/ui/field.tsx (3)
src/components/ui/label.tsx (1)
  • Label (22-22)
src/components/form/field-checkbox/index.tsx (1)
  • field (50-75)
src/components/form/field-text/index.tsx (1)
  • div (52-82)
src/components/new-form/docs.stories.tsx (10)
src/components/new-form/index.ts (1)
  • Form (4-4)
src/components/new-form/form.tsx (1)
  • Form (25-54)
src/lib/zod/zod-utils.ts (1)
  • zu (12-42)
src/components/new-form/hooks.ts (1)
  • useAppForm (26-37)
src/components/ui/input.tsx (1)
  • Input (116-116)
src/components/ui/field.tsx (1)
  • FieldError (244-244)
src/components/form/docs.stories.tsx (3)
  • form (70-114)
  • form (30-68)
  • z (24-28)
src/components/form/field-text/docs.stories.tsx (3)
  • form (55-82)
  • form (84-112)
  • z (18-21)
src/components/form/field-number/docs.stories.tsx (1)
  • form (138-161)
src/components/form/field-radio-group/docs.stories.tsx (2)
  • form (41-63)
  • form (94-122)
src/components/new-form/form-controller/index.tsx (3)
src/components/new-form/form-controller/context.tsx (2)
  • FormControllerContextValue (10-17)
  • FormControllerContext (19-20)
src/components/new-form/_field-components.ts (2)
  • FieldComponents (18-18)
  • fieldComponents (7-16)
src/components/new-form/index.ts (1)
  • FormController (4-4)
🪛 Biome (2.1.2)
src/components/new-form/form-controller/index.tsx

[error] 58-58: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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). (1)
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (7)
src/components/new-form/field-text/index.tsx (1)

25-32: LGTM: Event handler chaining.

The onChange and onBlur handlers correctly chain both the field handlers (for form state) and any external handlers passed via props. This ensures form integration works while still allowing consumer customization.

src/components/new-form/form-field-label.tsx (1)

4-8: LGTM: Clean context integration.

The component correctly consumes form controller context and wires the label's id and htmlFor attributes for proper accessibility linkage. The implementation is simple and focused.

src/components/new-form/_field-components.ts (1)

7-18: LGTM: Extensible registry design.

The fieldComponents registry with as const assertion provides strong typing and a central place to add new field types. The exported FieldComponents type enables type-safe consumption. The inline comment clearly indicates where to extend the registry.

src/components/new-form/docs.stories.tsx (2)

48-64: Verify htmlFor attribute in manual field rendering.

In the second field example (lines 48-64), field.Label receives htmlFor={field.props.name} (line 52), but the Input uses id={field.props.name} (line 56). While this creates the correct association, it differs from the first field's pattern where components handle IDs automatically.

This is intentional for demonstration purposes, but ensure the documentation clarifies:

  1. When using composed field components (field.Text), IDs are managed automatically
  2. When manually rendering inputs, consumers must wire htmlFor and id themselves

The story correctly demonstrates both patterns. Consider adding a comment in the story explaining the difference between automatic (field.Text) and manual (Input) ID management.


24-32: LGTM: Form setup with validation.

The form initialization correctly uses useAppForm with zod validation via zodResolver. The mode: 'onBlur' configuration provides a good balance between immediate feedback and avoiding excessive validation during typing.

src/components/new-form/form-controller/index.tsx (1)

70-78: LGTM: Clean render-prop composition.

The component correctly provides FormControllerContext and invokes the render callback with a well-structured field object containing props, state, and spread fieldComponents. This enables both the composed pattern (field.Label) and manual pattern (field.props.value) demonstrated in the stories.

src/components/ui/field.tsx (1)

56-78: LGTM: Well-structured variant system.

The fieldVariants using cva provides clean, type-safe orientation options (vertical, horizontal, responsive). The use of container queries (@container) for the responsive variant is modern and appropriate for flexible layouts.


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.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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: 8

🧹 Nitpick comments (3)
src/types/utilities.d.ts (1)

39-42: Consider a more robust WithRequired implementation.

The current implementation intersects with { [_ in TKey]: {} }, which doesn't preserve the original types of the required keys. The empty object type {} represents "any non-nullish value" in TypeScript, not an empty object.

Consider using a standard approach that preserves type information:

-type WithRequired<TTarget, TKey extends keyof TTarget> = TTarget & {
-  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
-  [_ in TKey]: {};
-};
+type WithRequired<TTarget, TKey extends keyof TTarget> = TTarget & 
+  Required<Pick<TTarget, TKey>>;

This ensures the required keys retain their original types from TTarget.

src/components/new-form/form-field/index.tsx (1)

56-66: Consider alternatives to useMemo in the render prop.

While the current pattern works (and is acknowledged with the eslint-disable), calling useMemo inside a render function is unconventional and flagged by static analysis. Consider these alternatives:

  1. Extract the render logic to a separate component that can use hooks at the top level
  2. Remove useMemo entirely since field and fieldState are already stable references from Controller

Example of removing useMemo:

       render={({ field, fieldState }) => {
-        // We are inside a render function so it's fine
-        // eslint-disable-next-line react-hooks/rules-of-hooks
-        const fieldCtx = useMemo(
-          () => ({
-            field,
-            fieldState,
-            size,
-          }),
-          [field, fieldState, size]
-        ) as FormFieldContextValue;
+        const fieldCtx = {
+          field,
+          fieldState,
+          size,
+        } as FormFieldContextValue;

         return (
src/components/ui/field.tsx (1)

127-138: Consider using a distinct data-slot for FieldTitle.

Both FieldTitle (line 130) and FieldLabel (line 115) use data-slot="field-label". While this might be intentional for styling purposes, it could cause confusion when selecting elements with CSS or JavaScript.

If they serve different semantic purposes, consider using data-slot="field-title" for FieldTitle.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b12d92 and 982e2f9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • package.json (1 hunks)
  • src/components/new-form/_field-components.ts (1 hunks)
  • src/components/new-form/docs.stories.tsx (1 hunks)
  • src/components/new-form/field-select/index.tsx (1 hunks)
  • src/components/new-form/field-text/index.tsx (1 hunks)
  • src/components/new-form/form-field-label.tsx (1 hunks)
  • src/components/new-form/form-field/context.tsx (1 hunks)
  • src/components/new-form/form-field/index.tsx (1 hunks)
  • src/components/new-form/form.tsx (1 hunks)
  • src/components/new-form/index.ts (1 hunks)
  • src/components/ui/field.tsx (1 hunks)
  • src/components/ui/input.tsx (1 hunks)
  • src/components/ui/label.tsx (1 hunks)
  • src/components/ui/select.tsx (1 hunks)
  • src/lib/react-hook-form/index.tsx (1 hunks)
  • src/types/utilities.d.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
src/components/new-form/index.ts (2)
src/components/form/docs.stories.tsx (2)
  • form (70-114)
  • form (30-68)
src/components/form/field-text/docs.stories.tsx (3)
  • form (55-82)
  • form (31-53)
  • form (84-112)
src/components/new-form/field-select/index.tsx (3)
src/components/ui/select.tsx (2)
  • TValueBase (23-23)
  • Select (55-212)
src/components/new-form/form-field/context.tsx (1)
  • useFormField (20-26)
src/components/form/field-select/index.tsx (3)
  • TFieldValues (25-95)
  • div (53-92)
  • e (82-85)
src/components/new-form/_field-components.ts (4)
src/components/new-form/form-field-label.tsx (1)
  • FormFieldLabel (4-10)
src/components/new-form/field-text/index.tsx (1)
  • FieldText (5-35)
src/components/new-form/field-select/index.tsx (1)
  • FieldSelect (6-45)
src/components/form/form-field-controller.tsx (1)
  • TFieldValues (72-140)
src/components/new-form/docs.stories.tsx (4)
src/components/new-form/form.tsx (1)
  • Form (25-54)
src/lib/zod/zod-utils.ts (1)
  • zu (12-42)
src/lib/react-hook-form/index.tsx (1)
  • useForm (26-37)
src/components/form/docs.stories.tsx (3)
  • form (70-114)
  • form (30-68)
  • z (24-28)
src/components/new-form/field-text/index.tsx (2)
src/components/new-form/form-field/context.tsx (1)
  • useFormField (20-26)
src/components/form/field-text/index.tsx (4)
  • e (71-74)
  • div (52-82)
  • TFieldValues (26-85)
  • e (75-78)
src/components/new-form/form-field-label.tsx (2)
src/components/ui/field.tsx (1)
  • FieldLabel (242-242)
src/components/new-form/form-field/context.tsx (1)
  • useFormField (20-26)
src/components/new-form/form-field/index.tsx (3)
src/components/new-form/form-field/context.tsx (3)
  • FormFieldSize (8-8)
  • FormFieldContextValue (10-14)
  • FormFieldContext (16-18)
src/components/new-form/_field-components.ts (2)
  • FieldComponents (14-14)
  • fieldComponents (5-12)
src/components/ui/field.tsx (1)
  • Field (237-237)
src/components/new-form/form-field/context.tsx (3)
src/components/form/field-text/index.tsx (3)
  • TFieldValues (26-85)
  • div (52-82)
  • e (71-74)
src/components/form/field-checkbox/index.tsx (1)
  • field (50-75)
src/components/form/field-checkbox-group/index.tsx (1)
  • field (59-101)
src/lib/react-hook-form/index.tsx (2)
src/components/new-form/form-field/index.tsx (1)
  • FormField (44-82)
src/components/new-form/index.ts (1)
  • FormField (4-4)
src/components/new-form/form.tsx (3)
src/components/form/form.tsx (1)
  • e (38-47)
src/components/form/docs.stories.tsx (2)
  • form (70-114)
  • form (30-68)
src/components/form/form-test-utils.tsx (1)
  • T (14-41)
🪛 Biome (2.1.2)
src/components/new-form/docs.stories.tsx

[error] 44-44: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 54-54: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

src/components/new-form/form-field/index.tsx

[error] 59-59: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Check: 🧹 Linter
src/components/ui/field.tsx

[warning] 214-214:
Do not use item index in the array as its key

⏰ 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). (2)
  • GitHub Check: 🔬 Tests (lts/*)
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (8)
src/components/ui/input.tsx (1)

13-13: LGTM! More precise ARIA invalid state handling.

Narrowing the selector to has-[[aria-invalid=true]] correctly ensures invalid styling only applies when aria-invalid is explicitly true, not when it's false or missing. This aligns well with the new form-field system's centralized error handling.

src/components/new-form/form-field-label.tsx (1)

1-10: LGTM! Clean composition pattern.

The component properly consumes form field context and wires up the label associations correctly. The id and htmlFor attributes ensure proper accessibility linkage between label and input.

package.json (1)

57-58: LGTM! Appropriate dependencies for accessible primitives.

The Radix UI Label and Separator components provide solid accessible foundations for the new form field system.

src/components/ui/label.tsx (1)

1-22: LGTM! Well-structured accessible label component.

The component properly wraps Radix UI's Label primitive with appropriate styling and disabled state handling. The use of both group-data-[disabled=true] and peer-disabled variants ensures proper disabled styling in various composition scenarios.

src/components/ui/select.tsx (1)

23-23: LGTM! Appropriate type export for form integration.

Exporting TValueBase enables the new form field components to properly constrain their generic type parameters without duplicating the type definition.

src/components/new-form/field-text/index.tsx (1)

5-35: Clean implementation with proper handler chaining.

The component correctly:

  • Integrates with form field context
  • Chains onChange/onBlur handlers to preserve both RHF and custom behavior
  • Conditionally renders errors based on validation state
  • Maintains consistent ID patterns for accessibility
src/components/new-form/index.ts (1)

1-4: LGTM! Clean barrel export for the new form API.

The exports properly consolidate the public API surface for the form abstraction.

src/components/ui/field.tsx (1)

1-247: Well-structured Field component library.

The Field UI component library demonstrates solid design patterns:

  • Consistent use of data-slot attributes for composability
  • Clean separation of concerns with focused components
  • Good use of class-variance-authority for variant management
  • Comprehensive coverage of form field scenarios

The composable API will integrate well with the new FormField abstraction.

Comment on lines 59 to 66
const fieldCtx = useMemo(
() => ({
field,
fieldState,
size,
}),
[field, fieldState]
) as FormFieldContextValue;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'size' to the useMemo dependency array.

The memoized fieldCtx includes size (line 63) but the dependency array on line 65 only includes [field, fieldState]. When size changes, the context will contain a stale value.

Apply this diff:

         const fieldCtx = useMemo(
           () => ({
             field,
             fieldState,
             size,
           }),
-          [field, fieldState]
+          [field, fieldState, size]
         ) as FormFieldContextValue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fieldCtx = useMemo(
() => ({
field,
fieldState,
size,
}),
[field, fieldState]
) as FormFieldContextValue;
const fieldCtx = useMemo(
() => ({
field,
fieldState,
size,
}),
[field, fieldState, size]
) as FormFieldContextValue;
🧰 Tools
🪛 Biome (2.1.2)

[error] 59-59: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In src/components/new-form/form-field/index.tsx around lines 59 to 66, the
useMemo that builds fieldCtx includes the value "size" but the dependency array
only lists [field, fieldState], so fieldCtx can become stale when size changes;
update the dependency array to include size (i.e., [field, fieldState, size]) so
the memo recomputes whenever size updates.

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: 1

♻️ Duplicate comments (3)
src/components/ui/field.tsx (3)

213-219: Consider using error message as the key for better semantics.

While the current implementation using ${id}-error-${index} is stable across remounts (thanks to the useId prefix), using the error message itself as the key would be more semantically correct since errors are deduplicated by message. This ensures React's reconciliation aligns with the logical identity of each error.

Apply this diff:

       <ul className="ml-4 flex list-disc flex-col gap-1">
         {uniqueErrors.map(
-          (error, index) =>
+          (error) =>
             error?.message && (
-              // eslint-disable-next-line @eslint-react/no-array-index-key
-              <li key={`${id}-error-${index}`}>{error.message}</li>
+              <li key={error.message}>{error.message}</li>
             )
         )}
       </ul>

Note: Since errors are deduplicated by message at line 203-204, each error.message in the final array should be unique, making it a suitable key.


203-204: Handle undefined error messages in deduplication logic.

The Map-based deduplication will collapse all errors with undefined messages into a single entry. If multiple validation rules fail without providing messages, only one undefined error will be preserved.

Apply this diff to filter out undefined messages before deduplication:

     const uniqueErrors = [
-      ...new Map(errors.map((error) => [error?.message, error])).values(),
+      ...new Map(
+        errors
+          .filter((error) => error?.message)
+          .map((error) => [error.message, error])
+      ).values(),
     ];

207-207: Use strict equality === instead of ==.

Loose equality can lead to unexpected type coercion.

Apply this diff:

-    if (uniqueErrors?.length == 1) {
+    if (uniqueErrors?.length === 1) {
       return uniqueErrors[0]?.message;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 982e2f9 and 6859983.

📒 Files selected for processing (1)
  • src/components/ui/field.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-09-30T11:07:14.833Z
Learnt from: ivan-dalmet
Repo: BearStudio/start-ui-web PR: 532
File: src/features/auth/PageOAuthCallback.tsx:43-45
Timestamp: 2024-09-30T11:07:14.833Z
Learning: When suggesting changes to `useEffect` dependencies in React components, ensure that removing dependencies doesn't cause React Hook errors about missing dependencies.

Applied to files:

  • src/components/ui/field.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). (1)
  • GitHub Check: Playwright E2E Tests

@ivan-dalmet
Copy link
Member

Yes if we can rename for this API:

<form.Controller
  name="name"
  size="lg"
  render={(field) => (
    <>
      <field.Label>Name</field.Label>
      <field.Text />
      <field.Description>This is an helper text</field.Description>
    </>
  )}
/>

@vercel
Copy link

vercel bot commented Dec 19, 2025

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

Project Deployment Review Updated (UTC)
start-ui-web-v3 Ready Ready Preview, Comment Dec 19, 2025 4:03pm

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: 3

♻️ Duplicate comments (1)
src/components/new-form/field-text/index.tsx (1)

19-21: aria-describedby always references descriptionId even when no description is rendered.

The aria-describedby attribute unconditionally includes descriptionId, but if consumers don't render a <FormFieldDescription> element, this creates an invalid ARIA reference. Consider making this conditional based on whether a description is actually present, or document that consumers must always render a description element.

🧹 Nitpick comments (6)
src/components/new-form/form-field-description.tsx (1)

7-9: Use descriptionId from context for consistency.

The context already provides descriptionId (computed as ${field.name}-description), but this component recomputes it inline. Other components like FormFieldLabel use the context-provided ID. For consistency and to avoid potential drift:

Proposed fix
 export function FormFieldDescription(
   props: React.ComponentProps<typeof FieldDescription>
 ) {
-  const { field } = useFormControllerContext();
+  const { descriptionId } = useFormControllerContext();

-  return <FieldDescription id={`${field.name}-description`} {...props} />;
+  return <FieldDescription id={descriptionId} {...props} />;
 }
src/components/new-form/form-field-error.tsx (2)

7-7: Return null explicitly instead of implicit undefined.

While React handles undefined returns, explicitly returning null is the idiomatic pattern for "render nothing" and improves readability.

Proposed fix
-  if (!fieldState.invalid) return;
+  if (!fieldState.invalid) return null;

10-14: Use errorId from context for consistency.

Similar to FormFieldDescription, this component recomputes the error ID inline instead of using the context-provided errorId. For consistency with the context design:

Proposed fix
 export function FormFieldError(props: React.ComponentProps<typeof FieldError>) {
-  const { field, fieldState } = useFormControllerContext();
+  const { errorId, fieldState } = useFormControllerContext();

   if (!fieldState.invalid) return null;

   return (
     <FieldError
-      id={`${field.name}-error`}
+      id={errorId}
       errors={[fieldState.error]}
       {...props}
     />
   );
 }
src/components/new-form/form-controller/context.tsx (1)

25-25: Consider updating error message to match context name.

The error message references <FormField /> but the context is FormControllerContext. For consistency and easier debugging, consider updating the message to reference <FormController /> or the actual parent component name.

🔎 Proposed fix
-  if (!context) throw new Error('Missing <FormField /> parent component.');
+  if (!context) throw new Error('Missing <FormController /> parent component.');
src/components/new-form/field-select/index.tsx (2)

17-17: Consider explicit string conversion for data attribute.

The data-invalid attribute receives a boolean value, which will be coerced to the string "true" or "false". For consistency with HTML5 data attribute conventions, consider explicitly converting to a string or using a conditional:

data-invalid={fieldState.invalid ? 'true' : undefined}

18-40: Consider simplifying boolean conversions.

Lines 19-20 use fieldState.error ? true : undefined which works correctly but could be simplified to !!fieldState.error or Boolean(fieldState.error) || undefined for brevity. However, the current form is explicit and may be intentional for clarity.

The value mapping (line 27), onChange handling (lines 28-31), and onBlur chaining (lines 34-36) are all implemented correctly and maintain proper event propagation.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6859983 and 6c23cfc.

📒 Files selected for processing (12)
  • src/components/new-form/_field-components.ts (1 hunks)
  • src/components/new-form/docs.stories.tsx (1 hunks)
  • src/components/new-form/field-select/index.tsx (1 hunks)
  • src/components/new-form/field-text/index.tsx (1 hunks)
  • src/components/new-form/form-controller/context.tsx (1 hunks)
  • src/components/new-form/form-controller/index.tsx (1 hunks)
  • src/components/new-form/form-field-description.tsx (1 hunks)
  • src/components/new-form/form-field-error.tsx (1 hunks)
  • src/components/new-form/form-field-label.tsx (1 hunks)
  • src/components/new-form/hooks.ts (1 hunks)
  • src/components/new-form/index.ts (1 hunks)
  • src/components/new-form/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/new-form/index.ts
  • src/components/new-form/docs.stories.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
src/components/new-form/field-select/index.tsx (4)
src/components/ui/select.tsx (2)
  • TValueBase (23-23)
  • Select (55-212)
src/components/new-form/types.ts (1)
  • FieldProps (3-11)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/ui/field.tsx (2)
  • Field (241-241)
  • FieldError (244-244)
src/components/new-form/form-controller/index.tsx (4)
src/components/new-form/form-controller/context.tsx (2)
  • FormControllerContextValue (10-17)
  • FormControllerContext (19-20)
src/components/new-form/_field-components.ts (2)
  • FieldComponents (18-18)
  • fieldComponents (7-16)
src/components/new-form/index.ts (1)
  • FormController (4-4)
src/components/form/form-field-controller.tsx (2)
  • props (86-118)
  • TFieldValues (72-140)
src/components/new-form/form-field-description.tsx (2)
src/components/ui/field.tsx (1)
  • FieldDescription (243-243)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/new-form/form-field-error.tsx (2)
src/components/ui/field.tsx (1)
  • FieldError (244-244)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/new-form/hooks.ts (4)
src/components/new-form/form-controller/index.tsx (1)
  • FormController (42-82)
src/components/form/field-text/index.tsx (3)
  • TFieldValues (26-85)
  • div (52-82)
  • e (71-74)
src/components/form/field-date/index.tsx (1)
  • TFieldValues (25-84)
src/components/form/field-select/index.tsx (1)
  • TFieldValues (25-95)
src/components/new-form/form-field-label.tsx (2)
src/components/ui/field.tsx (1)
  • FieldLabel (246-246)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/new-form/types.ts (5)
src/components/form/field-checkbox/index.tsx (1)
  • field (50-75)
src/components/form/field-text/index.tsx (2)
  • TFieldValues (26-85)
  • div (52-82)
src/components/form/docs.stories.tsx (1)
  • form (70-114)
src/components/form/field-number/index.tsx (1)
  • fieldProps (67-100)
src/components/form/form-field-controller.tsx (1)
  • props (86-118)
src/components/new-form/_field-components.ts (11)
src/components/new-form/form-field-label.tsx (1)
  • FormFieldLabel (4-8)
src/components/new-form/field-text/index.tsx (1)
  • FieldText (6-39)
src/components/new-form/field-select/index.tsx (1)
  • FieldSelect (7-46)
src/components/new-form/form-field-description.tsx (1)
  • FormFieldDescription (4-10)
src/components/new-form/form-field-error.tsx (1)
  • FormFieldError (4-16)
src/components/form/field-select/index.tsx (2)
  • TFieldValues (25-95)
  • div (53-92)
src/components/form/field-text/index.tsx (1)
  • TFieldValues (26-85)
src/components/form/field-text/docs.stories.tsx (3)
  • form (55-82)
  • form (84-112)
  • form (114-142)
src/components/form/field-number/docs.stories.tsx (1)
  • form (84-111)
src/components/form/field-number/index.tsx (1)
  • fieldProps (67-100)
src/components/form/form-field-controller.tsx (1)
  • TFieldValues (72-140)
src/components/new-form/form-controller/context.tsx (4)
src/components/new-form/types.ts (1)
  • FormFieldSize (1-1)
src/components/form/field-text/index.tsx (2)
  • TFieldValues (26-85)
  • div (52-82)
src/components/form/form-field-controller.tsx (2)
  • props (86-118)
  • TFieldValues (72-140)
src/components/form/field-date/index.tsx (1)
  • TFieldValues (25-84)
src/components/new-form/field-text/index.tsx (4)
src/components/new-form/types.ts (1)
  • FieldProps (3-11)
src/components/new-form/form-controller/context.tsx (1)
  • useFormControllerContext (22-28)
src/components/ui/field.tsx (2)
  • Field (241-241)
  • FieldError (244-244)
src/components/form/field-text/index.tsx (2)
  • div (52-82)
  • e (71-74)
🪛 Biome (2.1.2)
src/components/new-form/form-controller/index.tsx

[error] 58-58: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (10)
src/components/new-form/field-text/index.tsx (1)

6-38: Overall implementation is solid.

The component correctly:

  • Chains onChange/onBlur to both internal field handlers and external props
  • Uses context-provided size for consistency
  • Conditionally renders errors based on hideErrors and fieldState.invalid
src/components/new-form/form-field-label.tsx (1)

4-8: LGTM!

Clean implementation that correctly binds the label to the form field using htmlFor={field.name} and provides a stable id for potential aria-labelledby references.

src/components/new-form/types.ts (1)

1-11: LGTM!

Well-designed utility types. FieldProps provides a clean way to extend component props with form-specific options while preserving type inference for the underlying component.

src/components/new-form/_field-components.ts (1)

7-18: Registry pattern is clean and extensible.

The as const assertion preserves literal types, and the comment guides future contributors. This aligns well with the PR's goal of registry-based field additions.

src/components/new-form/form-controller/index.tsx (2)

54-68: Acknowledged: useMemo inside render prop is unconventional but acceptable here.

The static analysis flags this as a hooks violation. While technically correct (hooks should be at the top level), this pattern is safe because:

  1. The render function is called exactly once per Controller render cycle
  2. The call is unconditional within that render

The eslint-disable comment documents the intentional deviation. Consider extracting to a separate inner component if this pattern becomes problematic or confuses future maintainers.


20-35: Well-structured type definition.

The FormControllerProps type cleanly extends UseControllerProps with required name and adds a typed render prop that exposes both field state and the component registry. This enables the composition pattern described in the PR objectives.

src/components/new-form/field-select/index.tsx (2)

7-14: LGTM!

The component signature correctly preserves generic typing from the Select component, and the context consumption is clean and type-safe.


41-43: No issues to address. The code correctly implements error handling:

FieldError expects an array of error objects, and the code properly wraps fieldState.error in an array [fieldState.error]. The FieldError component extracts only the .message property from each error object, which correctly handles React Hook Form's FieldError structure. The conditional rendering (fieldState.invalid && !hideErrors) ensures errors only display when appropriate, and FieldError safely handles cases where error may be undefined.

src/components/new-form/hooks.ts (1)

9-24: The ExplicitAny type does not exist in react-hook-form or standard TypeScript.

React Hook Form's useFormContext uses TContext = any as the default, not ExplicitAny. If ExplicitAny appears in the code on line 11, it must be defined locally in the codebase or represents a typo. Verify whether this is a custom type or if any should be used instead.

src/components/new-form/form-controller/context.tsx (1)

1-1: No action needed. The project is using React 19.2.0, which fully supports the use() hook. The code correctly imports and uses use(FormControllerContext) to read context values, which is the intended pattern in React 19.

Likely an incorrect or invalid review comment.

@@ -0,0 +1,18 @@
import { FormFieldError } from '@/components/form/form-field-error';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incorrect import path: imports from old form module instead of new-form.

FormFieldError is imported from @/components/form/form-field-error (the old form system), but it should be imported from @/components/new-form/form-field-error to maintain module isolation. This contradicts the PR's goal to decouple from the existing form system.

Proposed fix
-import { FormFieldError } from '@/components/form/form-field-error';
+import { FormFieldError } from '@/components/new-form/form-field-error';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { FormFieldError } from '@/components/form/form-field-error';
import { FormFieldError } from '@/components/new-form/form-field-error';
🤖 Prompt for AI Agents
In src/components/new-form/_field-components.ts around line 1, the file imports
FormFieldError from the old form module; update the import to point to the
new-form module to maintain isolation. Change the import path from
'@/components/form/form-field-error' to '@/components/new-form/form-field-error'
and ensure any related exports/types continue to match the new module's API; run
a quick build/type-check to catch any path or type mismatches after updating.

Comment on lines +10 to +17
export type FormControllerContextValue = {
size?: FormFieldSize;
errorId?: string;
labelId?: string;
descriptionId?: string;
field: ControllerRenderProps<FieldValues>;
fieldState: ControllerFieldState;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Type safety concern: Base FieldValues loses generic type information.

Line 15 uses ControllerRenderProps<FieldValues> without generic parameters, which means field.value will lose specific type information when the context is consumed. This weakens type safety across the form system.

Consider making FormControllerContextValue generic over TFieldValues:

export type FormControllerContextValue<
  TFieldValues extends FieldValues = FieldValues
> = {
  size?: FormFieldSize;
  errorId?: string;
  labelId?: string;
  descriptionId?: string;
  field: ControllerRenderProps<TFieldValues>;
  fieldState: ControllerFieldState;
};

Then update the context and hook accordingly to preserve type safety throughout the component tree.

🤖 Prompt for AI Agents
In src/components/new-form/form-controller/context.tsx around lines 10 to 17,
the context type uses ControllerRenderProps<FieldValues> which erases the
concrete form value types; make FormControllerContextValue generic: add a type
parameter like TFieldValues extends FieldValues = FieldValues and change the
field property to ControllerRenderProps<TFieldValues>. Then update the context
creation and any exported context hook/type to accept and propagate that same
generic parameter so consumers retain proper typed field.value (update
createContext default/undefined typing, the provider value type, and the
useFormController/useContext hook signatures where applicable).

Comment on lines +58 to +68
const fieldCtx = useMemo(
() => ({
labelId: `${id}-label`,
errorId: `${id}-error`,
descriptionId: `${id}-description`,
field,
fieldState,
size,
}),
[field, fieldState, id]
) as FormControllerContextValue;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing size in useMemo dependency array.

The memoized object includes size (line 65), but it's not listed in the dependency array (line 67). This could cause stale values if size changes.

Proposed fix
         const fieldCtx = useMemo(
           () => ({
             labelId: `${id}-label`,
             errorId: `${id}-error`,
             descriptionId: `${id}-description`,
             field,
             fieldState,
             size,
           }),
-          [field, fieldState, id]
+          [field, fieldState, id, size]
         ) as FormControllerContextValue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fieldCtx = useMemo(
() => ({
labelId: `${id}-label`,
errorId: `${id}-error`,
descriptionId: `${id}-description`,
field,
fieldState,
size,
}),
[field, fieldState, id]
) as FormControllerContextValue;
const fieldCtx = useMemo(
() => ({
labelId: `${id}-label`,
errorId: `${id}-error`,
descriptionId: `${id}-description`,
field,
fieldState,
size,
}),
[field, fieldState, id, size]
) as FormControllerContextValue;
🧰 Tools
🪛 Biome (2.1.2)

[error] 58-58: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In src/components/new-form/form-controller/index.tsx around lines 58 to 68, the
useMemo creates fieldCtx including `size` but the dependency array omits `size`;
update the dependency array to include `size` (i.e., [field, fieldState, id,
size]) so the memo recalculates when size changes, ensuring fieldCtx always
reflects the current size.

@ntatoud ntatoud force-pushed the feat/rhf-form-abstraction branch from 6c23cfc to 5336a31 Compare December 19, 2025 16:01
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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