Skip to content

feat(mosaic): Wire up password section - #9930

Merged
austincalvelage merged 93 commits into
mainfrom
austin/password-section-wire-up
Oct 1, 2026
Merged

austincalvelage merged 93 commits into
mainfrom
austin/password-section-wire-up

Conversation

@austincalvelage

@austincalvelage austincalvelage commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Description

Wire the Mosaic password section to Clerk so users can set or change their password, with validation, API errors, pending state, and the option to sign out other sessions. Changing an existing password requires the current password. Users with an active enterprise account cannot set or change a password, matching legacy behavior. Add a Swingset live page at /live/password.

Session reverification UI is deferred. Requests that require reverification surface the API error.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Oct 1, 2026 9:57pm UTC
swingset Ready Ready Preview Oct 1, 2026 9:57pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e664a41

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Team

Run ID: b0422d90-c708-49db-b613-bcefdc003fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 4251d27 and 52e3837.

📒 Files selected for processing (4)
  • packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-feedback.test.ts
  • packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-feedback.ts
  • packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.model.ts
  • packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

Included review availability: This review used your included allowance. 9 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.


📝 Walkthrough

Walkthrough

Adds password policy and validation to Mosaic, including password-strength feedback and localized error handling. Adds a password section with asynchronous validation and update handling, and supplies it to the security panel through a slot. Adds a live password page and navigation entry. Updates feature tests, test fixtures, and field feedback support.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • clerk/javascript#9750: Adds the Mosaic password-section UI, controller, dialog, public types, and stories that this PR connects to Clerk’s password model and update flow.

Merge Risk: ⚪ Minimal · up to 52e38

No new merge-blocking issue is established for these changes. Normal checks and confirmation of the previously reported password-validation concern remain appropriate before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 31 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: wiring the Mosaic password section to Clerk.
Description check ✅ Passed The description directly covers password setting and changing, validation, API errors, pending state, enterprise-account behavior, and the new live page.
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 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch austin/password-section-wire-up
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from fe5b987 to 677cc0d Compare September 24, 2026 22:16
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 677cc0d to 0d3bec2 Compare September 24, 2026 22:20
@vercel
vercel Bot temporarily deployed to Preview – clerk-js-sandbox September 24, 2026 22:31 Inactive
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 114a51b to 805bf8c Compare September 24, 2026 22:32
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 805bf8c to 9af5b5a Compare September 24, 2026 22:43
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 9af5b5a to 1cf177f Compare September 24, 2026 22:45
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 1cf177f to 8bafdd9 Compare September 24, 2026 22:58
@austincalvelage
austincalvelage force-pushed the austin/password-section-wire-up branch from 8bafdd9 to d6de0b0 Compare September 24, 2026 23:23
@vercel
vercel Bot temporarily deployed to Preview – clerk-js-sandbox September 25, 2026 15:42 Inactive

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adds variant to mirror legacy:

Legacy:
Image

Mosiac:
Image

@Ephem Ephem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work! I focused mostly on the wrapper+model+controller patterns, not so much the rest of it or the parity with the existing flow.

return useUserProfilePasswordSlot(props)?.content ?? null;
}

export function useUserProfilePasswordSlot({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I get this is a hook to get a typed UserProfilePasswordSlot to work around the fact children can't be typed, but why?

I think this is unnecessary complexity, if we truly want this level of type safety, we should rewrite all our components to be hooks instead, which feels like a different framework. 😅

const model = useUserProfilePasswordModel();
const m = useMessages('userProfilePasswordSection');
if (model.status === 'loading') {
// TODO: Add a password section skeleton as the default loading fallback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note that there is a tradeoff here. If we do add a default skeleton and it turns out that instanceIsPasswordBased === false, the skeleton would disappear to nothing causing a layout shift.

If we don't have a skeleton by default, it would be a layout shift when this section pops in.

};
}

function PasswordEditor({ model }: { model: Extract<UserProfilePasswordModel, { status: 'ready' }> }) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this approach of putting the controller in a separate component so it always only gets the 'ready' version and can skip a ton of complexity. Wont always be possible, but very nice when it is.


return (
<UserProfilePasswordSectionView
hasPassword={model.mode === 'change'}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm curious if using model directly here was a conscious decision or agent-led?

The way we've used model+controller so far has been to re-export any parts of the model we need on the controller so the view only relies on the controller. That way we don't have to reason about the interaction between model and controller when looking at the components and we have one very clear interface and source of truth. Makes refactoring things easier in the future too I think.

Plus, that's a clean story when we want to reuse the controller for swingset too. Mock the things you pass into the controller, pass the controller result to the view.

Happy for pushback if we think that is unnecessarily cumbersome on small components though, this is just how I've been thinking about it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This mostly overlaps with the .feature. tests and I think we should remove it. Two tests should likely be moved into the feature tests before removing:

  • API error becomes a field error (form_password_incorrect on currentPassword)
  • Enterprise accounts: blank name becomes undefined

Comment on lines +69 to +97
useEffect(() => {
// TODO: Discuss keeping the password hint hidden on open or showing it immediately when the field autofocuses. https://github.com/clerk/javascript/pull/9930#discussion_r4150863181
if (!isOpen || (password === '' && !passwordLeft) || !validatePassword) {
setPasswordFeedback(undefined);
return;
}

let active = true;
const timeout = setTimeout(() => {
void Promise.resolve()
.then(() => validatePassword(password))
.then(
feedback => {
if (active) {
setPasswordFeedback(feedback);
}
},
() => {
if (active) {
setPasswordFeedback({ type: 'error', message: validationError });
}
},
);
}, DEBOUNCE_MS);
return () => {
active = false;
clearTimeout(timeout);
};
}, [isOpen, password, passwordLeft, validatePassword, validationError]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@alexcarpenter This looks like a good candidate for something to support in the form abstraction?

@alexcarpenter alexcarpenter Oct 5, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah, took a stab at it here #10065

kept it out of the form though: the hint never blocks submit, so it's not validation. It's now a reusable useDebouncedAsync hook instead of the effect. TanStack Form and RHF keep non-blocking hints out of their validators too.

This branch was successfully deployed

2 active deployments
Preview – swingset — e664a414 Deployed Oct 1, 2026 by vercel[bot]
Preview – clerk-js-sandbox — e664a414 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants