Skip to content

Conversation

@Oksamies
Copy link
Contributor

@Oksamies Oksamies commented Dec 9, 2025

  • add interaction-aware helpers to useStrongForm so inputs only go invalid after user focus/blur or submit, and expose ready-to-spread props for focus/blur/csModifiers/aria
  • refactor create-team, add-member, and add-service-account forms to use the new helper props instead of hand-rolled wiring
  • teach the theme’s TextInput component about invalid state colors so csModifiers visually reflect errors

- add interaction-aware helpers to `useStrongForm` so inputs only go invalid after user focus/blur or submit, and expose ready-to-spread props for focus/blur/csModifiers/aria
- refactor create-team, add-member, and add-service-account forms to use the new helper props instead of hand-rolled wiring
- teach the theme’s TextInput component about invalid state colors so `csModifiers` visually reflect errors
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

This PR enhances form field validation and interaction tracking across multiple components. The useStrongForm hook is extended with three new methods—getFieldState, getFieldInteractionProps, and getFieldComponentProps—to enable per-field state management and validation. Three settings components (Teams.tsx, MemberAddForm.tsx, ServiceAccounts.tsx) are updated to use these new APIs for binding field-level props and displaying required indicators. Supporting CSS changes introduce invalid state styling with new color tokens for input elements.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and accurately summarizes the main changes: improving validation UX in StrongForm and adding invalid state styling to TextInput components.
Description check ✅ Passed The description clearly outlines the three primary objectives of the PR and relates directly to the changeset across all affected files.
✨ 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 12-09-improve_strongform_validation_ux_and_text_input_styling

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.

Copy link
Contributor Author

Oksamies commented Dec 9, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link

@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/cyberstorm-remix/app/settings/teams/Teams.tsx (1)

234-236: Modal may close on submit error.

strongForm.submit() can throw an error, but .then(() => setOpen(false)) will still execute if the promise resolves (even when onSubmitError is called internally before re-throwing). Consider using async/await with try/catch, or move setOpen(false) into onSubmitSuccess to ensure the modal stays open on error.

-          onClick={() => {
-            strongForm.submit().then(() => setOpen(false));
-          }}
+          onClick={async () => {
+            try {
+              await strongForm.submit();
+              setOpen(false);
+            } catch {
+              // Modal stays open on error
+            }
+          }}
🧹 Nitpick comments (3)
packages/cyberstorm-theme/src/components/TextInput/TextInput.css (1)

31-33: Wrapper invalid icon color missing focus-within state.

Line 31-33 sets the icon color for invalid state, but line 27-29 still applies focus color unconditionally. When an invalid input is focused, the icon will show focus color instead of invalid color.

Consider updating to maintain invalid styling during focus:

    &:focus-within .text-input__left-icon {
+     &:not(.text-input__wrapper--invalid) {
        --text-input-left-icon-color: var(--input-icon-color--focus);
+     }
    }

    &.text-input__wrapper--invalid .text-input__left-icon {
      --text-input-left-icon-color: var(--input-icon-color--invalid);
    }
apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts (2)

73-78: Consider extending isValueEmpty for arrays/objects.

Currently handles strings, undefined, and null. If you later need to validate that an array has items or an object has keys, this helper would incorrectly consider [] or {} as non-empty.

  const isValueEmpty = (value: unknown) => {
    if (typeof value === "string") {
      return value.trim() === "";
    }
+   if (Array.isArray(value)) {
+     return value.length === 0;
+   }
    return value === undefined || value === null;
  };

191-191: Missing dependencies in useEffect dependency array.

The effect references props.refiner, props.onRefineSuccess, and props.onRefineError (lines 172, 174-175, 182-183) but the dependency array only includes props.inputs. If these callbacks change between renders, the effect will use stale versions, potentially causing bugs.

Add them to the dependency array or wrap them with useRef to maintain stable references.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41c60ce and e53f68a.

