feat(ui): add mosaic block/section/aio prototype layers#8838
feat(ui): add mosaic block/section/aio prototype layers#8838alexcarpenter wants to merge 27 commits into
Conversation
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).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 3907704 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen 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 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited) Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis 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. ChangesOrganization Profile UI Feature
Mosaic Tabs Components and Group-Aware Docs Navigation
Story and Documentation Content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
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. Comment |
API Changes Report
Summary
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
#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
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
packages/swingset/src/stories/leave-organization.stories.tsx (1)
13-15: ⚡ Quick winAdd explicit return types to the exported story functions.
These
Defaultexports 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 winUnify story-function signatures across
delete-organization.stories.tsx,destructive.stories.tsx, anddialog.component.stories.tsx.All three files export
Defaultstory functions without the package’s expectedRecord<string, unknown>args contract. Please align eachDefaultsignature 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 takeRecord<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 winAdd an explicit return type for
DocsViewerand switch to type-only React imports.This exported API currently relies on inferred return typing and ambient
Reactnamespace 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 valueDuplicate
stateToAttrsandkebabhelpers exist inuseSlot.ts.The
stateToAttrsfunction (lines 303-315) and thekebabhelper (lines 317-318) are duplicated inuseSlot.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 winScope 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (82)
.changeset/calm-aliens-care.md.changeset/delete-org-block.md.changeset/mosaic-slot-recipes.mdpackages/headless/src/primitives/dialog/README.mdpackages/headless/src/primitives/dialog/dialog-backdrop.tsxpackages/headless/src/primitives/dialog/dialog-close.tsxpackages/headless/src/primitives/dialog/dialog-description.tsxpackages/headless/src/primitives/dialog/dialog-popup.tsxpackages/headless/src/primitives/dialog/dialog-title.tsxpackages/headless/src/primitives/dialog/dialog-trigger.tsxpackages/headless/src/primitives/dialog/dialog-viewport.tsxpackages/headless/src/primitives/dialog/dialog.test.tsxpackages/headless/src/primitives/tabs/tabs-panel.tsxpackages/swingset/next.config.mjspackages/swingset/src/app/[group]/[component]/page.tsxpackages/swingset/src/app/components/[component]/page.tsxpackages/swingset/src/components/ClientRoot.tsxpackages/swingset/src/components/Composition.tsxpackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/components/StoryEmbed.tsxpackages/swingset/src/components/StoryPreview.tsxpackages/swingset/src/components/app-sidebar.tsxpackages/swingset/src/components/ui/sidebar.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/lib/slug.tspackages/swingset/src/lib/types.tspackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/delete-organization.mdxpackages/swingset/src/stories/delete-organization.stories.tsxpackages/swingset/src/stories/destructive.mdxpackages/swingset/src/stories/destructive.stories.tsxpackages/swingset/src/stories/dialog.component.mdxpackages/swingset/src/stories/dialog.component.stories.tsxpackages/swingset/src/stories/input.stories.tsxpackages/swingset/src/stories/leave-organization.mdxpackages/swingset/src/stories/leave-organization.stories.tsxpackages/swingset/src/stories/organization-profile-general.mdxpackages/swingset/src/stories/organization-profile-general.stories.tsxpackages/swingset/src/stories/organization-profile.mdxpackages/swingset/src/stories/organization-profile.stories.tsxpackages/swingset/src/stories/tabs.component.mdxpackages/swingset/src/stories/tabs.component.stories.tsxpackages/ui/package.jsonpackages/ui/src/mosaic/MosaicProvider.tsxpackages/ui/src/mosaic/__tests__/MosaicProvider.test.tsxpackages/ui/src/mosaic/__tests__/conditions.test.tspackages/ui/src/mosaic/__tests__/cva.test.tspackages/ui/src/mosaic/__tests__/resolveSlot.test.tspackages/ui/src/mosaic/__tests__/slot-recipe.test.tspackages/ui/src/mosaic/__tests__/utils.test.tspackages/ui/src/mosaic/aio/organization-profile.tsxpackages/ui/src/mosaic/appearance.tspackages/ui/src/mosaic/block/destructive.tsxpackages/ui/src/mosaic/components/__tests__/button.test.tsxpackages/ui/src/mosaic/components/box.tsxpackages/ui/src/mosaic/components/button.tsxpackages/ui/src/mosaic/components/dialog.tsxpackages/ui/src/mosaic/components/input.tsxpackages/ui/src/mosaic/components/section-skeleton.tsxpackages/ui/src/mosaic/components/skeleton.tsxpackages/ui/src/mosaic/components/tabs.tsxpackages/ui/src/mosaic/conditions.tspackages/ui/src/mosaic/cva.tspackages/ui/src/mosaic/mock/organization-store.tspackages/ui/src/mosaic/mock/use-organization.tsxpackages/ui/src/mosaic/panels/organization-profile-general.tsxpackages/ui/src/mosaic/primitives/box.tsxpackages/ui/src/mosaic/primitives/dialog.tsxpackages/ui/src/mosaic/primitives/tabs.tsxpackages/ui/src/mosaic/primitives/withMosaicSlot.tsxpackages/ui/src/mosaic/primitives/withMosaicTheme.tsxpackages/ui/src/mosaic/registry.tspackages/ui/src/mosaic/resolveSlot.tspackages/ui/src/mosaic/sections/delete-organization.tsxpackages/ui/src/mosaic/sections/leave-organization.tsxpackages/ui/src/mosaic/slot-recipe.tspackages/ui/src/mosaic/styled.tsxpackages/ui/src/mosaic/types.tspackages/ui/src/mosaic/useSlot.tspackages/ui/src/mosaic/utils.tspackages/ui/src/mosaic/variables.tsreferences/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
There was a problem hiding this comment.
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 winAdd explicit return types to the exported story functions.
These
Defaultexports 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 winUnify story-function signatures across
delete-organization.stories.tsx,destructive.stories.tsx, anddialog.component.stories.tsx.All three files export
Defaultstory functions without the package’s expectedRecord<string, unknown>args contract. Please align eachDefaultsignature 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 takeRecord<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 winAdd an explicit return type for
DocsViewerand switch to type-only React imports.This exported API currently relies on inferred return typing and ambient
Reactnamespace 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 valueDuplicate
stateToAttrsandkebabhelpers exist inuseSlot.ts.The
stateToAttrsfunction (lines 303-315) and thekebabhelper (lines 317-318) are duplicated inuseSlot.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 winScope 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (82)
.changeset/calm-aliens-care.md.changeset/delete-org-block.md.changeset/mosaic-slot-recipes.mdpackages/headless/src/primitives/dialog/README.mdpackages/headless/src/primitives/dialog/dialog-backdrop.tsxpackages/headless/src/primitives/dialog/dialog-close.tsxpackages/headless/src/primitives/dialog/dialog-description.tsxpackages/headless/src/primitives/dialog/dialog-popup.tsxpackages/headless/src/primitives/dialog/dialog-title.tsxpackages/headless/src/primitives/dialog/dialog-trigger.tsxpackages/headless/src/primitives/dialog/dialog-viewport.tsxpackages/headless/src/primitives/dialog/dialog.test.tsxpackages/headless/src/primitives/tabs/tabs-panel.tsxpackages/swingset/next.config.mjspackages/swingset/src/app/[group]/[component]/page.tsxpackages/swingset/src/app/components/[component]/page.tsxpackages/swingset/src/components/ClientRoot.tsxpackages/swingset/src/components/Composition.tsxpackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/components/StoryEmbed.tsxpackages/swingset/src/components/StoryPreview.tsxpackages/swingset/src/components/app-sidebar.tsxpackages/swingset/src/components/ui/sidebar.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/lib/slug.tspackages/swingset/src/lib/types.tspackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/delete-organization.mdxpackages/swingset/src/stories/delete-organization.stories.tsxpackages/swingset/src/stories/destructive.mdxpackages/swingset/src/stories/destructive.stories.tsxpackages/swingset/src/stories/dialog.component.mdxpackages/swingset/src/stories/dialog.component.stories.tsxpackages/swingset/src/stories/input.stories.tsxpackages/swingset/src/stories/leave-organization.mdxpackages/swingset/src/stories/leave-organization.stories.tsxpackages/swingset/src/stories/organization-profile-general.mdxpackages/swingset/src/stories/organization-profile-general.stories.tsxpackages/swingset/src/stories/organization-profile.mdxpackages/swingset/src/stories/organization-profile.stories.tsxpackages/swingset/src/stories/tabs.component.mdxpackages/swingset/src/stories/tabs.component.stories.tsxpackages/ui/package.jsonpackages/ui/src/mosaic/MosaicProvider.tsxpackages/ui/src/mosaic/__tests__/MosaicProvider.test.tsxpackages/ui/src/mosaic/__tests__/conditions.test.tspackages/ui/src/mosaic/__tests__/cva.test.tspackages/ui/src/mosaic/__tests__/resolveSlot.test.tspackages/ui/src/mosaic/__tests__/slot-recipe.test.tspackages/ui/src/mosaic/__tests__/utils.test.tspackages/ui/src/mosaic/aio/organization-profile.tsxpackages/ui/src/mosaic/appearance.tspackages/ui/src/mosaic/block/destructive.tsxpackages/ui/src/mosaic/components/__tests__/button.test.tsxpackages/ui/src/mosaic/components/box.tsxpackages/ui/src/mosaic/components/button.tsxpackages/ui/src/mosaic/components/dialog.tsxpackages/ui/src/mosaic/components/input.tsxpackages/ui/src/mosaic/components/section-skeleton.tsxpackages/ui/src/mosaic/components/skeleton.tsxpackages/ui/src/mosaic/components/tabs.tsxpackages/ui/src/mosaic/conditions.tspackages/ui/src/mosaic/cva.tspackages/ui/src/mosaic/mock/organization-store.tspackages/ui/src/mosaic/mock/use-organization.tsxpackages/ui/src/mosaic/panels/organization-profile-general.tsxpackages/ui/src/mosaic/primitives/box.tsxpackages/ui/src/mosaic/primitives/dialog.tsxpackages/ui/src/mosaic/primitives/tabs.tsxpackages/ui/src/mosaic/primitives/withMosaicSlot.tsxpackages/ui/src/mosaic/primitives/withMosaicTheme.tsxpackages/ui/src/mosaic/registry.tspackages/ui/src/mosaic/resolveSlot.tspackages/ui/src/mosaic/sections/delete-organization.tsxpackages/ui/src/mosaic/sections/leave-organization.tsxpackages/ui/src/mosaic/slot-recipe.tspackages/ui/src/mosaic/styled.tsxpackages/ui/src/mosaic/types.tspackages/ui/src/mosaic/useSlot.tspackages/ui/src/mosaic/utils.tspackages/ui/src/mosaic/variables.tsreferences/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 winAdd 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 winHandle empty
compositionarrays as “no composition”.
if (!composition)treats[]as present, so the UI renders an empty Composition footer. Gate on.lengthto 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 50Repository: 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 -nRepository: clerk/javascript
Length of output: 6384
Add an explicit return type for exported
OrganizationProfile.
export function OrganizationProfile()should be annotated with: JSX.Elementto 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 winPreserve caller
classNamewhen recipe injects appearance classes.On Line 68, spreading
rootafterrestmeansroot.classNameoverwrites a caller-providedclassNamewhenever 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 winProps spread order may override user-provided styles.
The pattern
{...props} {...trigger}means recipe-generated props (includingcss,className,data-cl-slot) will override any user-provided values for those keys. If a consumer passes their owncssorclassName, 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/classNamevalues 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 winProps spread order in
dialog.tsxandinput.tsxallows recipe styles to override user-provided props.Both files use
{...props} {...slotProps}ordering, meaning recipe-generatedcss,className, anddata-cl-*attributes override any user-supplied values. If this is the intended design (styling viaappearanceAPI only), document it. If consumers should be able to extend styles via props, consider mergingcss/classNamerather 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
delayhelper
delayis exported without an explicit return type; annotate it asPromise<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 winEnsure 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. Applytry/finally(and keep dialog close on success only). This same pattern appears inpackages/ui/src/mosaic/sections/leave-organization.tsxat 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 winClarify who owns
data-cl-slot.This sentence still says
data-cl-slotships 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.
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
| 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 : '' }; | ||
| } |
Summary
packages/ui/src/mosaic/:block/destructive.tsx— controlled confirmation dialog composed fromDialog+Input+Button; caller owns open/deleting state; input must match resource name to enable the actionsection/leave-organization.tsx— ownsopen/isDeletingstate, wiresDestructiveto the leave-organization flowaio/organization-profile.tsx— assembles organization sections into a full-page viewslug.tsto treat consecutive uppercase letters as acronyms (AIO→aionota-i-o)https://swingset-git-carp-mosaic-block-destructive.clerkstage.dev/sections/delete-organization
Test plan
localhost:6006/aio/organization-profile— page renders with a "Leave organization" heading and trigger buttonlocalhost:6006/sections/leave-organization— same section in isolationlocalhost:6006/blocks/destructive— bare block with controlled state in the storyturbo build --filter=@clerk/uipasses with no type errorsSummary by CodeRabbit
Release Notes
New Features
Documentation