fix(committees): block save when org name typed but not resolved - #1124
Conversation
When the organization field is shown on the Add Member dialog, typing a free-form name that is never resolved through the CDP org search left organization_id null. The invite would be created with an unverifiable org name, mirroring the accept-flow bug (LFXV2-2737) where a mismatched pre-populated name silently blocks the Join Group button. Gate onSubmit() behind an orgInvalid() check: if a name is entered but no org ID has been resolved, set orgSubmitAttempted and return early. Show a red validation message under the org search field explaining that the user must select from search results. LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Move orgFormValues (toSignal), orgInvalid (computed), and showOrgError (computed) from inline class body to private init functions, matching the initSearchResults() pattern per component-organization convention. LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
PR SummaryLow Risk Overview Validation is reactive—an “Organization not found…” alert appears as soon as there is search text without a confirmed selection, and Send Invites stays disabled until the org is valid or the field is cleared. Manual “create org” mode relies on required HTTPS website validators on
Reviewed by Cursor Bugbot for commit 506445f. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe add-member dialog validates organization selection after submission attempts, preserves organization autofill data, blocks invalid invitations when an organization is required, and displays an organization selection error. ChangesOrganization validation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds submit-time validation to prevent committee invitations with unresolved organization names.
Changes:
- Tracks organization validity and submit attempts.
- Displays an organization resolution error.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
add-member-dialog.component.ts |
Adds organization validation gating. |
add-member-dialog.component.html |
Renders the validation error. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Allow name+URL as valid org state (employer autofill path does not resolve an org_id but provides a known name and website). Patch both organization and organization_url from the employer autofill so the new validation is satisfied without requiring a manual re-selection. Add role="alert" to the validation error paragraph so screen readers announce the message when it appears after a submit attempt. LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-1124.dev.v2.cluster.linuxfound.info Deployment Details:
The deployment will be automatically removed when this PR is closed. |
The name-change subscription cleared organization_id but left organization_url intact. A user who selected Org A (getting both id + url) then typed a new free-form name would have the id cleared while Org A's URL remained, satisfying the name+URL validity check and bypassing the org validation gate. Fix: also clear organization_url in the subscription patch so that any name deviation from the resolved org invalidates both fields. To keep the employer-autofill path working, set resolvedOrganizationName to the employer name before patching so the subscription treats the autofilled name as the resolved name and does not immediately wipe the URL that was just written. Addresses copilot-pull-request-reviewer and cursor[bot] findings on PR #1124. LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Review Feedback AddressedChanges Made
Questions Answered
Threads Resolved4 of 5 unresolved threads addressed in this iteration. Still Open
|
The autofill path called getUserCurrentEmployer() which returns
CommitteeOrganizationReference via currentEmployerFromWorkExperiences().
That util returns { name, id } only — no website. The patchValue call
only set organization and organization_url (always ''), leaving
organization_id null. orgInvalid() then saw hasOrgId=false, hasUrl=false
and blocked every autofilled submit.
Fix: include organization_id: employer.id ?? null in the patchValue so
the autofill carries the CDP ID that is already available, satisfying the
validation without requiring a URL.
Per copilot-pull-request-reviewer.
Generated with [Claude Code](https://claude.ai/code)
Signed-off-by: Andres Tobon <andrest2455@gmail.com>
- Clear resolvedOrganizationName when the name-change subscription invalidates the form, so returning to a previously resolved name is treated as unresolved and cannot bypass the URL-based org check - Remove emitEvent: false from patchValue so orgFormValues signal updates immediately and orgInvalid() reads current state LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8d32cb4. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:330
- The pending autocomplete text is only considered when the parent name is empty. After selecting Org A and then typing an unresolved Org B, keystrokes update only the child
searchTerm; the parent still contains A's name/ID/URL, so this guard passes andresolveCurrentEntry()submits Org A while the field displays B. Treat any mismatch between the displayed search text and the parent name as unresolved.
const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
if (!hasName && pendingSearch) return true;
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:148
- Returning unconditionally in manual mode preserves resolution data from the previous selection. If Org A is selected, the user types Org B and chooses “I want to create,” the name changes to B while A's ID and website remain; the website is already valid, so B can be submitted with A's metadata. Clear the prior resolution once when manual mode diverges from the resolved name, while preserving later edits to the new manual entry.
if (this.organizationSearch()?.manualMode()) return;
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:187
Validators.requiredis the only validator here that rejectsnull, but the manual-mode template renders messages only fortrimmedRequiredandhttpsUrl. Since clearing the search sets the domain control tonull, typing another name and entering manual mode can produce a touched, invalid control with only arequirederror and no explanation. Render the required message for eitherrequiredortrimmedRequired.
const validators = this.domainRequired() ? [Validators.required, trimmedRequired(), httpsUrlValidator()] : [httpsUrlValidator()];
- org-search: markAsTouched() on domain control immediately on entering manual mode so the required-URL error is visible from the start and the disabled Send Invites button is always self-explaining (T1/T2) - add-member-dialog: replace isValidUrl() with an inline https-protocol check (new URL(v).protocol === 'https:') in initOrgInvalid() so search- mode and manual-mode validation are consistent; remove unused import (T3) LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
switchToSearchMode() now clears the parent form's organization name and URL values (matching onSearchClear() behavior) and resets the internal search input. Previously the stale manual-mode name remained in the form with no org-id, making orgInvalid() return true and keeping Send Invites greyed out after switching back to search mode. LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:119
canSubmitpreventsonSubmit()from ever settingorgSubmitAttempted, butshowOrgError()remains false for invalid states where the parent already has a name. This occurs for the supported employer-autofill case whencurrentEmployerFromWorkExperiences()returns a name with a null ID (packages/shared/src/utils/invitation.utils.ts:24-28) and no website: Send Invites is disabled with no explanation or actionable error. Keep the button enabled when emails are submittable and let the existing submit guard expose the organization error.
public readonly canSubmit = computed(
() => !this.submitting() && this.categorized().toInvite.length > 0 && !(this.showOrganizationField() && this.orgInvalid())
);
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:187
Validators.requiredreports{ required: true }fornull, but the manual-mode template only renderstrimmedRequiredandhttpsUrlerrors. After clearing an existing autocomplete selection (which sets the domain tonull) and then choosing “I want to create,” the website is invalid and Send Invites is disabled, yet no website error is shown. Render therequirederror together withtrimmedRequired(or use a validator that emits the displayed error key).
const validators = this.domainRequired() ? [Validators.required, trimmedRequired(), httpsUrlValidator()] : [httpsUrlValidator()];
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323
pendingSearchis only considered when the parent name is empty. After selecting/autofilling Org A, editing the autocomplete to unresolved Org B changes only the child search text; the parent still contains A's name and resolution data, so this returns valid and submission resolves/sends A even though B is displayed. Compare the trimmed child text with the parent organization name, and ensure the error predicate also handles a mismatch.
const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
if (!hasName && pendingSearch) return true;
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:119
- Disabling on
orgInvalid()can make the new validation gate impossible to trigger. The employer helper can return a name with a null ID and no website; that autofill is invalid, butshowOrgError()remains false because the parent has a name, while this disables the only action that setsorgSubmitAttempted. Keep the button available when emails are submittable and letonSubmit()block and reveal the organization error.
public readonly canSubmit = computed(
() => !this.submitting() && this.categorized().toInvite.length > 0 && !(this.showOrganizationField() && this.orgInvalid())
);
- switchToManualMode() now clears organization_url and organization_id before applying manual-mode validators, preventing a prior selection's URL from silently passing httpsUrlValidator for a different new org - Template now checks errors['required'] alongside errors['trimmedRequired'] so the "Website is required" message appears when the control is null (Validators.required fires on null, trimmedRequired only on whitespace) LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:324
- Typing after a prior selection is still accepted as that prior organization. The child search term changes, but the parent retains the selected name/ID/URL; because this check only considers
pendingSearchwhenhasNameis false,orgInvalid()stays false and submission sends the stale selected org while the UI displays the newly typed unresolved text. Compare the pending search text with the parent name and reject any non-empty mismatch.
const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
if (!hasName && pendingSearch) return true;
if (!hasName) return false;
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:191
- Setting the website to
nullreopens the required-website bypass inAcceptInviteOrganizationDialogComponent. Switching to manual mode makes itsisNewOrg()effect rerun and replace these validators withtrimmedRequired()plushttpsUrlValidator(); both explicitly acceptnull, so the control becomes valid and a new organization can be confirmed without a website. Clear to an empty string so the parent's trimmed-required validator still rejects it after that effect runs.
domainCtrl.setValue(null);
audigregorie
left a comment
There was a problem hiding this comment.
Code Review Summary
Targeted, well-reasoned fix for the LFXV2-2737 silent-acceptance bug — gating on !hasOrgId && !hasValidUrl correctly distinguishes "typed but never resolved" from "selected with a normalized domain but no CDP id." Validator application on manual-mode entry/exit is symmetric and restores the original ValidatorFn.
Two things to resolve before merge:
- UX divergence — the Summary states the error only appears after the first submit attempt, but
initShowOrgError()surfaces it reactively as the user types. Either gate onorgSubmitAttempted()or update the PR description/test plan. (See inline thread.) - No tests for the new
orgInvalid/showOrgErrorstate machine; given this guards a regression-prone accept-flow path, a few component spec cases are warranted.
Major — outside the diff
- No unit tests for
initOrgInvalid()/initShowOrgError()branching (manual vs search mode, hasName/hasOrgId/hasValidUrl matrix, autofill vs typed, pendingSearch). The repo has specs for comparable components; this behavioral logic should be covered. - Redundant
organization_idpatch on submit — inonSubmit,resolveCurrentEntry()already emitsonOrganizationResolved→onOrgResolved()→patchValue({ organization_id }), then thenextcallback patchesorganization_idagain from the sameresult.id. Harmless, but a future reader may wonder which path wins. Consider droppingorganization_idfrom thenextpatch and keeping onlyorganization: result.name.
What's done well
- Correct root-cause gating:
!hasOrgId && !hasValidUrlallows a selected suggestion (normalized domain) through even when CDP resolve returns no id, while blocking pure free-text-no-resolution. - Autofill path sets
resolvedOrganizationNamebefore patching so the name-change subscription doesn't immediately wipeorganization_id/organization_url. - Symmetric validator save/restore on manual-mode toggle;
role="alert"+data-testidon the error message.
See the inline threads for the remaining Minor nits (hardcoded organization_id control name, domainOriginalValidator re-entrancy, dual searchTerm writers, initOrgFormValues re-emit cadence).
audigregorie
left a comment
There was a problem hiding this comment.
Coding-standards pass
Follow-up audit against .claude/rules/ (global comment rules, angular-standards.md, component-organization.md, styling.md, ssr-safety.md). The PR is largely compliant — init*() structure, signal naming, inject() DI, @if/@for templates, lfxColors scale usage, and SSR safety all check out.
Two hard violations (over-long comments) and one design suggestion below. The over-long comments violate the global rule: "Comments must be one or two lines maximum. No exceptions — not even when existing functions in the same file use longer comments."
- Add idControl input to OrganizationSearchComponent; switchToManualMode() now clears the id control via input rather than hardcoding 'organization_id' - Guard domainOriginalValidator capture with undefined check to prevent double-entry from overwriting the saved original with manual validators - Shorten 4-line and 3-line comments to 2-line max per global comment rule LFXV2-2737 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323
- This still allows a stale selected organization to be submitted when the user edits the autocomplete text. After selecting Org A, typing unresolved Org B only changes
searchTerm; the parent still contains A's name/ID/URL, sohasNameis true, this branch is skipped, andresolveCurrentEntry()submits Org A even though the field displays B. Compare the pending search text with the parent organization (and makeshowOrgError()use the same mismatch condition) so edited text invalidates the prior selection.
const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
if (!hasName && pendingSearch) return true;
🧹 Deployment RemovedThe deployment for PR #1124 has been removed. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323
- This only treats pending autocomplete text as unresolved when the parent name is empty. After selecting Org A, typing Org B updates the child
searchTermbut leaves Org A's parent name/ID/URL intact, so this guard remains valid and submission sends Org A while the UI shows Org B. Treat any non-empty pending text that differs from the parent name as unresolved.
// User typed in the search box but never selected a result (e.g. org doesn't exist in CDP).
// The parent form stays empty in that case, so check the component's pending search text.
const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
if (!hasName && pendingSearch) return true;
| const hasName = !!(this.orgFormValues().organization ?? '').trim(); | ||
| return !hasName && !!(this.organizationSearch()?.searchTerm() ?? ''); |
Add orgInvalid and showOrgWarning computed signals, mirroring the pattern from the add-member dialog (PR #1124). The existing isNewOrg() only inspected the parent form's organization value, which is only set on explicit selection or manual-mode entry — so typing in the search box with no confirmed result left the button enabled and the warning hidden. orgInvalid covers three cases: - manual mode: URL validator status (same as before via urlStatus) - pending search: searchTerm() is non-empty but organization is empty - unresolved name: name in form but no organization_id and URL invalid showOrgWarning gates the warning text to non-manual-mode only since the URL field's own inline validation owns display in manual/create mode. canConfirm now delegates to !orgInvalid() instead of the previous (!isNewOrg() || urlStatus() === 'VALID') check. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
…1156) * fix(committees): pre-resolve org before accept-invite dialog opens When a pre-filled organization has a name but no CDP id (sourced from a free-text invite or a work-experience employer with no linked account), isNewOrg() in the accept dialog becomes true, adds a required URL validator, and onConfirm() silently returns invalid before any network call can be made. Fix by resolving the org against CDP in InvitationAcceptFlowService before opening the dialog. On success the dialog opens with a valid organization_id, isNewOrg() stays false, and the user can confirm immediately. On failure the context passes through unchanged so the existing manual-fallback behaviour is preserved. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(review): guard against empty name in resolved org Preserve the original org.name if CDP resolveOrganization returns an empty or missing name, preventing a silent overwrite. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(review): log warning on org pre-resolution failure Add console.warn in catchError so failed CDP resolve calls surface in browser devtools instead of silently passing through. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(review): use search-then-resolve to pre-fill org id safely Replace direct resolveOrganization call (find-or-create, requires domain) with a two-step read-only approach: 1. searchOrganizations(name) — read-only GET to find the CDP-canonical domain without side effects. 2. resolveOrganization only on an exact name match, using the CDP domain so the call is a find (not a create). Adds timeout(2000) so a stalled lookup falls through in 2 s rather than blocking the dialog indefinitely. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): forward resolved org website to accept dialog context After a successful CDP search-then-resolve, preResolveOrganization now also sets organization.website from the CDP-canonical domain via normalizeToUrl. Without this, buildCommitteeOrganizationPayload (which intentionally drops organization_id — committee-service rejects CDP UUIDs) would send a name-only payload and the dialog's organization_url field would open empty. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): show org warning and disable join when unresolved When the accept dialog opens with a pre-filled org name that has no CDP id (e.g. pre-resolution failed or org is not in CDP), the Join Group button is now disabled and a red error message explains that the org must be searched and selected (or entered manually with a website URL). Previously the button appeared active but onConfirm() returned silently when form.valid was false, leaving the user with no feedback. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(review): fix manual org entry and stale resolvedOrganizationName - canConfirm computed replaces isNewOrg() on the Join button so manual mode (name + valid HTTPS URL, no CDP id) can be confirmed; button is disabled only while submitting or while the URL is invalid, not whenever isNewOrg() is true - urlStatus toSignal drives canConfirm reactively in zoneless mode - resolvedOrganizationName initialized from config.data.organization.name when the dialog opens with a pre-resolved id, preventing the valueChanges handler from clearing organization_id on any programmatic setValue that re-sets the same CDP-confirmed name - Warning copy updated to "I want to create" to match the actual org-search button label LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): fix stale urlStatus after validator change in dialog Replace toSignal(statusChanges) with a WritableSignal for urlStatus. The effect was calling updateValueAndValidity({ emitEvent: false }), which suppressed statusChanges — leaving urlStatus stale at VALID even after validators made the empty URL field invalid. canConfirm() returned true, the button appeared enabled, and onConfirm() silently bailed on form.valid — re-creating the original silent-fail for unresolved orgs. Fix: subscribe to statusChanges via takeUntilDestroyed() to track user-typed changes, and manually call urlStatus.set(urlControl.status) inside the effect immediately after updateValueAndValidity so that any validator-driven status change is always reflected in the signal. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): clear org id when entering manual create mode in dialog Pass idControl="organization_id" to lfx-organization-search so that switchToManualMode() clears the pre-resolved organization_id when the user clicks "I want to create". Without this, clicking "I want to create" with a pre-resolved org kept organization_id set (because the typed name equalled resolvedOrganizationName), leaving isNewOrg() false and bypassing the URL validator — Join Group could be submitted without a website URL. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): disable join and warn on unresolved org search Add orgInvalid and showOrgWarning computed signals, mirroring the pattern from the add-member dialog (PR #1124). The existing isNewOrg() only inspected the parent form's organization value, which is only set on explicit selection or manual-mode entry — so typing in the search box with no confirmed result left the button enabled and the warning hidden. orgInvalid covers three cases: - manual mode: URL validator status (same as before via urlStatus) - pending search: searchTerm() is non-empty but organization is empty - unresolved name: name in form but no organization_id and URL invalid showOrgWarning gates the warning text to non-manual-mode only since the URL field's own inline validation owns display in manual/create mode. canConfirm now delegates to !orgInvalid() instead of the previous (!isNewOrg() || urlStatus() === 'VALID') check. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): grey join and show error when returning to search mode Two fixes for the back-to-search-mode case in the confirm-org dialog: 1. orgInvalid() now returns true for an empty org name even when there is no pending searchTerm. Previously the early-return `return false` left the button enabled after the org-search component cleared the name on switchToSearchMode(), so clicking Join Group silently failed. 2. markAsTouched() is called on organizationControl whenever the name is cleared (normalizedName is empty). Because the toSignal subscription and our valueChanges subscription both fire synchronously before the template re-renders, touched is already true when Angular reads organizationControl.touched in the next render cycle — making the "Organization is required" error appear without a submit attempt. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(invite): disable Join Group when URL empty in manual mode `orgInvalid()` delegated to `urlStatus()` for both the manual-mode and the is-new-org branches. Because `switchToManualMode()` reads the `domainRequired` input synchronously — before Angular's change-detection has propagated the updated `isNewOrg()` value — the stale `false` value caused only `[httpsUrlValidator()]` to be applied. That validator returns null (valid) for empty strings, so `urlStatus` was set to 'VALID' and the button stayed enabled with no URL entered. Add an explicit empty-URL guard before the `urlStatus()` check in both branches. An empty `organization_url` now immediately returns `true` (invalid) regardless of validator state, making `canConfirm()` false and the button disabled the moment manual mode is entered. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> * fix(review): address PR #1156 review feedback Address review comments from @audigregorie, copilot-pull-request-reviewer: - accept-invite-organization-dialog.component.ts: add org-name guard to manual-mode branch of orgInvalid() so clearing the name while a valid URL is set still disables Join Group (per @audigregorie, copilot-pull-request-reviewer) - accept-invite-organization-dialog.component.ts: remove redundant `!hasName && pendingSearch` dead branch — subsumed by `!hasName` (per @audigregorie) - accept-invite-organization-dialog.component.ts: block submission when visible search term has been edited without a new selection, preventing stale pre-resolved name/id from being submitted (per copilot-pull-request-reviewer) - accept-invite-organization-dialog.component.ts: skip resolveCurrentEntry() when organization_id is already set to avoid a redundant /resolve POST (per @audigregorie) - accept-invite-organization-dialog.component.html: replace "Organization not found" warning with selection-focused wording accurate while results are loading or visible (per copilot-pull-request-reviewer) - invitation-accept-flow.service.ts: fall back to org.website when normalizeToUrl(match.domain) returns null so an original website is not silently discarded for empty-domain matches (per @audigregorie) Resolves 7 review threads. Thread 4 (pre-resolution latency) acknowledged with a PR description update — addressed separately without a code change. LFXV2-2751 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com> --------- Signed-off-by: Andres Tobon <andrest2455@gmail.com>

Summary
Ticket
LFXV2-2737
Test plan