Skip to content

feat(profile): allow clearing free-text fields to empty - #1301

Merged
fayazg merged 8 commits into
mainfrom
feat/LFXV2-2933-clear-fields
Aug 4, 2026
Merged

feat(profile): allow clearing free-text fields to empty#1301
fayazg merged 8 commits into
mainfrom
feat/LFXV2-2933-clear-fields

Conversation

@fayazg

@fayazg fayazg commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Free-text profile fields (bio, job_title, address, city, postal_code,
    phone_number) now send the raw form value on save, so emptying a control sends
    '' and auth-service overwrites the stored value.
  • Previously every field mapped field || undefined, so an emptied control was
    omitted from the PATCH and a set value could never be cleared back to empty.
  • Name and the constrained selects (country, state_province, t_shirt_size,
    organization) keep || undefined and remain non-clearable, by product decision.
  • Applied identically to both save paths — the drawer's onSubmit() and the Flow C
    management-token replay in handleProfileAuthReturn().
  • Client-only change: the BFF controller is a pass-through, validateUserMetadata
    accepts '', and the optimistic update already forwards empty strings — no server
    or optimistic-update change required.
  • Adds an E2E (S5) that seeds a bio, reopens the drawer, clears it, and asserts the
    PATCH carries bio: '' and the panel drops the About Me block.

Free-text profile fields (bio, job_title, address, city, postal_code,
phone_number) now send the raw form value on save, so emptying a control
sends '' and auth-service overwrites the stored value. Previously every
field mapped `field || undefined`, dropping empties from the PATCH so a
set value could never be cleared. Name and the constrained selects keep
`|| undefined` and stay non-clearable. Change is client-only: the BFF is
a pass-through, the validator accepts '', and the optimistic update
already forwards empty strings.

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
@fayazg
fayazg requested a review from a team as a code owner August 3, 2026 23:28
Copilot AI balanced review requested due to automatic review settings August 3, 2026 23:28
@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes profile PATCH semantics and optimistic/Flow C replay paths; incorrect metadataLoaded handling could omit updates or wipe fields, though guards and new E2E tests target those cases.

Overview
LFXV2-2933: Users can clear free-text profile fields (bio, job_title, city, address, postal_code, phone_number) by saving an empty control. The edit drawer sends '' in user_metadata when metadata actually loaded; name and select fields still use omit-on-empty (|| undefined) and stay non-clearable.

Safety when metadata never loaded: If combinedProfile.profile is null (failed GET), empty free-text values are omitted from PATCH so the UI does not wipe server data the form never showed. applyOptimisticProfileUpdate skips merging in that case and refetches instead, so a save after a degraded load cannot flip metadataLoaded and enable accidental clears on reopen.

Flow C: Pending save in sessionStorage now stores mapped userMetadata (not raw form) so clear-to-empty survives the management-token redirect; legacy { savedAt, form } envelopes are still replayed via mapLegacyFormEnvelope.

Tests: E2E S5–S7 cover clear-to-'' + panel hide, omit-on-failed-load, and no clear-to-empty after save on failed load.

Reviewed by Cursor Bugbot for commit d341ce6. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: acc8f772-8e86-4c59-9322-f09068304f5e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Profile submission preserves empty free-text values when metadata loads successfully. It omits empty values when metadata is unavailable. Flow C restores mapped metadata and supports legacy envelopes. E2E tests verify both payload behaviors.

Changes

Profile empty-value clearing

Layer / File(s) Summary
Normalize and restore profile metadata
apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts, apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts
Loaded metadata preserves empty free-text values. Unavailable metadata omits empty values. Redirect persistence stores mapped userMetadata. Flow C restores current payloads and maps legacy envelopes.
Validate cleared-value payloads
apps/lfx-one/e2e/profile-edit-drawer.spec.ts
E2E tests verify an explicit empty bio value after successful metadata loading and omission of empty bio and job title values when metadata loading fails.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant ProfileEditDrawerComponent
  participant ProfileAPI
  participant SessionStorage
  participant ProfileLayoutComponent
  ProfileEditDrawerComponent->>ProfileAPI: PATCH normalized user_metadata
  ProfileEditDrawerComponent->>SessionStorage: Persist mapped userMetadata on redirect
  SessionStorage->>ProfileLayoutComponent: Restore userMetadata
  ProfileLayoutComponent->>ProfileLayoutComponent: Apply legacy envelope mapping when required
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: allowing users to clear free-text profile fields.
Description check ✅ Passed The description directly explains the free-text clearing behavior, affected save paths, conditions, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-2933-clear-fields

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Enables users to clear free-text profile fields by sending explicit empty strings.

