Skip to content

feat: add agreement title and nickname frontend validation#5670

Open
weimiao67 wants to merge 7 commits into
mainfrom
OPS-4636/unique_agreement_title_nickname_ui_validation
Open

feat: add agreement title and nickname frontend validation#5670
weimiao67 wants to merge 7 commits into
mainfrom
OPS-4636/unique_agreement_title_nickname_ui_validation

Conversation

@weimiao67
Copy link
Copy Markdown
Contributor

@weimiao67 weimiao67 commented May 14, 2026

What changed

Adds inline uniqueness validation for agreement title and nickname fields in the create/edit Agreement form. A new backend endpoint (GET /agreements/check-unique) checks whether a candidate value is already in use, respecting existing DB constraints (case-insensitive name scoped by agreement_type, globally unique nick_name). The frontend calls this endpoint on blur and shows an inline error if a duplicate is detected. The check is debounced when agreement_type changes to avoid rapid API calls.

Also adds onBlur support to the shared Input UI component.

Issue

#4636

How to test

  1. Navigate to Agreements > Create Agreement
  2. Select agreement type "Contract"
  3. Enter a title that matches an existing contract agreement
  4. Tab out of the title field — an inline error should appear: "This title already exists. Try a different one"
  5. Change the agreement type — the error should clear/re-evaluate (same title may be valid for a different type)
  6. Enter a nickname that matches an existing agreement's nickname, tab out — an inline error should appear: "This nickname already exists. Try a different one"
  7. Verify the Continue button is disabled while any uniqueness error is shown
  8. Edit an existing agreement — verify it does not flag itself as a duplicate

A11y impact

  • No accessibility-impacting changes in this PR

Screenshots

Screenshot 2026-05-14 at 10 43 02 AM

Definition of Done Checklist

  • OESA: Code refactored for clarity
  • OESA: Dependency rules followed
  • Automated unit tests updated and passed
  • Automated integration tests updated and passed
  • Automated quality tests updated and passed
  • Automated load tests updated and passed
  • Automated a11y tests updated and passed
  • Automated security tests updated and passed
  • 90%+ Code coverage achieved
  • Form validations updated

@weimiao67 weimiao67 self-assigned this May 14, 2026
@weimiao67 weimiao67 marked this pull request as ready for review May 14, 2026 16:52
@weimiao67 weimiao67 marked this pull request as draft May 14, 2026 19:26
@weimiao67 weimiao67 marked this pull request as ready for review May 15, 2026 05:25
Copy link
Copy Markdown
Contributor

@josbell josbell left a comment

Choose a reason for hiding this comment

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

Review: Request Changes

After deep investigation of the implementation, I found 2 critical issues that need to be fixed before merge. I also validated several concerns raised by automated review and found them to be false positives.


✅ Issues That Need Fixing

1. CRITICAL: Validation Bypass Due to Incomplete Dependency Array

Location: frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.hooks.js:274-279

Issue: The useEffect that re-checks title uniqueness when agreementType changes is missing agreementTitle in its dependency array:

React.useEffect(() => {
    if (agreementType && agreementTitle) {
        checkUniqueOnBlur("name", agreementTitle);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
}, [agreementType]);  // ❌ agreementTitle used but not in deps

Impact: Users can bypass uniqueness validation by editing the title without changing the agreement type:

  1. User types "Agreement A" → uniqueness check runs ✓
  2. User changes to "Duplicate Title" (without changing type) → no check runs
  3. Form submits with duplicate title, backend rejects it

This defeats the core purpose of this PR (early duplicate detection).

Fix:

React.useEffect(() => {
    if (agreementType && agreementTitle) {
        checkUniqueOnBlur.cancel();  // Also fix issue #2
        checkUniqueOnBlur("name", agreementTitle);
    }
}, [agreementType, agreementTitle, checkUniqueOnBlur]);  // ✅ Complete deps

2. Race Condition: Missing Debounce Cancellation on Type Change

Location: Same useEffect at lines 274-279

Issue: When agreementType changes, pending debounced uniqueness checks aren't cancelled before triggering a new check.

Impact: If a user rapidly changes agreement types, the old debounced call (300ms delay) may execute after the new type is selected, showing stale validation errors for the wrong type.

Fix: Add checkUniqueOnBlur.cancel(); before the new check (included in Fix #1 above).


❌ False Positives (No Action Needed)

I also investigated several other concerns and found them to be invalid:

✅ Case-Sensitivity Is Consistent

  • Claim: Frontend/backend case-sensitivity mismatch for title
  • Reality: All three layers (DB, backend query, frontend) use case-insensitive comparison for title via lower() / .toLowerCase(). No mismatch exists.

✅ Vest v6 Pattern Is Correct

  • Claim: Conditional if (fieldName) { only(fieldName); } violates Vest v6 patterns
  • Reality: This is actually the safer pattern. The CLAUDE.md warning is about passing the data object to only(), not about conditional usage.

✅ Nickname Case-Sensitivity Is By Design

  • All layers consistently treat nickname as case-sensitive (unlike title which is case-insensitive). This is working as designed.

Summary

Please fix the 2 issues in the useEffect at lines 274-279:

  1. Add complete dependency array: [agreementType, agreementTitle, checkUniqueOnBlur]
  2. Add checkUniqueOnBlur.cancel(); before calling the debounced function

These are critical to ensuring the uniqueness validation works correctly in all user workflows.

@weimiao67
Copy link
Copy Markdown
Contributor Author

@josbell Thanks for the review and comments! I looked into both.

  1. The useEffect at 274–279 is a secondary trigger for the per-type re-check; the primary triggers are onBlur on the title input (AgreementEditForm.jsx:208) and verifyUniquenessBeforeSubmit on Save Draft / Continue (hooks.js:458). Adding agreementTitle to the deps would shift the check from on-blur to on-every-keystroke, which is a behavior change.
  2. The pending debounce is already cancelled when checkUniqueOnBlur is rebuilt — see the cleanup at hooks.js:270, which fires whenever agreementType changes.

Let me know if I missed anything.

@weimiao67 weimiao67 requested a review from josbell May 15, 2026 19:56
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.

2 participants