Skip to content

feat(ui): add mosaic block/section/aio prototype layers#8838

Open
alexcarpenter wants to merge 27 commits into
mainfrom
carp/mosaic-block-destructive
Open

feat(ui): add mosaic block/section/aio prototype layers#8838
alexcarpenter wants to merge 27 commits into
mainfrom
carp/mosaic-block-destructive

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds three new component hierarchy layers under packages/ui/src/mosaic/:
    • block/destructive.tsx — controlled confirmation dialog composed from Dialog + Input + Button; caller owns open/deleting state; input must match resource name to enable the action
    • section/leave-organization.tsx — owns open / isDeleting state, wires Destructive to the leave-organization flow
    • aio/organization-profile.tsx — assembles organization sections into a full-page view
  • Adds swingset stories for all three under new AIO, Sections, and Blocks sidebar groups
  • Fixes slug.ts to treat consecutive uppercase letters as acronyms (AIOaio not a-i-o)

https://swingset-git-carp-mosaic-block-destructive.clerkstage.dev/sections/delete-organization

Test plan

  • Visit localhost:6006/aio/organization-profile — page renders with a "Leave organization" heading and trigger button
  • Visit localhost:6006/sections/leave-organization — same section in isolation
  • Visit localhost:6006/blocks/destructive — bare block with controlled state in the story
  • Open the dialog, verify submit button is disabled until the resource name is typed exactly
  • Confirm input resets after closing the dialog without confirming
  • turbo build --filter=@clerk/ui passes with no type errors

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Organization Profile feature with tabbed interface and General tab
    • Added Leave Organization confirmation flow
    • Added Delete Organization with secure confirmation dialog
    • Introduced Destructive action confirmation UI pattern requiring typed confirmation
    • Added Tabs component with full styling and keyboard navigation
    • Added Box and Skeleton UI components for layouts and loading states
  • Documentation

    • Added comprehensive Tabs component documentation with ARIA behavior and usage examples

Replaces the emotionCache.insert monkey-patch with a useInsertionEffect
that rewrites <style> textContent after emotion injects but before paint,
avoiding the need to intercept the internal insert API.
Slot identity now lives in the mosaic styled layer. The dialog primitives
keep their runtime state attrs (data-cl-open, data-cl-starting-style, …)
but no longer emit data-cl-slot; tests locate parts via role/text/testid.
Replace the cva/styled Dialog with a multi-slot dialogRecipe that owns slot
identity and base styles. Each part reads its slot via useRecipe and spreads
it onto the bridged headless primitive through the new withMosaicSlot bridge.
Removes the obsolete styled/types/withMosaicTheme cva helpers.
The insertion effect rewrote emotion's <style> textContent to wrap rules in
@layer, which desynced from emotion's rule bookkeeping and dropped component
styles on client navigation. Reverting to normal emotion insertion; the
@layer/cssLayerName ordering will be revisited separately.
Adds a styled mosaic Dialog story (Components) that uses a Button as the
trigger via the render prop. Routes are now group-aware (/components/<c>
and /primitives/<c>) so the styled Dialog and the headless Dialog primitive
no longer collide on a title-only slug.
Introduces three new component hierarchy layers under packages/ui/src/mosaic/:

- block/destructive.tsx — controlled confirmation dialog (trigger + input guard + action)
- section/leave-organization.tsx — owns open/deleting state, wires Destructive to leave flow
- aio/organization-profile.tsx — assembles organization sections into a full-page view

Also adds swingset stories for all three (Blocks / Sections / AIO sidebar groups) and fixes
slug.ts to treat consecutive uppercase letters as acronyms (AIO → aio, not a-i-o).
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 12, 2026 8:02pm
swingset Ready Ready Preview, Comment Jun 12, 2026 8:02pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3907704

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

@github-actions github-actions Bot added the ui label Jun 11, 2026
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 540d2af5-c491-42ff-bc86-1246bcc1b12d

📥 Commits

Reviewing files that changed from the base of the PR and between 541c4e6 and 3907704.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/swingset/src/lib/registry.ts
  • packages/ui/package.json
  • packages/ui/src/mosaic/components/button.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/package.json

📝 Walkthrough

Walkthrough

This PR adds an organization profile feature with destructive action flows (delete organization, leave organization), group-aware docs navigation in Swingset, and Mosaic Tabs styled components. It expands design tokens, introduces Box and Skeleton UI foundations, and provides comprehensive story coverage for the new features.

Changes

Organization Profile UI Feature

Layer / File(s) Summary
Design tokens, styling foundations, and Button variant
packages/ui/src/mosaic/variables.ts, packages/ui/src/mosaic/components/button.tsx, packages/ui/src/mosaic/primitives/box.tsx
Mosaic tokens expand with destructive/muted colors and font weights; Button gains a destructive color variant; Box headless primitive is introduced for polymorphic div rendering.
UI component foundation: Box, Skeleton, SectionSkeleton
packages/ui/src/mosaic/components/box.tsx, packages/ui/src/mosaic/components/skeleton.tsx, packages/ui/src/mosaic/components/section-skeleton.tsx
Box wrapper adds theme-resolved sx prop; Skeleton renders animated loading bars per line; SectionSkeleton mimics org-profile layout with responsive grid.
Mock organization state and useOrganization hook
packages/ui/src/mosaic/mock/organization-store.ts, packages/ui/src/mosaic/mock/use-organization.tsx, packages/ui/package.json
Alien-signals dependency added; signal-backed org-load state; mock organization/membership objects with async destroy() methods.
Destructive confirmation block
packages/ui/src/mosaic/block/destructive.tsx
Dialog requiring typed resource-name matching before enabling delete button; manages confirmation state and submission.
DeleteOrganization and LeaveOrganization sections
packages/ui/src/mosaic/sections/delete-organization.tsx, packages/ui/src/mosaic/sections/leave-organization.tsx
Destructive action sections with loading skeleton fallback and async org/membership destroy calls via Destructive block.
OrganizationProfileGeneral and OrganizationProfile AIO
packages/ui/src/mosaic/panels/organization-profile-general.tsx, packages/ui/src/mosaic/aio/organization-profile.tsx
Panel combines leave and delete sections with divider; AIO wraps panel in tabbed General/Members layout.

Mosaic Tabs Components and Group-Aware Docs Navigation

Layer / File(s) Summary
Headless tabs inert attribute React version compatibility
packages/headless/src/primitives/tabs/tabs-panel.tsx
Adds React version detection and inertProps helper to conditionally render inert as boolean (modern React) or string (older versions).
Mosaic Tabs styled recipe and slot wrappers
packages/ui/src/mosaic/components/tabs.tsx
Defines tabsRecipe with multi-slot styling; wraps each primitive sub-component with forwardRef to apply slot props.
Tabs primitives bridge
packages/ui/src/mosaic/primitives/tabs.tsx
Re-exports @clerk/headless/tabs Root and wraps List/Tab/Trigger/Panel/Indicator with withMosaicSlot for ref forwarding and slot prop application.
Registry group-aware lookup and slug refinement
packages/swingset/src/lib/registry.ts, packages/swingset/src/lib/slug.ts, packages/swingset/src/lib/types.ts
Registry replaces slug-only lookup with getModule(groupSlug, componentSlug); updates getSidebarGroups to include groupSlug; adds optional label to StoryMeta; refines toSlug camelCase conversion.
DocsViewer and AppSidebar group-aware consumers
packages/swingset/src/components/DocsViewer.tsx, packages/swingset/src/components/app-sidebar.tsx
DocsViewer resolves docs via two-level group/slug map; AppSidebar generates grouped hrefs and uses label fallback; docs registry reorganized into two-level structure.
Composition panel and StoryEmbed collapsible integration
packages/swingset/src/components/Composition.tsx, packages/swingset/src/components/StoryEmbed.tsx
Composition replaces implementation with layer grouping and name sorting; StoryEmbed adds optional collapsible composition footer controlled by compositionOpen state.
Webpack alias and configuration
packages/swingset/next.config.mjs
Adds more specific @clerk/headless/utils webpack alias before broader @clerk/headless for module resolution precedence.