Changes:

  • Preserves empty values in direct and Flow C saves.
  • Adds E2E coverage for clearing a bio.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
profile-edit-drawer.component.ts Sends raw free-text values.
profile-layout.component.ts Preserves empty values during Flow C replay.
profile-edit-drawer.spec.ts Tests clearing and optimistic UI updates.

Address PR #1301 review feedback from cursor[bot], copilot[bot]:

- profile-edit-drawer.component.ts / profile-layout.component.ts: only
  send free-text fields as raw '' when the profile metadata loaded. On a
  NATS getUserInfo miss the GET returns 200 with profile: null and the
  controls seed empty, so an unguarded save would wipe unloaded bio,
  job_title, address, and phone data. Fall back to omitting empties when
  profile is null. (per cursor[bot])
- profile-layout.component.ts: narrow the applyOptimisticProfileUpdate
  comment so it no longer claims all empty fields map to undefined; only
  name/select fields and the failed-load case do, and cleared free-text
  fields send ''. (per copilot[bot])

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 3, 2026 23:47
@fayazg

fayazg commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 83bffb6

Changes Made

  • profile-edit-drawer.component.ts / profile-layout.component.ts: Gated clear-to-empty behind loaded profile metadata. When the profile GET returns profile: null (a NATS getUserInfo miss, which the controller surfaces as a 200 with null metadata), the six free-text fields fall back to omitting empties (|| undefined) so an empty-seeded control can't overwrite stored bio/job_title/address/postal_code/phone_number the form never received. Real clears still work whenever the profile loaded. Applied to both the drawer save (onSubmit) and the Flow C replay (handleProfileAuthReturn). (per cursor[bot])
  • profile-layout.component.ts: Narrowed the applyOptimisticProfileUpdate() comment so it no longer claims all empty fields map to undefined — only name/select fields and the failed-load case are omitted; cleared free-text fields send '' and are kept. (per copilot[bot])

Threads Resolved

2 of 2 unresolved threads addressed in this iteration.

Note: the new guard's failed-load path isn't covered by the S5 E2E (it would require stubbing the profile GET to return profile: null); the happy-path clear remains covered.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts:252

  • On the Flow C return, this check runs immediately from the constructor's queryParams subscription, before the cold-load profile GET populates combinedProfile (the existing comment in applyOptimisticProfileUpdate() documents that ordering). As a result, metadataLoaded is false and a saved '' becomes undefined, so the replayed PATCH still cannot clear a free-text field whenever management-token authorization is required. Preserve the original loaded-state in the pending-save envelope or defer replay mapping until the profile load completes.
    const metadataLoaded = this.combinedProfile?.profile != null;
    const freeText = (value: string | null | undefined): string | undefined => (metadataLoaded ? (value ?? '') : value || undefined);

Comment thread apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts (1)

208-228: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the failed metadata-load branch.

The current E2E scenario covers clearing bio after a successful metadata load. It does not verify that empty values are omitted when the metadata lookup fails. Add a regression test for that branch and cover at least one additional clearable field.

According to the PR objective, the existing E2E coverage is limited to bio.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts`
around lines 208 - 228, Add E2E coverage for the failed metadata-load path
represented by metadataLoaded in the profile edit flow, verifying that empty bio
is omitted rather than submitted when profile metadata is unavailable. Also
clear and assert omission of at least one additional free-text field such as
job_title, city, address, postal_code, phone_number, or bio, while preserving
the existing successful-load bio coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts`:
- Around line 208-228: Add E2E coverage for the failed metadata-load path
represented by metadataLoaded in the profile edit flow, verifying that empty bio
is omitted rather than submitted when profile metadata is unavailable. Also
clear and assert omission of at least one additional free-text field such as
job_title, city, address, postal_code, phone_number, or bio, while preserving
the existing successful-load bio coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0116286e-3f13-4a53-9621-92798313cfa1

📥 Commits

Reviewing files that changed from the base of the PR and between 9775459 and 83bffb6.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts
  • apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts

Address Cursor Bugbot review on PR #1301 (LFXV2-2933):

The management-token redirect (Flow C) re-derived the PATCH payload in
ProfileLayoutComponent.handleProfileAuthReturn() from the persisted raw
form, gating clear-to-empty on the live combinedProfile. On a full-page
Flow C return that signal hasn't reloaded yet (still null), so legitimate
cleared free-text fields were dropped instead of sent as ''.