📒 Files selected for processing (6)
  • apps/cyberstorm-remix/app/settings/teams/Teams.tsx (3 hunks)
  • apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/MemberAddForm.tsx (3 hunks)
  • apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts (4 hunks)
  • packages/cyberstorm-theme/src/components/TextInput/TextInput.css (2 hunks)
  • packages/cyberstorm-theme/src/components/componentsColors.css (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx (1)
packages/cyberstorm/src/newComponents/Modal/Modal.tsx (1)
  • Modal (185-345)
⏰ 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: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (9)
apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/MemberAddForm.tsx (1)

98-99: LGTM!

Clean integration of the new getFieldComponentProps helper. The spread placement after explicit props is correct, and the aria-hidden="true" on the asterisk appropriately hides the visual indicator from screen readers while the required attribute handles accessibility.

Also applies to: 126-127, 141-142

apps/cyberstorm-remix/app/settings/teams/Teams.tsx (1)

186-187: LGTM!

Consistent use of the new field props pattern. Required indicator and accessibility attributes are properly configured.

Also applies to: 213-213, 216-216, 225-226

packages/cyberstorm-theme/src/components/TextInput/TextInput.css (1)

62-79: CSS specificity: hover/focus may override invalid styling.

The invalid state rules (lines 62-68) are placed before hover/focus rules (lines 70-79). Since both use equivalent specificity, hover and focus styles will override the invalid styling when users interact with an invalid input.

If the intent is for invalid styling to persist during hover/focus, consider either:

  1. Adding :not(.text-input--invalid) to the hover/focus rules, or
  2. Repeating the invalid rules after hover/focus to ensure they take precedence
  .text-input:hover {
+   &:not(.text-input--invalid) {
      --text-input-background-color: var(--input-bg-color--hover);
      --text-input-border-color: var(--input-border-color--hover);
+   }
  }

  .text-input:focus-within {
+   &:not(.text-input--invalid) {
      --text-input-text-color: var(--input-text-color--focus);
      --text-input-background-color: var(--input-bg-color--focus);
      --text-input-border-color: var(--input-border-color--focus);
+   }
  }
packages/cyberstorm-theme/src/components/componentsColors.css (1)

266-266: LGTM!

New invalid state color tokens follow existing naming conventions and use appropriate red accent colors for visual feedback.

Also applies to: 270-270, 274-274, 279-279

apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx (1)

157-158: LGTM!

Clean refactor to use Modal subcomponents with the new field props pattern. The direct onClick={strongForm.submit} approach avoids the modal-close-on-error issue seen in Teams.tsx. Consistent integration with the rest of the PR.

Also applies to: 202-242

apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts (4)

1-1: LGTM!

Good additions for field interaction tracking. The fieldInteractions state structure and hasAttemptedSubmit flag enable the interaction-aware validation UX described in the PR objectives.

Also applies to: 60-71


93-110: Well-designed field state logic.

The interaction-aware validation (hasFinishedInteraction combining focus+blur or submit attempt) provides good UX by not showing errors before user engagement. Clean implementation.


112-130: Nice optimization with idempotent updates.

The early return when state hasn't changed (lines 120-125) prevents unnecessary re-renders. Good practice.


140-158: Clean composition of field props.

getFieldComponentProps nicely combines state derivation with interaction handlers and ARIA attributes. The optional disabled parameter provides flexibility.

One consideration: csModifiers returns a new array on each call. If NewTextInput does shallow comparison on csModifiers, this could cause re-renders. Memoizing the array or using a stable reference pattern would help if this becomes an issue.

@codecov
Copy link

codecov bot commented Dec 9, 2025

Codecov Report

❌ Patch coverage is 0% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.55%. Comparing base (41c60ce) to head (e53f68a).

Files with missing lines Patch % Lines
...remix/cyberstorm/utils/StrongForm/useStrongForm.ts 0.00% 81 Missing ⚠️
...eams/team/tabs/ServiceAccounts/ServiceAccounts.tsx 0.00% 37 Missing ⚠️
apps/cyberstorm-remix/app/settings/teams/Teams.tsx 0.00% 5 Missing ⚠️
...settings/teams/team/tabs/Members/MemberAddForm.tsx 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1661      +/-   ##
==========================================
- Coverage   11.57%   11.55%   -0.03%     
==========================================
  Files         317      317              
  Lines       22867    22955      +88     
  Branches      505      508       +3     
==========================================
+ Hits         2647     2652       +5     
- Misses      20220    20303      +83     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

<div className="add-member-form__field add-member-form__username">
<label className="add-member-form__label" htmlFor="username">
Username
Username <span aria-hidden="true">*</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on:

  • adding something like title="Required" to this element?
  • having a dedicated component for it to not have to repeat this over and over around the project?

</Modal.Body>
) : (
<Modal.Body>
<form
Copy link
Contributor

Choose a reason for hiding this comment

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

The form was here on purpose to allow submitting the form with enter on text input, as is expected. It shouldn't be removed as part of these changes.

The changes also makes the modal layout awkward:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, hmm, I'm not sure of the usage of the form elements with useStrongForm 🤔 But I'll check this out next time me work

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this could be achieved by attaching event listeners to inputs, but on a bigged form that's a lot of boilerplate. Maybe the getFieldComponentProps could now be used to avoid that? But then we'd need to be able to tell different field types apart (input vs textarea vs select...).

Just a thought, some completely different approach might be better.

>({});
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);

const isValueEmpty = (value: unknown) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the body of useStrongForm is getting rather long, so a helper function like this could be moved elsewhere.

const validator = props.validators?.[field];
const value = props.inputs[field];
const isRequired = Boolean(validator?.required);
const rawInvalid = isRequired && isValueEmpty(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

As part of my own task, I tried to build a URL validator for the team profile form on top of the changes on this PR. Having duplicate checks here and in isReady gets less ideal with each validator that is added. Maybe check if that can be avoided?

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