Story and Documentation Content

Layer / File(s) Summary
Destructive block and LeaveOrganization section stories
packages/swingset/src/stories/destructive.mdx, packages/swingset/src/stories/destructive.stories.tsx, packages/swingset/src/stories/leave-organization.mdx, packages/swingset/src/stories/leave-organization.stories.tsx
Adds MDX and story files for destructive block (with triggered dialog and simulated delete) and leave-organization section render.
DeleteOrganization and OrganizationProfile stories
packages/swingset/src/stories/delete-organization.mdx, packages/swingset/src/stories/delete-organization.stories.tsx, packages/swingset/src/stories/organization-profile.mdx, packages/swingset/src/stories/organization-profile.stories.tsx, packages/swingset/src/stories/organization-profile-general.mdx, packages/swingset/src/stories/organization-profile-general.stories.tsx
Adds delete-organization section story and organization-profile page stories (AIO and general panel variant) with composition wiring.
Tabs component documentation and story
packages/swingset/src/stories/tabs.component.mdx, packages/swingset/src/stories/tabs.component.stories.tsx
Comprehensive Tabs component docs covering ARIA/keyboard behavior, usage example, parts/slot reference, and styling guidance; multi-tab story with indicator.
Changesets and dependencies
.changeset/calm-aliens-care.md, .changeset/delete-org-block.md
Release metadata for changeset entries; alien-signals dependency for signal-backed mock state.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • clerk/javascript#8797: Initial Swingset scaffold with registry and slug-based docs resolution that this PR extends to group-aware routing.
  • clerk/javascript#8812: Concurrently updates packages/swingset/src/components/app-sidebar.tsx with similar navigation changes for group-aware sidebar organization.
  • clerk/javascript#8830: Extends Mosaic token and styling with additional design tokens built on the slot-recipe foundations.

Suggested reviewers

  • prestonwebdev
  • brkalow
  • Ephem

Poem

🐰 A profile emerges from the shadows,
Destructive flows now bend to our will,
Tabs march in formation, slots all aglow,
Groups navigate where slugs once stood still,
The mosaic blooms with colors and care!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding new component layers (block, section, aio) to the Mosaic prototype under packages/ui/src/mosaic/.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@vercel vercel Bot temporarily deployed to Preview – clerk-js-sandbox June 11, 2026 21:31 Inactive
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-12T20:04:20.724Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

Note
Break Check could not snapshot 1 subpath; the diff below excludes them.

  • @clerk/astro ./env: ambient declaration file (no top-level import or export): API Extractor can only analyze module entry points, so this global-augmentation surface cannot be snapshotted; add the subpath to ignoreSubpaths to acknowledge it (API Extractor: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts)

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on d4ba05b.

#8839)

Co-authored-by: Alex Carpenter <alex.carpenter@clerk.dev>
Only mosaic source (consumed by swingset from source) uses alien-signals;
it never reaches the @clerk/ui build output, so it shouldn't ship in
consumers' transitive install graph.
…tructive

# Conflicts:
#	packages/swingset/src/components/DocsViewer.tsx
#	packages/swingset/src/lib/registry.ts
#	packages/ui/src/mosaic/components/button.tsx
#	packages/ui/src/mosaic/components/dialog.tsx
#	pnpm-lock.yaml

@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.

Actionable comments posted: 9

🧹 Nitpick comments (5)
packages/swingset/src/stories/leave-organization.stories.tsx (1)

13-15: ⚡ Quick win

Add explicit return types to the exported story functions.

These Default exports are public story entry points, and as per coding guidelines they should declare explicit return types. This keeps the new story modules consistent and avoids inference drift.

🤖 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 `@packages/swingset/src/stories/leave-organization.stories.tsx` around lines 13
- 15, Exported story function Default lacks an explicit return type; update the
Default function declaration to include an explicit React element return type
(e.g., React.ReactElement or JSX.Element) so the exported story's signature is
explicit—locate the Default function that returns LeaveOrganization and add the
return type to its declaration.

Source: Coding guidelines

packages/swingset/src/stories/delete-organization.stories.tsx (1)

13-15: ⚡ Quick win

Unify story-function signatures across delete-organization.stories.tsx, destructive.stories.tsx, and dialog.component.stories.tsx.

All three files export Default story functions without the package’s expected Record<string, unknown> args contract. Please align each Default signature to the same knobs-compatible pattern used in existing Swingset stories to keep story registration and args handling consistent.

As per coding guidelines, packages/swingset/**/*.stories.{ts,tsx} story functions must take Record<string, unknown> and cast to the real prop type.

🤖 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 `@packages/swingset/src/stories/delete-organization.stories.tsx` around lines
13 - 15, The Default story function currently has no args parameter; update its
signature to accept args: Record<string, unknown> (matching other Swingset
stories) and cast args to the component's real prop type before passing to
DeleteOrganization, e.g. change function Default() to Default(args:
Record<string, unknown>) and use the casted props when rendering; apply the same
pattern in the other two files (destructive.stories.tsx and
dialog.component.stories.tsx) so all Default exports follow the knobs-compatible
args contract.

Source: Coding guidelines

packages/swingset/src/components/DocsViewer.tsx (1)

3-3: ⚡ Quick win

Add an explicit return type for DocsViewer and switch to type-only React imports.

This exported API currently relies on inferred return typing and ambient React namespace types.

♻️ Proposed update
+import type { ComponentType, JSX } from 'react';
 import dynamic from 'next/dynamic';
 
 import { getModule } from '`@/lib/registry`';