- profile-edit-drawer.component.ts: persist the already-mapped userMetadata
  payload (not the raw form) to sessionStorage at submit time, capturing the
  clear-to-empty decision while the profile is loaded.
- profile-layout.component.ts: replay that payload verbatim on return,
  removing the duplicated mapping and the stale combinedProfile gate.

Resolves 1 review thread.

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 00:01
@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 9227aa6

Changes Made

  • profile-edit-drawer.component.ts: persist the already-mapped userMetadata payload (not the raw form) to sessionStorage at submit time, so the clear-to-empty decision is captured while the profile is loaded (per cursor[bot]).
  • profile-layout.component.ts: replay that payload verbatim on the Flow C profile-auth return, removing the duplicated mapping and the stale combinedProfile gate — cleared free-text fields now correctly send '' through the management-token retry (per cursor[bot]).

Threads Resolved

1 of 1 unresolved thread addressed in this iteration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts:244

  • Changing the pending-save envelope drops valid saves created by a pre-deploy tab. Flow C performs a full navigation, so a user can store the old { savedAt, form } shape, return after a deployment, and hit this new parser; the key has already been removed at line 234, then this returns without replaying the PATCH or showing an error. Accept the old shape for the existing 10-minute TTL and map it with the previous rules, or explicitly version and migrate the envelope.
      const envelope = JSON.parse(savedState) as { savedAt?: unknown; userMetadata?: Partial<UserMetadata> };
      if (typeof envelope?.savedAt !== 'number' || !envelope.userMetadata || Date.now() - envelope.savedAt > ProfileLayoutComponent.pendingSaveTtlMs) {
        return;
      }
      userMetadata = envelope.userMetadata;

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 00:07
@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Comment cleanup

Commit: 26457e7