@@
-const docModules: Record<string, Record<string, React.ComponentType>> = {
+const docModules: Record<string, Record<string, ComponentType>> = {
@@
-export function DocsViewer({ group, slug }: DocsViewerProps) {
+export function DocsViewer({ group, slug }: DocsViewerProps): JSX.Element {

As per coding guidelines, "Always define explicit return types for functions, especially public APIs" and "Use type-only imports ... where possible." Based on learnings, exported TypeScript APIs in this repository should keep explicit return type annotations.

Also applies to: 12-12, 51-51

🤖 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 `@packages/swingset/src/components/DocsViewer.tsx` at line 3, Add an explicit
return type to the exported DocsViewer component and switch React imports to
type-only; specifically, change any "import React" (or implicit ambient React
usage) to "import type React from 'react'" or "import type { ReactElement } from
'react'" and annotate the DocsViewer signature (export default function
DocsViewer(): React.ReactElement or JSX.Element) so the public API has an
explicit return type; update any other functions in this file with the same
pattern (the other DocsViewer-related occurrences) to use type-only imports and
explicit return annotations.

Sources: Coding guidelines, Learnings

packages/ui/src/mosaic/slot-recipe.ts (1)

303-318: 💤 Low value

Duplicate stateToAttrs and kebab helpers exist in useSlot.ts.

The stateToAttrs function (lines 303-315) and the kebab helper (lines 317-318) are duplicated in useSlot.ts (lines 51-61). Consider extracting these to a shared internal utility module to avoid drift between implementations.

🤖 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 `@packages/ui/src/mosaic/slot-recipe.ts` around lines 303 - 318, Duplicate
helpers stateToAttrs and kebab are defined in two places; extract them into a
single internal utility module (e.g., create a new file exporting kebab and
stateToAttrs), replace the duplicate implementations by importing those
functions in both slot-recipe.ts and useSlot.ts, remove the local definitions,
and update the import statements where stateToAttrs and kebab are used to
reference the new shared utility.
packages/ui/src/mosaic/components/__tests__/button.test.tsx (1)

10-15: ⚡ Quick win

Scope CSS scraping to Mosaic Emotion tags to avoid cross-test style bleed.

insertedStyles() currently reads every <style> element, so assertions can become flaky when unrelated styles are present. Restricting to the Mosaic Emotion cache key keeps these tests deterministic.

Suggested patch
 function insertedStyles(): string {
-  return Array.from(document.querySelectorAll('style'))
+  return Array.from(document.querySelectorAll('style[data-emotion^="cl-mosaic"]'))
     .map(el => el.textContent ?? '')
     .join('')
     .replace(/\s+/g, '');
 }
🤖 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 `@packages/ui/src/mosaic/components/__tests__/button.test.tsx` around lines 10
- 15, The test helper insertedStyles() is reading all <style> tags which allows
unrelated styles to leak into assertions; update insertedStyles() to only
collect styles produced by Mosaic's Emotion cache by selecting style elements
with the Emotion cache key (e.g.
querySelectorAll('style[data-emotion^="mosaic"]') or similar), then
map/textContent/join/replace as before so only Mosaic-specific styles are
returned.
🤖 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.

Inline comments:
In @.changeset/calm-aliens-care.md:
- Around line 1-2: The three empty changeset files (including
.changeset/calm-aliens-care.md) must be populated: either add a proper changeset
entry listing the package(s) to bump and a short summary of the user-facing
change (e.g., package-name: minor/patch/major + one-sentence description) so the
release tooling will create changelog/version bumps, or explicitly mark the
change as intentionally deferred by adding a clear note in the file (e.g.,
"RELEASE DEFERRED" or equivalent project-standard defer flag) so reviewers know
no release is expected; update each empty changeset file accordingly.

In `@packages/swingset/src/components/StoryEmbed.tsx`:
- Around line 41-43: The check in StoryEmbed that currently uses if
(!composition) doesn't treat an empty array as "no composition" and causes an
empty Composition footer to render; update the conditional in the StoryEmbed
render path (where composition is inspected) to guard on length (e.g., if
(!composition || composition.length === 0) or if (!(composition?.length))) so
empty arrays are treated as absent and the preview branch is returned instead.

In `@packages/ui/src/mosaic/aio/organization-profile.tsx`:
- Line 5: Add an explicit return type to the exported component function: change
the signature of OrganizationProfile to include ": JSX.Element" (i.e., export
function OrganizationProfile(): JSX.Element { ... }) so it matches the repo's
exported-component typing pattern; update the OrganizationProfile function
declaration accordingly.

In `@packages/ui/src/mosaic/components/button.tsx`:
- Around line 59-69: The Button component spreads {...rest} before {...root},
causing root.className to overwrite a caller-provided className; update
MosaicButton (the React.forwardRef function using useRecipe and the returned
root) to merge class names instead of overwriting them—combine rest.className
(or props.className) with root.className (e.g., via a utility like clsx or
string concat) and pass the merged className to the button so caller className
is preserved while keeping recipe classes from root.

In `@packages/ui/src/mosaic/components/dialog.tsx`:
- Around line 96-107: The Dialog trigger spreads recipe props after user props
so recipe values override consumer-provided styles; in the DialogTrigger inside
Trigger (React.forwardRef) adjust the spread order or merge style-related props
so user values are preserved—specifically, call <Primitive.Trigger> with
{...trigger} spread first and then {...props} or explicitly merge className and
css from props and trigger (from useRecipe(dialogRecipe)) so consumers can
extend/override styles while still applying recipe defaults.
- Around line 100-104: The props spread order in dialog.tsx (Primitive.Trigger
with {...props} {...trigger}) and similarly in input.tsx allows recipe-generated
slotProps/trigger to overwrite user-provided props; change the merge strategy so
user props take precedence and recipe props augment them: either spread
slotProps first then props (e.g., {...slotProps} {...props}) or explicitly
merge/concat className and css (and merge data-* attributes) so user values are
preserved (refer to Primitive.Trigger, the trigger/slotProps objects, and the
props parameter); if the design intends recipe override, add documentation
stating that appearance API wins instead of changing code.

In `@packages/ui/src/mosaic/mock/organization-store.ts`:
- Line 33: The exported delay helper currently lacks an explicit return type;
update the function signature for delay to include the explicit return type
Promise<void> (i.e., export const delay = (ms: number): Promise<void> => ...) so
it matches the repo TypeScript API guidelines and ensures callers see the
correct promise type; adjust any related type imports if necessary and run
typecheck to verify.

In `@packages/ui/src/mosaic/sections/delete-organization.tsx`:
- Around line 18-23: The delete handler (handleDelete) currently sets
setIsDeleting(true) then awaits organization.destroy() but if the promise
rejects the subsequent setIsDeleting(false) and dialog close (setOpen(false))
are skipped; wrap the await in a try/finally so setIsDeleting(false) always
runs, and only call setOpen(false) on success (e.g., after await returns
successfully inside try) — apply the same fix for the analogous handleDelete in
leave-organization.tsx; locate references to handleDelete, organization.destroy,
setIsDeleting and setOpen when making the change.

In `@references/mosaic-architecture.md`:
- Line 9: Update the paragraph to clarify that the `data-cl-slot` attribute is
owned and emitted by Mosaic (not `@clerk/headless`); state and variant
attributes (`data-cl-<state>`, `data-cl-<variant>`) remain the public styling
contract, and components continue to be authored with slot recipes via
`defineSlotRecipe` which manage variants, slot identity, state→attribute
mapping, and the appearance cascade. Replace the current claim that
`data-cl-slot` ships with `@clerk/headless` with this corrected ownership and
keep the rest of the sentence about no classname derivation, no `__state`
concatenation, and no central appearance-key registry intact.

---

Nitpick comments:
In `@packages/swingset/src/components/DocsViewer.tsx`:
- Line 3: Add an explicit return type to the exported DocsViewer component and
switch React imports to type-only; specifically, change any "import React" (or
implicit ambient React usage) to "import type React from 'react'" or "import
type { ReactElement } from 'react'" and annotate the DocsViewer signature
(export default function DocsViewer(): React.ReactElement or JSX.Element) so the
public API has an explicit return type; update any other functions in this file
with the same pattern (the other DocsViewer-related occurrences) to use
type-only imports and explicit return annotations.

In `@packages/swingset/src/stories/delete-organization.stories.tsx`:
- Around line 13-15: The Default story function currently has no args parameter;
update its signature to accept args: Record<string, unknown> (matching other
Swingset stories) and cast args to the component's real prop type before passing
to DeleteOrganization, e.g. change function Default() to Default(args:
Record<string, unknown>) and use the casted props when rendering; apply the same
pattern in the other two files (destructive.stories.tsx and
dialog.component.stories.tsx) so all Default exports follow the knobs-compatible
args contract.

In `@packages/swingset/src/stories/leave-organization.stories.tsx`:
- Around line 13-15: Exported story function Default lacks an explicit return
type; update the Default function declaration to include an explicit React
element return type (e.g., React.ReactElement or JSX.Element) so the exported
story's signature is explicit—locate the Default function that returns
LeaveOrganization and add the return type to its declaration.

In `@packages/ui/src/mosaic/components/__tests__/button.test.tsx`:
- Around line 10-15: The test helper insertedStyles() is reading all <style>
tags which allows unrelated styles to leak into assertions; update
insertedStyles() to only collect styles produced by Mosaic's Emotion cache by
selecting style elements with the Emotion cache key (e.g.
querySelectorAll('style[data-emotion^="mosaic"]') or similar), then
map/textContent/join/replace as before so only Mosaic-specific styles are
returned.

In `@packages/ui/src/mosaic/slot-recipe.ts`:
- Around line 303-318: Duplicate helpers stateToAttrs and kebab are defined in
two places; extract them into a single internal utility module (e.g., create a
new file exporting kebab and stateToAttrs), replace the duplicate
implementations by importing those functions in both slot-recipe.ts and
useSlot.ts, remove the local definitions, and update the import statements where
stateToAttrs and kebab are used to reference the new shared utility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: d349c294-65fa-4be1-b8bf-d2c0369a323c

📥 Commits

Reviewing files that changed from the base of the PR and between 5a28ac1 and 541c4e6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (82)
  • .changeset/calm-aliens-care.md
  • .changeset/delete-org-block.md
  • .changeset/mosaic-slot-recipes.md
  • packages/headless/src/primitives/dialog/README.md
  • packages/headless/src/primitives/dialog/dialog-backdrop.tsx
  • packages/headless/src/primitives/dialog/dialog-close.tsx
  • packages/headless/src/primitives/dialog/dialog-description.tsx
  • packages/headless/src/primitives/dialog/dialog-popup.tsx
  • packages/headless/src/primitives/dialog/dialog-title.tsx
  • packages/headless/src/primitives/dialog/dialog-trigger.tsx
  • packages/headless/src/primitives/dialog/dialog-viewport.tsx
  • packages/headless/src/primitives/dialog/dialog.test.tsx
  • packages/headless/src/primitives/tabs/tabs-panel.tsx
  • packages/swingset/next.config.mjs
  • packages/swingset/src/app/[group]/[component]/page.tsx
  • packages/swingset/src/app/components/[component]/page.tsx
  • packages/swingset/src/components/ClientRoot.tsx
  • packages/swingset/src/components/Composition.tsx
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/components/StoryEmbed.tsx
  • packages/swingset/src/components/StoryPreview.tsx
  • packages/swingset/src/components/app-sidebar.tsx
  • packages/swingset/src/components/ui/sidebar.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/lib/slug.ts
  • packages/swingset/src/lib/types.ts
  • packages/swingset/src/stories/button.stories.tsx
  • packages/swingset/src/stories/delete-organization.mdx
  • packages/swingset/src/stories/delete-organization.stories.tsx
  • packages/swingset/src/stories/destructive.mdx
  • packages/swingset/src/stories/destructive.stories.tsx
  • packages/swingset/src/stories/dialog.component.mdx
  • packages/swingset/src/stories/dialog.component.stories.tsx
  • packages/swingset/src/stories/input.stories.tsx
  • packages/swingset/src/stories/leave-organization.mdx
  • packages/swingset/src/stories/leave-organization.stories.tsx
  • packages/swingset/src/stories/organization-profile-general.mdx
  • packages/swingset/src/stories/organization-profile-general.stories.tsx
  • packages/swingset/src/stories/organization-profile.mdx
  • packages/swingset/src/stories/organization-profile.stories.tsx
  • packages/swingset/src/stories/tabs.component.mdx
  • packages/swingset/src/stories/tabs.component.stories.tsx
  • packages/ui/package.json
  • packages/ui/src/mosaic/MosaicProvider.tsx
  • packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx
  • packages/ui/src/mosaic/__tests__/conditions.test.ts
  • packages/ui/src/mosaic/__tests__/cva.test.ts
  • packages/ui/src/mosaic/__tests__/resolveSlot.test.ts
  • packages/ui/src/mosaic/__tests__/slot-recipe.test.ts
  • packages/ui/src/mosaic/__tests__/utils.test.ts
  • packages/ui/src/mosaic/aio/organization-profile.tsx
  • packages/ui/src/mosaic/appearance.ts
  • packages/ui/src/mosaic/block/destructive.tsx
  • packages/ui/src/mosaic/components/__tests__/button.test.tsx
  • packages/ui/src/mosaic/components/box.tsx
  • packages/ui/src/mosaic/components/button.tsx
  • packages/ui/src/mosaic/components/dialog.tsx
  • packages/ui/src/mosaic/components/input.tsx
  • packages/ui/src/mosaic/components/section-skeleton.tsx
  • packages/ui/src/mosaic/components/skeleton.tsx
  • packages/ui/src/mosaic/components/tabs.tsx
  • packages/ui/src/mosaic/conditions.ts
  • packages/ui/src/mosaic/cva.ts
  • packages/ui/src/mosaic/mock/organization-store.ts
  • packages/ui/src/mosaic/mock/use-organization.tsx
  • packages/ui/src/mosaic/panels/organization-profile-general.tsx
  • packages/ui/src/mosaic/primitives/box.tsx
  • packages/ui/src/mosaic/primitives/dialog.tsx
  • packages/ui/src/mosaic/primitives/tabs.tsx
  • packages/ui/src/mosaic/primitives/withMosaicSlot.tsx
  • packages/ui/src/mosaic/primitives/withMosaicTheme.tsx
  • packages/ui/src/mosaic/registry.ts
  • packages/ui/src/mosaic/resolveSlot.ts
  • packages/ui/src/mosaic/sections/delete-organization.tsx
  • packages/ui/src/mosaic/sections/leave-organization.tsx
  • packages/ui/src/mosaic/slot-recipe.ts
  • packages/ui/src/mosaic/styled.tsx
  • packages/ui/src/mosaic/types.ts
  • packages/ui/src/mosaic/useSlot.ts
  • packages/ui/src/mosaic/utils.ts
  • packages/ui/src/mosaic/variables.ts
  • references/mosaic-architecture.md
💤 Files with no reviewable changes (14)
  • packages/headless/src/primitives/dialog/dialog-description.tsx
  • packages/ui/src/mosaic/utils.ts
  • packages/headless/src/primitives/dialog/dialog-viewport.tsx
  • packages/ui/src/mosaic/types.ts
  • packages/ui/src/mosaic/styled.tsx
  • packages/headless/src/primitives/dialog/dialog-title.tsx
  • packages/swingset/src/app/components/[component]/page.tsx
  • packages/headless/src/primitives/dialog/dialog-close.tsx
  • packages/headless/src/primitives/dialog/dialog-trigger.tsx
  • packages/ui/src/mosaic/primitives/withMosaicTheme.tsx
  • packages/ui/src/mosaic/cva.ts
  • packages/headless/src/primitives/dialog/dialog-backdrop.tsx
  • packages/ui/src/mosaic/tests/cva.test.ts
  • packages/headless/src/primitives/dialog/dialog-popup.tsx

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

🧹 Nitpick comments (5)
packages/swingset/src/stories/leave-organization.stories.tsx (1)

13-15: ⚡ Quick win

Add explicit return types to the exported story functions.

These Default exports are public story entry points, and as per coding guidelines they should declare explicit return types. This keeps the new story modules consistent and avoids inference drift.

🤖 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 `@packages/swingset/src/stories/leave-organization.stories.tsx` around lines 13
- 15, Exported story function Default lacks an explicit return type; update the
Default function declaration to include an explicit React element return type
(e.g., React.ReactElement or JSX.Element) so the exported story's signature is
explicit—locate the Default function that returns LeaveOrganization and add the
return type to its declaration.

Source: Coding guidelines

packages/swingset/src/stories/delete-organization.stories.tsx (1)

13-15: ⚡ Quick win

Unify story-function signatures across delete-organization.stories.tsx, destructive.stories.tsx, and dialog.component.stories.tsx.

All three files export Default story functions without the package’s expected Record<string, unknown> args contract. Please align each Default signature to the same knobs-compatible pattern used in existing Swingset stories to keep story registration and args handling consistent.

As per coding guidelines, packages/swingset/**/*.stories.{ts,tsx} story functions must take Record<string, unknown> and cast to the real prop type.

🤖 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 `@packages/swingset/src/stories/delete-organization.stories.tsx` around lines
13 - 15, The Default story function currently has no args parameter; update its
signature to accept args: Record<string, unknown> (matching other Swingset
stories) and cast args to the component's real prop type before passing to
DeleteOrganization, e.g. change function Default() to Default(args:
Record<string, unknown>) and use the casted props when rendering; apply the same
pattern in the other two files (destructive.stories.tsx and
dialog.component.stories.tsx) so all Default exports follow the knobs-compatible
args contract.

Source: Coding guidelines

packages/swingset/src/components/DocsViewer.tsx (1)

3-3: ⚡ Quick win

Add an explicit return type for DocsViewer and switch to type-only React imports.

This exported API currently relies on inferred return typing and ambient React namespace types.

♻️ Proposed update
+import type { ComponentType, JSX } from 'react';
 import dynamic from 'next/dynamic';
 
 import { getModule } from '`@/lib/registry`';
@@
-const docModules: Record<string, Record<string, React.ComponentType>> = {
+const docModules: Record<string, Record<string, ComponentType>> = {
@@
-export function DocsViewer({ group, slug }: DocsViewerProps) {
+export function DocsViewer({ group, slug }: DocsViewerProps): JSX.Element {

As per coding guidelines, "Always define explicit return types for functions, especially public APIs" and "Use type-only imports ... where possible." Based on learnings, exported TypeScript APIs in this repository should keep explicit return type annotations.

Also applies to: 12-12, 51-51

🤖 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 `@packages/swingset/src/components/DocsViewer.tsx` at line 3, Add an explicit
return type to the exported DocsViewer component and switch React imports to
type-only; specifically, change any "import React" (or implicit ambient React
usage) to "import type React from 'react'" or "import type { ReactElement } from
'react'" and annotate the DocsViewer signature (export default function
DocsViewer(): React.ReactElement or JSX.Element) so the public API has an
explicit return type; update any other functions in this file with the same
pattern (the other DocsViewer-related occurrences) to use type-only imports and
explicit return annotations.

Sources: Coding guidelines, Learnings

packages/ui/src/mosaic/slot-recipe.ts (1)

303-318: 💤 Low value

Duplicate stateToAttrs and kebab helpers exist in useSlot.ts.

The stateToAttrs function (lines 303-315) and the kebab helper (lines 317-318) are duplicated in useSlot.ts (lines 51-61). Consider extracting these to a shared internal utility module to avoid drift between implementations.

🤖 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 `@packages/ui/src/mosaic/slot-recipe.ts` around lines 303 - 318, Duplicate
helpers stateToAttrs and kebab are defined in two places; extract them into a
single internal utility module (e.g., create a new file exporting kebab and
stateToAttrs), replace the duplicate implementations by importing those
functions in both slot-recipe.ts and useSlot.ts, remove the local definitions,
and update the import statements where stateToAttrs and kebab are used to
reference the new shared utility.
packages/ui/src/mosaic/components/__tests__/button.test.tsx (1)

10-15: ⚡ Quick win

Scope CSS scraping to Mosaic Emotion tags to avoid cross-test style bleed.

insertedStyles() currently reads every <style> element, so assertions can become flaky when unrelated styles are present. Restricting to the Mosaic Emotion cache key keeps these tests deterministic.

Suggested patch
 function insertedStyles(): string {
-  return Array.from(document.querySelectorAll('style'))
+  return Array.from(document.querySelectorAll('style[data-emotion^="cl-mosaic"]'))
     .map(el => el.textContent ?? '')
     .join('')
     .replace(/\s+/g, '');
 }
🤖 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 `@packages/ui/src/mosaic/components/__tests__/button.test.tsx` around lines 10
- 15, The test helper insertedStyles() is reading all <style> tags which allows
unrelated styles to leak into assertions; update insertedStyles() to only
collect styles produced by Mosaic's Emotion cache by selecting style elements
with the Emotion cache key (e.g.
querySelectorAll('style[data-emotion^="mosaic"]') or similar), then
map/textContent/join/replace as before so only Mosaic-specific styles are
returned.
🤖 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.

Inline comments:
In @.changeset/calm-aliens-care.md:
- Around line 1-2: The three empty changeset files (including
.changeset/calm-aliens-care.md) must be populated: either add a proper changeset
entry listing the package(s) to bump and a short summary of the user-facing
change (e.g., package-name: minor/patch/major + one-sentence description) so the
release tooling will create changelog/version bumps, or explicitly mark the
change as intentionally deferred by adding a clear note in the file (e.g.,
"RELEASE DEFERRED" or equivalent project-standard defer flag) so reviewers know
no release is expected; update each empty changeset file accordingly.

In `@packages/swingset/src/components/StoryEmbed.tsx`:
- Around line 41-43: The check in StoryEmbed that currently uses if
(!composition) doesn't treat an empty array as "no composition" and causes an
empty Composition footer to render; update the conditional in the StoryEmbed
render path (where composition is inspected) to guard on length (e.g., if
(!composition || composition.length === 0) or if (!(composition?.length))) so
empty arrays are treated as absent and the preview branch is returned instead.

In `@packages/ui/src/mosaic/aio/organization-profile.tsx`:
- Line 5: Add an explicit return type to the exported component function: change
the signature of OrganizationProfile to include ": JSX.Element" (i.e., export
function OrganizationProfile(): JSX.Element { ... }) so it matches the repo's
exported-component typing pattern; update the OrganizationProfile function
declaration accordingly.

In `@packages/ui/src/mosaic/components/button.tsx`:
- Around line 59-69: The Button component spreads {...rest} before {...root},
causing root.className to overwrite a caller-provided className; update
MosaicButton (the React.forwardRef function using useRecipe and the returned
root) to merge class names instead of overwriting them—combine rest.className
(or props.className) with root.className (e.g., via a utility like clsx or
string concat) and pass the merged className to the button so caller className
is preserved while keeping recipe classes from root.

In `@packages/ui/src/mosaic/components/dialog.tsx`:
- Around line 96-107: The Dialog trigger spreads recipe props after user props
so recipe values override consumer-provided styles; in the DialogTrigger inside
Trigger (React.forwardRef) adjust the spread order or merge style-related props
so user values are preserved—specifically, call <Primitive.Trigger> with
{...trigger} spread first and then {...props} or explicitly merge className and
css from props and trigger (from useRecipe(dialogRecipe)) so consumers can
extend/override styles while still applying recipe defaults.
- Around line 100-104: The props spread order in dialog.tsx (Primitive.Trigger
with {...props} {...trigger}) and similarly in input.tsx allows recipe-generated
slotProps/trigger to overwrite user-provided props; change the merge strategy so
user props take precedence and recipe props augment them: either spread
slotProps first then props (e.g., {...slotProps} {...props}) or explicitly
merge/concat className and css (and merge data-* attributes) so user values are
preserved (refer to Primitive.Trigger, the trigger/slotProps objects, and the
props parameter); if the design intends recipe override, add documentation
stating that appearance API wins instead of changing code.

In `@packages/ui/src/mosaic/mock/organization-store.ts`:
- Line 33: The exported delay helper currently lacks an explicit return type;
update the function signature for delay to include the explicit return type
Promise<void> (i.e., export const delay = (ms: number): Promise<void> => ...) so
it matches the repo TypeScript API guidelines and ensures callers see the
correct promise type; adjust any related type imports if necessary and run
typecheck to verify.

In `@packages/ui/src/mosaic/sections/delete-organization.tsx`:
- Around line 18-23: The delete handler (handleDelete) currently sets
setIsDeleting(true) then awaits organization.destroy() but if the promise
rejects the subsequent setIsDeleting(false) and dialog close (setOpen(false))
are skipped; wrap the await in a try/finally so setIsDeleting(false) always
runs, and only call setOpen(false) on success (e.g., after await returns
successfully inside try) — apply the same fix for the analogous handleDelete in
leave-organization.tsx; locate references to handleDelete, organization.destroy,
setIsDeleting and setOpen when making the change.

In `@references/mosaic-architecture.md`:
- Line 9: Update the paragraph to clarify that the `data-cl-slot` attribute is
owned and emitted by Mosaic (not `@clerk/headless`); state and variant
attributes (`data-cl-<state>`, `data-cl-<variant>`) remain the public styling
contract, and components continue to be authored with slot recipes via
`defineSlotRecipe` which manage variants, slot identity, state→attribute
mapping, and the appearance cascade. Replace the current claim that
`data-cl-slot` ships with `@clerk/headless` with this corrected ownership and
keep the rest of the sentence about no classname derivation, no `__state`
concatenation, and no central appearance-key registry intact.

---

Nitpick comments:
In `@packages/swingset/src/components/DocsViewer.tsx`:
- Line 3: Add an explicit return type to the exported DocsViewer component and
switch React imports to type-only; specifically, change any "import React" (or
implicit ambient React usage) to "import type React from 'react'" or "import
type { ReactElement } from 'react'" and annotate the DocsViewer signature
(export default function DocsViewer(): React.ReactElement or JSX.Element) so the
public API has an explicit return type; update any other functions in this file
with the same pattern (the other DocsViewer-related occurrences) to use
type-only imports and explicit return annotations.

In `@packages/swingset/src/stories/delete-organization.stories.tsx`:
- Around line 13-15: The Default story function currently has no args parameter;
update its signature to accept args: Record<string, unknown> (matching other
Swingset stories) and cast args to the component's real prop type before passing
to DeleteOrganization, e.g. change function Default() to Default(args:
Record<string, unknown>) and use the casted props when rendering; apply the same
pattern in the other two files (destructive.stories.tsx and
dialog.component.stories.tsx) so all Default exports follow the knobs-compatible
args contract.

In `@packages/swingset/src/stories/leave-organization.stories.tsx`:
- Around line 13-15: Exported story function Default lacks an explicit return
type; update the Default function declaration to include an explicit React
element return type (e.g., React.ReactElement or JSX.Element) so the exported
story's signature is explicit—locate the Default function that returns
LeaveOrganization and add the return type to its declaration.

In `@packages/ui/src/mosaic/components/__tests__/button.test.tsx`:
- Around line 10-15: The test helper insertedStyles() is reading all <style>
tags which allows unrelated styles to leak into assertions; update
insertedStyles() to only collect styles produced by Mosaic's Emotion cache by
selecting style elements with the Emotion cache key (e.g.
querySelectorAll('style[data-emotion^="mosaic"]') or similar), then
map/textContent/join/replace as before so only Mosaic-specific styles are
returned.

In `@packages/ui/src/mosaic/slot-recipe.ts`:
- Around line 303-318: Duplicate helpers stateToAttrs and kebab are defined in
two places; extract them into a single internal utility module (e.g., create a
new file exporting kebab and stateToAttrs), replace the duplicate
implementations by importing those functions in both slot-recipe.ts and
useSlot.ts, remove the local definitions, and update the import statements where
stateToAttrs and kebab are used to reference the new shared utility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: d349c294-65fa-4be1-b8bf-d2c0369a323c

📥 Commits

Reviewing files that changed from the base of the PR and between 5a28ac1 and 541c4e6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (82)
  • .changeset/calm-aliens-care.md
  • .changeset/delete-org-block.md
  • .changeset/mosaic-slot-recipes.md
  • packages/headless/src/primitives/dialog/README.md
  • packages/headless/src/primitives/dialog/dialog-backdrop.tsx
  • packages/headless/src/primitives/dialog/dialog-close.tsx
  • packages/headless/src/primitives/dialog/dialog-description.tsx
  • packages/headless/src/primitives/dialog/dialog-popup.tsx
  • packages/headless/src/primitives/dialog/dialog-title.tsx
  • packages/headless/src/primitives/dialog/dialog-trigger.tsx
  • packages/headless/src/primitives/dialog/dialog-viewport.tsx
  • packages/headless/src/primitives/dialog/dialog.test.tsx
  • packages/headless/src/primitives/tabs/tabs-panel.tsx
  • packages/swingset/next.config.mjs
  • packages/swingset/src/app/[group]/[component]/page.tsx
  • packages/swingset/src/app/components/[component]/page.tsx
  • packages/swingset/src/components/ClientRoot.tsx
  • packages/swingset/src/components/Composition.tsx
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/components/StoryEmbed.tsx
  • packages/swingset/src/components/StoryPreview.tsx
  • packages/swingset/src/components/app-sidebar.tsx
  • packages/swingset/src/components/ui/sidebar.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/lib/slug.ts
  • packages/swingset/src/lib/types.ts
  • packages/swingset/src/stories/button.stories.tsx
  • packages/swingset/src/stories/delete-organization.mdx
  • packages/swingset/src/stories/delete-organization.stories.tsx
  • packages/swingset/src/stories/destructive.mdx
  • packages/swingset/src/stories/destructive.stories.tsx
  • packages/swingset/src/stories/dialog.component.mdx
  • packages/swingset/src/stories/dialog.component.stories.tsx
  • packages/swingset/src/stories/input.stories.tsx
  • packages/swingset/src/stories/leave-organization.mdx
  • packages/swingset/src/stories/leave-organization.stories.tsx
  • packages/swingset/src/stories/organization-profile-general.mdx
  • packages/swingset/src/stories/organization-profile-general.stories.tsx
  • packages/swingset/src/stories/organization-profile.mdx
  • packages/swingset/src/stories/organization-profile.stories.tsx
  • packages/swingset/src/stories/tabs.component.mdx
  • packages/swingset/src/stories/tabs.component.stories.tsx
  • packages/ui/package.json
  • packages/ui/src/mosaic/MosaicProvider.tsx
  • packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx
  • packages/ui/src/mosaic/__tests__/conditions.test.ts
  • packages/ui/src/mosaic/__tests__/cva.test.ts
  • packages/ui/src/mosaic/__tests__/resolveSlot.test.ts
  • packages/ui/src/mosaic/__tests__/slot-recipe.test.ts
  • packages/ui/src/mosaic/__tests__/utils.test.ts
  • packages/ui/src/mosaic/aio/organization-profile.tsx
  • packages/ui/src/mosaic/appearance.ts
  • packages/ui/src/mosaic/block/destructive.tsx
  • packages/ui/src/mosaic/components/__tests__/button.test.tsx
  • packages/ui/src/mosaic/components/box.tsx
  • packages/ui/src/mosaic/components/button.tsx
  • packages/ui/src/mosaic/components/dialog.tsx
  • packages/ui/src/mosaic/components/input.tsx
  • packages/ui/src/mosaic/components/section-skeleton.tsx
  • packages/ui/src/mosaic/components/skeleton.tsx
  • packages/ui/src/mosaic/components/tabs.tsx
  • packages/ui/src/mosaic/conditions.ts
  • packages/ui/src/mosaic/cva.ts
  • packages/ui/src/mosaic/mock/organization-store.ts
  • packages/ui/src/mosaic/mock/use-organization.tsx
  • packages/ui/src/mosaic/panels/organization-profile-general.tsx
  • packages/ui/src/mosaic/primitives/box.tsx
  • packages/ui/src/mosaic/primitives/dialog.tsx
  • packages/ui/src/mosaic/primitives/tabs.tsx
  • packages/ui/src/mosaic/primitives/withMosaicSlot.tsx
  • packages/ui/src/mosaic/primitives/withMosaicTheme.tsx
  • packages/ui/src/mosaic/registry.ts
  • packages/ui/src/mosaic/resolveSlot.ts
  • packages/ui/src/mosaic/sections/delete-organization.tsx
  • packages/ui/src/mosaic/sections/leave-organization.tsx
  • packages/ui/src/mosaic/slot-recipe.ts
  • packages/ui/src/mosaic/styled.tsx
  • packages/ui/src/mosaic/types.ts
  • packages/ui/src/mosaic/useSlot.ts
  • packages/ui/src/mosaic/utils.ts
  • packages/ui/src/mosaic/variables.ts
  • references/mosaic-architecture.md
💤 Files with no reviewable changes (14)
  • packages/headless/src/primitives/dialog/dialog-description.tsx
  • packages/ui/src/mosaic/utils.ts
  • packages/headless/src/primitives/dialog/dialog-viewport.tsx
  • packages/ui/src/mosaic/types.ts
  • packages/ui/src/mosaic/styled.tsx
  • packages/headless/src/primitives/dialog/dialog-title.tsx
  • packages/swingset/src/app/components/[component]/page.tsx
  • packages/headless/src/primitives/dialog/dialog-close.tsx
  • packages/headless/src/primitives/dialog/dialog-trigger.tsx
  • packages/ui/src/mosaic/primitives/withMosaicTheme.tsx
  • packages/ui/src/mosaic/cva.ts
  • packages/headless/src/primitives/dialog/dialog-backdrop.tsx
  • packages/ui/src/mosaic/tests/cva.test.ts
  • packages/headless/src/primitives/dialog/dialog-popup.tsx
🛑 Comments failed to post (9)
.changeset/calm-aliens-care.md (1)

1-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add release entries or mark the bumps as intentionally deferred.

These three changeset files are empty. That’s fine for docs-only work, but this PR introduces new Mosaic layers / public-facing UI surface, so leaving all three blank risks shipping the package changes without any version or changelog entry. If the release is intentionally deferred, please note that explicitly; otherwise add the corresponding changeset entries here.

🤖 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 @.changeset/calm-aliens-care.md around lines 1 - 2, The three empty changeset
files (including .changeset/calm-aliens-care.md) must be populated: either add a
proper changeset entry listing the package(s) to bump and a short summary of the
user-facing change (e.g., package-name: minor/patch/major + one-sentence
description) so the release tooling will create changelog/version bumps, or
explicitly mark the change as intentionally deferred by adding a clear note in
the file (e.g., "RELEASE DEFERRED" or equivalent project-standard defer flag) so
reviewers know no release is expected; update each empty changeset file
accordingly.
packages/swingset/src/components/StoryEmbed.tsx (1)

41-43: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle empty composition arrays as “no composition”.

if (!composition) treats [] as present, so the UI renders an empty Composition footer. Gate on .length to avoid empty collapsibles.

Suggested fix
-  if (!composition) {
+  if (!composition?.length) {
     return <div className='not-prose border-border bg-background my-4 rounded-lg border'>{preview}</div>;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  if (!composition?.length) {
    return <div className='not-prose border-border bg-background my-4 rounded-lg border'>{preview}</div>;
  }
🤖 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 `@packages/swingset/src/components/StoryEmbed.tsx` around lines 41 - 43, The
check in StoryEmbed that currently uses if (!composition) doesn't treat an empty
array as "no composition" and causes an empty Composition footer to render;
update the conditional in the StoryEmbed render path (where composition is
inspected) to guard on length (e.g., if (!composition || composition.length ===
0) or if (!(composition?.length))) so empty arrays are treated as absent and the
preview branch is returned instead.
packages/ui/src/mosaic/aio/organization-profile.tsx (1)

5-5: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion of the reviewed file
sed -n '1,120p' packages/ui/src/mosaic/aio/organization-profile.tsx | cat -n

# Check if any explicit return type is already present for this export
rg -n "export function OrganizationProfile" packages/ui/src/mosaic/aio/organization-profile.tsx || true
rg -n "export (function|const) OrganizationProfile" packages/ui/src/mosaic/aio/organization-profile.tsx || true

# Find other exported TSX components and see whether they use explicit return types
rg -n --glob "**/*.tsx" "export function [A-Z][A-Za-z0-9_]*\\(.*\\)\\s*:\\s*" packages/ui/src | head -n 50
rg -n --glob "**/*.tsx" "export function [A-Z][A-Za-z0-9_]*\\(.*\\)\\s*\\{" packages/ui/src | head -n 50

Repository: clerk/javascript

Length of output: 5850


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' packages/ui/src/mosaic/panels/organization-profile-general.tsx | cat -n
sed -n '1,80p' packages/ui/src/mosaic/sections/delete-organization.tsx | cat -n
sed -n '1,80p' packages/ui/src/mosaic/sections/leave-organization.tsx | cat -n

Repository: clerk/javascript

Length of output: 6384


Add an explicit return type for exported OrganizationProfile.

export function OrganizationProfile() should be annotated with : JSX.Element to match the repo’s exported-component typing pattern.

🤖 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 `@packages/ui/src/mosaic/aio/organization-profile.tsx` at line 5, Add an
explicit return type to the exported component function: change the signature of
OrganizationProfile to include ": JSX.Element" (i.e., export function
OrganizationProfile(): JSX.Element { ... }) so it matches the repo's
exported-component typing pattern; update the OrganizationProfile function
declaration accordingly.

Source: Coding guidelines

packages/ui/src/mosaic/components/button.tsx (1)

59-69: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve caller className when recipe injects appearance classes.

On Line 68, spreading root after rest means root.className overwrites a caller-provided className whenever appearance defines a string class override. Merge both class names instead.

Suggested patch
 export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function MosaicButton(props, ref) {
-  const { color, size, disabled, sx, children, ...rest } = props;
+  const { color, size, disabled, sx, children, className, ...rest } = props;
   const { root } = useRecipe(buttonRecipe, { variants: { color, size }, state: { disabled: !!disabled }, sx });
+  const { className: recipeClassName, ...rootProps } = root;
+  const mergedClassName = [className, recipeClassName].filter(Boolean).join(' ') || undefined;
   return (
     <button
       ref={ref}
       disabled={disabled || false}
       type='button'
       {...rest}
-      {...root}
+      {...rootProps}
+      className={mergedClassName}
     >
       {children}
     </button>
   );
 });
🤖 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 `@packages/ui/src/mosaic/components/button.tsx` around lines 59 - 69, The
Button component spreads {...rest} before {...root}, causing root.className to
overwrite a caller-provided className; update MosaicButton (the React.forwardRef
function using useRecipe and the returned root) to merge class names instead of
overwriting them—combine rest.className (or props.className) with root.className
(e.g., via a utility like clsx or string concat) and pass the merged className
to the button so caller className is preserved while keeping recipe classes from
root.
packages/ui/src/mosaic/components/dialog.tsx (2)

96-107: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Props spread order may override user-provided styles.

The pattern {...props} {...trigger} means recipe-generated props (including css, className, data-cl-slot) will override any user-provided values for those keys. If a consumer passes their own css or className, it will be silently replaced.

If this is intentional (recipe styles should not be overridable via props), consider documenting it. If consumers should be able to extend styles, swap the spread order or merge the css/className values explicitly.

🤖 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 `@packages/ui/src/mosaic/components/dialog.tsx` around lines 96 - 107, The
Dialog trigger spreads recipe props after user props so recipe values override
consumer-provided styles; in the DialogTrigger inside Trigger (React.forwardRef)
adjust the spread order or merge style-related props so user values are
preserved—specifically, call <Primitive.Trigger> with {...trigger} spread first
and then {...props} or explicitly merge className and css from props and trigger
(from useRecipe(dialogRecipe)) so consumers can extend/override styles while
still applying recipe defaults.

100-104: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Props spread order in dialog.tsx and input.tsx allows recipe styles to override user-provided props.

Both files use {...props} {...slotProps} ordering, meaning recipe-generated css, className, and data-cl-* attributes override any user-supplied values. If this is the intended design (styling via appearance API only), document it. If consumers should be able to extend styles via props, consider merging css/className rather than overwriting.

🤖 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 `@packages/ui/src/mosaic/components/dialog.tsx` around lines 100 - 104, The
props spread order in dialog.tsx (Primitive.Trigger with {...props}
{...trigger}) and similarly in input.tsx allows recipe-generated
slotProps/trigger to overwrite user-provided props; change the merge strategy so
user props take precedence and recipe props augment them: either spread
slotProps first then props (e.g., {...slotProps} {...props}) or explicitly
merge/concat className and css (and merge data-* attributes) so user values are
preserved (refer to Primitive.Trigger, the trigger/slotProps objects, and the
props parameter); if the design intends recipe override, add documentation
stating that appearance API wins instead of changing code.
packages/ui/src/mosaic/mock/organization-store.ts (1)

33-33: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/mock/organization-store.ts"
echo "== File: $FILE =="
test -f "$FILE" && echo "exists" || (echo "missing"; exit 1)
nl -ba "$FILE" | sed -n '1,120p' | sed -n '20,60p'

Repository: clerk/javascript

Length of output: 190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/mock/organization-store.ts"
nl -ba "$FILE" | sed -n '20,50p'

Repository: clerk/javascript

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/mock/organization-store.ts"
nl -ba "$FILE" | sed -n '20,50p'

Repository: clerk/javascript

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/mock/organization-store.ts"

echo "== $FILE =="
sed -n '25,45p' "$FILE"
echo "== Lines with numbering (NR>=25 && NR<=45) =="
awk 'NR>=25 && NR<=45 { printf "%5d: %s\n", NR, $0 }' "$FILE"

Repository: clerk/javascript

Length of output: 711


Add explicit return type to exported delay helper

delay is exported without an explicit return type; annotate it as Promise<void> to match the repo’s TS API guidelines.

Proposed change
-export const delay = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));
+export const delay = (ms: number): Promise<void> => new Promise<void>(resolve => setTimeout(resolve, ms));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const delay = (ms: number): Promise<void> => new Promise<void>(resolve => setTimeout(resolve, ms));
🤖 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 `@packages/ui/src/mosaic/mock/organization-store.ts` at line 33, The exported
delay helper currently lacks an explicit return type; update the function
signature for delay to include the explicit return type Promise<void> (i.e.,
export const delay = (ms: number): Promise<void> => ...) so it matches the repo
TypeScript API guidelines and ensures callers see the correct promise type;
adjust any related type imports if necessary and run typecheck to verify.

Source: Coding guidelines

packages/ui/src/mosaic/sections/delete-organization.tsx (1)

18-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure deleting state is always reset on mutation failure.

At Lines 18–23, if organization.destroy() rejects, setIsDeleting(false) is skipped and the destructive flow can remain stuck in a loading/disabled state. Apply try/finally (and keep dialog close on success only). This same pattern appears in packages/ui/src/mosaic/sections/leave-organization.tsx at Lines 18–23.

Proposed change
   const handleDelete = async () => {
-    setIsDeleting(true);
-    await organization.destroy();
-    setIsDeleting(false);
-    setOpen(false);
+    setIsDeleting(true);
+    try {
+      await organization.destroy();
+      setOpen(false);
+    } finally {
+      setIsDeleting(false);
+    }
   };
🤖 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 `@packages/ui/src/mosaic/sections/delete-organization.tsx` around lines 18 -
23, The delete handler (handleDelete) currently sets setIsDeleting(true) then
awaits organization.destroy() but if the promise rejects the subsequent
setIsDeleting(false) and dialog close (setOpen(false)) are skipped; wrap the
await in a try/finally so setIsDeleting(false) always runs, and only call
setOpen(false) on success (e.g., after await returns successfully inside try) —
apply the same fix for the analogous handleDelete in leave-organization.tsx;
locate references to handleDelete, organization.destroy, setIsDeleting and
setOpen when making the change.
references/mosaic-architecture.md (1)

9-9: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify who owns data-cl-slot.

This sentence still says data-cl-slot ships with @clerk/headless, but the dialog README in this patch says headless parts no longer emit slot identity and that Mosaic owns it. Please update this paragraph so the architecture doc matches the actual contract.

🤖 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 `@references/mosaic-architecture.md` at line 9, Update the paragraph to clarify
that the `data-cl-slot` attribute is owned and emitted by Mosaic (not
`@clerk/headless`); state and variant attributes (`data-cl-<state>`,
`data-cl-<variant>`) remain the public styling contract, and components continue
to be authored with slot recipes via `defineSlotRecipe` which manage variants,
slot identity, state→attribute mapping, and the appearance cascade. Replace the
current claim that `data-cl-slot` ships with `@clerk/headless` with this
corrected ownership and keep the rest of the sentence about no classname
derivation, no `__state` concatenation, and no central appearance-key registry
intact.

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8838

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8838

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8838

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8838

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8838

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8838

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8838

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8838

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8838

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8838

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8838

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8838

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8838

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8838

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8838

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8838

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8838

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8838

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8838

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8838

commit: d4ba05b

Comment on lines +10 to +18
const major = parseInt(version, 10);
const isModernReact = major >= 19 || major === 0;

export function inertProps(active: boolean): Record<string, unknown> {
if (!active) {
return {};
}
return { inert: isModernReact ? true : '' };
}

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.

temporary til #8820 lands

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.

2 participants