Trimmed the clear-to-empty explanatory comments in profile-edit-drawer.component.ts and profile-layout.component.ts down to two lines each, per the repo's ≤2-line comment convention. No code behavior change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts:239

  • This hard schema switch drops pending saves created by the previous client version, which stores { savedAt, form }. If a user starts Flow C before a deployment and returns after the new bundle is live, this branch removes the session entry first and then silently returns because userMetadata is absent, so their profile edit is lost. Please accept the legacy envelope during the 10-minute migration window (mapping form with the previous field rules) alongside the new userMetadata envelope.
      const envelope = JSON.parse(savedState) as { savedAt?: unknown; userMetadata?: Partial<UserMetadata> };
      if (typeof envelope?.savedAt !== 'number' || !envelope.userMetadata || Date.now() - envelope.savedAt > ProfileLayoutComponent.pendingSaveTtlMs) {

@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

No Change Needed

  • profile-edit-drawer.component.ts:260: CodeQL "clear text storage of sensitive information" (alert feat(permissions): implement user permissions management system #99) flagged the Flow C pending-save payload. Dismissed as accepted risk — it's the user's own profile data in tab-scoped sessionStorage, written just before the management-token auth redirect and removed on return (profile-layout.component.ts:232). XSS able to read it already holds the user's live session, so it adds no exposure beyond it; the full-page redirect requires client persistence to replay the PATCH, and combinedProfile isn't yet reloaded on return to re-derive it (flagged by github-advanced-security).

Threads Resolved

1 of 1 unresolved thread addressed in this iteration. No code changes.

Copilot AI review requested due to automatic review settings August 4, 2026 01:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

apps/lfx-one/src/app/modules/profile/components/profile-edit-drawer/profile-edit-drawer.component.ts:260

  • The new redirect payload contract is not exercised by S5: its PATCH always succeeds, so neither storing userMetadata nor replaying bio: '' after Flow C is tested. A mismatch between the two envelope keys would silently lose the save. Add coverage where the first PATCH returns management_token_required, the pending state retains the empty string, and the callback replay sends it unchanged.
              // Persist the mapped payload (not the raw form) so the submit-time clear-to-empty decision
              // survives the redirect; the host replays it verbatim (stringify drops undefined keys).
              sessionStorage.setItem(PENDING_PROFILE_SAVE_KEY, JSON.stringify({ savedAt: Date.now(), userMetadata }));

Comment thread apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts Outdated
The Flow C replay parser only accepted the new { savedAt, userMetadata }
envelope. A pre-LFXV2-2933 bundle writes { savedAt, form } (raw form value),
so a save started just before a mid-Flow-C deploy would be silently dropped
on return. Accept the legacy shape during the pending-save TTL window and map
it with the prior omit-empties rules (per copilot-pull-request-reviewer).

Also add E2E S6: with profile metadata unloaded (GET returns no profile),
clearing bio and job_title omits both keys from the PATCH rather than wiping
stored data the form never received (per coderabbitai).

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 04:14
@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 4df86d5

Changes Made

  • profile-layout.component.ts: The Flow C replay parser now accepts both pending-save envelope shapes — the new { savedAt, userMetadata } (replayed verbatim) and the legacy { savedAt, form } written by a pre-2933 bundle, mapped with the prior || undefined omit-empties rules via a new mapLegacyFormEnvelope() helper. This closes the rollout race where a save started just before a mid-Flow-C deploy would be silently dropped on return. The savedAt/TTL guard still discards stale returns for both shapes. (per copilot-pull-request-reviewer)
  • profile-edit-drawer.spec.ts: Added E2E S6 covering the failed-metadata-load branch — the GET is rewritten to return no profile (keeping the real user so the panel renders), then clearing bio and job_title asserts both keys are omitted from the PATCH rather than sent as '' and wiping unloaded data. (per coderabbitai)

Threads Resolved

1 of 1 unresolved thread addressed in this iteration (the legacy-envelope parser thread). The earlier Cursor, CodeQL, and Copilot threads were already resolved.

The legacy-envelope compat is intentionally temporary — removable once no pre-2933 bundle can still be serving the write path (past the pending-save TTL). It's commented as such.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

A failed profile GET left combinedProfile.profile null; the first save's
optimistic merge fabricated a non-null profile, flipping the drawer's
metadataLoaded true on reopen so a later save could send '' and wipe
free-text fields the GET never loaded. On the null-profile path, refetch
instead of merging so the load status stays truthful across save → reopen
→ save (per copilot-pull-request-reviewer).

Add E2E S7: with the profile GET always returning no profile, a first save
then reopen must still omit an empty bio rather than send ''.

Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 04:26
@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: f5cdb47

Changes Made

  • profile-layout.component.ts: Fixed the clear-to-empty gate surviving only the first save on a failed load. applyOptimisticProfileUpdate() was merging saved metadata into a null profile, fabricating a non-null object that flipped the drawer's metadataLoaded true on reopen — so a later save could send '' and wipe free-text fields the GET never loaded. On the null-profile path we now refetch instead of merging, so the load status stays truthful across save → reopen → save. (per copilot-pull-request-reviewer)
  • profile-edit-drawer.spec.ts: Added E2E S7 — with the profile GET always returning no profile, a first job_title save then reopen must still omit an empty bio from the PATCH rather than send ''. This is exactly the save → reopen → save sequence that was requested, and it fails without the layout fix.

Threads Resolved

1 of 1 unresolved thread addressed in this iteration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/lfx-one/src/app/layouts/profile-layout/profile-layout.component.ts:201

  • This immediate refetch contradicts the component's eventual-consistency invariant (lines 79–81 and 269–271). When the initial metadata GET yields profile: null but the PATCH succeeds, the refetch can still return the pre-save/null body, so the success toast is shown while the saved free-text value never appears in the panel or a reopened drawer. Preserve the optimistic update, but keep the original metadata-load status as separate state so reopening cannot enable clear-to-empty until a real metadata response has loaded.
    // A null profile means the GET never loaded; merging would fabricate a non-null profile and flip
    // the drawer's metadataLoaded true, letting a later save wipe unloaded fields. Refetch instead.
    if (this.combinedProfile.profile == null) {
      this.refreshProfile$.next();
      return;

@fayazg

fayazg commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

No Change Needed

  • profile-layout.component.ts:197 — "Flow C clears can restore fields" (flagged by cursor): partially valid but not a data-loss bug. On the Flow C cold return, if the replay PATCH resolves before the initial profile GET populates combinedProfile, the fallback refetch is eventually consistent and can transiently re-seed the pre-clear value. The PATCH has already persisted the clear, so the server value is authoritative and the view self-heals on the next consistent GET; it's also a pre-existing property of the cold-return refetch fallback (non-clear edits hit the same path), only widened here to cleared fields. A fully race-free read-your-writes fix is non-trivial (two eventually-consistent GETs race to set combinedProfile), so it's tracked as a follow-up to harden the cold-return path rather than rushed into this PR.

Threads Resolved

1 of 1 unresolved thread addressed in this iteration. No code changes.

@dealako

dealako commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hey @fayazg — reviewed the current state of this PR (head f5cdb477b).

Overall impression: This is a well-scoped fix for a real usability gap (free-text profile fields couldn't be cleared to empty because value || undefined silently dropped them from the PATCH). The implementation correctly gates the new clear-to-empty behavior behind metadataLoaded (combinedProfile.profile != null) so it only activates once the profile GET has actually succeeded — this is the right call, since applying it unconditionally would let a failed/partial load wipe fields the user never touched. The Flow C (management-token redirect) path mirrors the same logic by persisting the already-mapped payload rather than re-deriving it from raw form values on return, which avoids a second, inconsistent mapping pass.

This PR has already been through multiple rounds of bot review (Copilot, cursor[bot], CodeQL) and the author has iterated commit-by-commit to address each one. I traced each of those through to the current code rather than re-litigating them:

  • Copilot — "load-failure guard only survives the first save": confirmed fixed in f5cdb477b. applyOptimisticProfileUpdate() now bails to a refetch both when combinedProfile is null and when combinedProfile.profile is null, so a merge can no longer fabricate a non-null profile object that would incorrectly flip metadataLoaded to true on the next drawer open.
  • cursor[bot] — "Empty saves can wipe unloaded fields": confirmed fixed in 83bffb67 via the metadataLoaded gate in the drawer's onSubmit().
  • cursor[bot] — "Flow C drops cleared fields": confirmed fixed in 9227aa6 — the redirect path now persists the mapped userMetadata payload instead of re-deriving it from raw form values after the auth round-trip.
  • Copilot — "legacy envelope shape no longer accepted": confirmed fixed in 4df86d58 via mapLegacyFormEnvelope(), which handles the old { savedAt, form } shape written by a pre-deploy bundle during a rolling-deploy window, alongside the new { savedAt, userMetadata } shape.
  • CodeQL — clear-text storage of phone_number in sessionStorage: agree with the author's dismissal — this is the user's own data, written to a tab-scoped sessionStorage key, and removed immediately on return. Not a meaningful exposure.
  • cursor[bot] — "Flow C clears can restore fields" (cold-load race): I agree this is a real but narrow window — if the auth-return PATCH resolves before the initial profile GET populates combinedProfile, applyOptimisticProfileUpdate correctly falls back to a refetch (since combinedProfile is still null), so the eventually-consistent GET can transiently redisplay pre-clear values until the next refresh completes. This is a display staleness issue, not data loss — the PATCH itself already persisted. Agree with the author's "not a data-loss bug" call; not blocking.

I didn't find anything beyond what's already been raised and resolved. Server-side validation (validateUserMetadata) already treats '' as valid input via its existing falsy checks, so no backend change was required, consistent with the PR description. New E2E coverage (S5/S6/S7) directly targets the three behaviors that matter most: clearing to empty succeeds and hides the About Me panel, a failed load doesn't let a clear wipe untouched fields, and a first successful save doesn't fabricate state that would enable a later wipe.

Issue count:

  • 🔴 Blocking: 0
  • 🟡 Minor: 0
  • ⚪ Nit: 0
  • ❔ Question: 0

Final decision: ✅ Approved

@dealako dealako left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved — see the summary comment for full reconciliation against prior review rounds. No blocking, minor, or open questions found in an independent pass; the metadataLoaded gate and Flow C persistence both hold up under tracing.

Copilot AI review requested due to automatic review settings August 4, 2026 16:02
@fayazg
fayazg merged commit 6c60a31 into main Aug 4, 2026
10 of 11 checks passed
@fayazg
fayazg deleted the feat/LFXV2-2933-clear-fields branch August 4, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit d341ce6. Configure here.

sessionStorage.setItem(PENDING_PROFILE_SAVE_KEY, JSON.stringify({ savedAt: Date.now(), form: this.profileForm.value }));
// Persist the mapped payload (not the raw form) so the submit-time clear-to-empty decision
// survives the redirect; the host replays it verbatim (stringify drops undefined keys).
sessionStorage.setItem(PENDING_PROFILE_SAVE_KEY, JSON.stringify({ savedAt: Date.now(), userMetadata }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Flow C envelope rollout gap

Medium Severity

The pending-save format changed from { savedAt, form } to { savedAt, userMetadata }, and the host only accepts the reverse (legacy write → new read). A Flow C save started on the new bundle can still return onto an old bundle mid-rollout; that reader requires form, removes the session entry first, then bails — so the replay is silently dropped.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d341ce6. Configure here.

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.

4 participants