Repository navigation
feat: implement auto-discovery system for playground components - #306
Conversation
|
@yashranaway is attempting to deploy a commit to the Dodo Payments Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds a manifest-driven auto-discovery system for playground components, integrates it into playground-header and playground core to load components dynamically, removes the local component registry and the usePlaygroundState hook/export, updates Changes
Sequence Diagram(s)sequenceDiagram
participant PH as PlaygroundHeader
participant PC as PlaygroundMain
participant AD as AutoDiscovery
participant Registry as Public /r/ (manifest)
participant SDK as billingsdk/
rect rgba(0,128,128,0.06)
PH->>AD: getComponentList() (on mount)
AD->>Registry: GET /r/registry.json
Registry-->>AD: manifest
AD->>Registry: GET /r/{name}.json (parallel)
Registry-->>AD: metadata
AD->>SDK: dynamic import billingsdk/...
SDK-->>AD: component module
AD-->>PH: ComponentListItem[] (lightweight)
end
rect rgba(128,64,0,0.06)
PC->>AD: discoverComponent(slug)
AD->>Registry: GET /r/{slug}.json
Registry-->>AD: metadata
AD->>SDK: dynamic import billingsdk/... (resolve main export)
SDK-->>AD: component module
AD-->>PC: ComponentConfig | null
PC->>PC: setSelectedComponent(config)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Potential attention points:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
src/components/playground/auto-discovery.ts (3)
86-96: Harden and simplify snippet extraction regex; avoid variable-pattern pitfalls.Current pattern is vulnerable to pathological backtracking and can miss self-closing tags. Escape the name and use a single alternation that handles paired/self‑closing tags.
- const componentPattern = new RegExp(`<${pascalName}[^>]*(?:>|\\/>)[\\s\\S]*?(?:<\\/${pascalName}>|(?=\\n\\s*\\)))`, 'i'); + const escaped = pascalName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const componentPattern = new RegExp( + `<${escaped}\\b[^>]*>([\\s\\S]*?)<\\/${escaped}>|<${escaped}\\b[^>]*/>`, + 'i' + );Optionally, apply the same approach to the fallback regex. Based on static analysis hints.
112-121: Add camelCase export fallback; improve resilience.Many modules export camelCase names. Try that before giving up.
const module = await import(`@/components/billingsdk/${componentName}`); const pascalName = toPascalCase(componentName); + const camelName = componentName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); // Try multiple export patterns - const component = module[pascalName] || module.default || module[componentName]; + const component = + module[pascalName] || + module.default || + module[camelName] || + module[componentName];
27-36: Add fetch timeouts to avoid hanging UI.Wrap fetch with AbortController to fail fast.
async function fetchRegistryManifest(): Promise<RegistryManifest | null> { try { - const response = await fetch('/r/registry.json'); + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 10000); + const response = await fetch('/r/registry.json', { signal: ac.signal }); + clearTimeout(t); if (!response.ok) return null; return await response.json(); } catch (error) {async function fetchComponentMetadata(componentName: string): Promise<any> { try { - const response = await fetch(`/r/${componentName}.json`); + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 10000); + const response = await fetch(`/r/${componentName}.json`, { signal: ac.signal }); + clearTimeout(t); if (!response.ok) return null; return await response.json(); } catch (error) {Also applies to: 41-50
src/components/playground/playground-header.tsx (2)
104-108: Use SelectValue without children.shadcn/ui SelectValue is self-closing; children are ignored. Rely on placeholder and selected item label.
- <SelectTrigger className="w-64"> - <SelectValue placeholder={isLoading ? "Loading components..." : "Select a component"}> - {state.selectedComponent?.name || (isLoading ? "Loading..." : "Select a component")} - </SelectValue> + <SelectTrigger className="w-64"> + <SelectValue placeholder={isLoading ? "Loading components..." : "Select a component"} />
40-43: Minor: remove redundant filtering state.selectedCategory is fixed to "all"; filteredComponents mirrors components. Simplify by using components directly (or implement category selection).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
public/sitemap-0.xml(1 hunks)src/components/playground/auto-discovery.ts(1 hunks)src/components/playground/playground-header.tsx(2 hunks)src/components/playground/playground.tsx(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/playground/playground.tsx (1)
src/components/playground/auto-discovery.ts (1)
discoverComponent(202-241)
src/components/playground/playground-header.tsx (3)
src/components/playground/playground-context.tsx (1)
usePlayground(257-263)src/components/playground/types.ts (1)
ComponentConfig(1-10)src/components/playground/auto-discovery.ts (1)
discoverComponents(132-196)
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 86-86: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(<${pascalName}[^>]*(?:>|\\/>)[\\s\\S]*?(?:<\\/${pascalName}>|(?=\\n\\s*\\))), 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (1)
src/components/playground/playground.tsx (1)
55-69: Slug parsing + discovery hookup looks good.Decoding, normalization, and guarded selection are correct.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/components/playground/playground-header.tsx (1)
21-30: Lazy list + on-demand load looks good; resolves earlier eager-import concern.Switched to getComponentList() on mount and discoverComponent() on selection. This addresses the prior perf warning in the header.
Also applies to: 44-59
🧹 Nitpick comments (8)
src/components/playground/playground-header.tsx (4)
16-16: Remove unused category state and simplify filtering.selectedCategory is fixed to "all" and never updated; filteredComponents is thus always componentList. Simplify and avoid dead state.
Apply:
- const [selectedCategory] = useState<string>("all"); - const filteredComponents = selectedCategory === "all" - ? componentList - : componentList.filter(comp => comp.category === selectedCategory); + // Category filtering not yet implemented; use full list. + const filteredComponents = componentList;Also applies to: 40-43, 132-134
126-126: Tailwind class likely invalid: use min-w-[16rem] instead of min-w-64.Tailwind provides w-64 but not min-w-64. Use an arbitrary value class.
- <SelectContent className="w-64 min-w-64 max-h-96 overflow-y-auto"> + <SelectContent className="w-64 min-w-[16rem] max-h-96 overflow-y-auto">
22-30: Guard setState on unmount.Add AbortController or a mounted flag to avoid state updates after unmount.
useEffect(() => { - async function loadComponentList() { + let cancelled = false; + async function loadComponentList() { setIsLoading(true); const list = await getComponentList(); - setComponentList(list); - setIsLoading(false); + if (!cancelled) { + setComponentList(list); + setIsLoading(false); + } } loadComponentList(); - }, []); + return () => { + cancelled = true; + }; + }, []);
120-124: SelectValue children are unnecessary with shadcn/ui.SelectValue typically uses the placeholder prop and renders selected text automatically; custom children here likely won’t render. Keep placeholder logic; drop children to reduce noise.
- <SelectValue placeholder={isLoading ? "Loading components..." : "Select a component"}> - {isLoadingComponent - ? "Loading component..." - : state.selectedComponent?.name || (isLoading ? "Loading..." : "Select a component")} - </SelectValue> + <SelectValue placeholder={isLoading ? "Loading components..." : (isLoadingComponent ? "Loading component..." : "Select a component")} />src/components/playground/auto-discovery.ts (4)
202-218: Improve name normalization for acronyms and number↔letter boundaries.Handle “APICard” → “api-card” and “v2Plan”/“plan2X” cases.
-function normalizeComponentName(name: string): string { - return name - // Insert hyphens between lower→upper transitions (decamelize) - .replace(/([a-z])([A-Z])/g, '$1-$2') - // Insert hyphens between letter→number transitions - .replace(/([a-zA-Z])([0-9])/g, '$1-$2') - // Lowercase everything - .toLowerCase() - // Replace whitespace with hyphens - .replace(/\s+/g, '-') - // Remove non a-z0-9- characters - .replace(/[^a-z0-9-]/g, '') - // Collapse multiple hyphens - .replace(/-+/g, '-') - // Trim leading/trailing hyphens - .replace(/^-+|-+$/g, ''); -} +function normalizeComponentName(name: string): string { + return name + .trim() + // lower/number -> Upper (e.g., "planId" → "plan-Id") + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + // ACRONYM -> Word (e.g., "APIClient" → "API-Client") + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') + // number -> letter and letter -> number + .replace(/([0-9])([a-zA-Z])/g, '$1-$2') + .replace(/([a-zA-Z])([0-9])/g, '$1-$2') + // spaces/underscores -> hyphens + .replace(/[\s_]+/g, '-') + // strip invalid + .replace(/[^a-zA-Z0-9-]/g, '') + // collapse hyphens + .replace(/-+/g, '-') + // trim hyphens + .replace(/^-+|-+$/g, '') + .toLowerCase(); +}
151-183: Avoid unbounded parallel fetch/imports; limit concurrency.Large registries can spike network/CPU and bloat memory. Process in small batches.
- // Process each component in parallel for better performance - const results = await Promise.allSettled( - componentItems.map(async (item) => { - try { - // Fetch detailed metadata - const metadata = await fetchComponentMetadata(item.name); - - // Dynamically import the component - const Component = await importComponent(item.name); - - if (!Component) { - console.warn(`Skipping ${item.name}: Component not found`); - return null; - } - - const config: ComponentConfig = { - id: item.name, - name: item.title, - description: item.description, - category: categorizeComponent(item.name), - component: Component, - imports: [`@/components/billingsdk/${item.name}`], - defaultCode: extractUsageSnippet(metadata, item.name), - defaultProps: {}, - }; - - return config; - } catch (error) { - console.error(`Failed to process component ${item.name}:`, error); - return null; - } - }) - ); + const concurrency = 6; + const results: PromiseSettledResult<ComponentConfig | null>[] = []; + for (let i = 0; i < componentItems.length; i += concurrency) { + const slice = componentItems.slice(i, i + concurrency); + // process a batch + const batch = await Promise.allSettled( + slice.map(async (item) => { + try { + const metadata = await fetchComponentMetadata(item.name); + const Component = await importComponent(item.name); + if (!Component) { + console.warn(`Skipping ${item.name}: Component not found`); + return null; + } + return { + id: item.name, + name: item.title, + description: item.description, + category: categorizeComponent(item.name), + component: Component, + imports: [`@/components/billingsdk/${item.name}`], + defaultCode: extractUsageSnippet(metadata, item.name), + defaultProps: {}, + } as ComponentConfig; + } catch (error) { + console.error(`Failed to process component ${item.name}:`, error); + return null; + } + }) + ); + results.push(...batch); + }
27-36: Memoize registry/metadata to reduce redundant fetches.getComponentList/getComponentNames/discovery all hit the manifest; add module-level caching to cut network calls on the same page load.
+let manifestCache: Promise<RegistryManifest | null> | null = null; async function fetchRegistryManifest(): Promise<RegistryManifest | null> { - try { + try { - const response = await fetch('/r/registry.json'); + if (!manifestCache) { + manifestCache = (async () => { + const response = await fetch('/r/registry.json'); + if (!response.ok) return null; + return await response.json(); + })(); + } - if (!response.ok) return null; - return await response.json(); + return await manifestCache; } catch (error) { console.error('Failed to fetch registry manifest:', error); return null; } }Optionally cache per-component metadata with a Map<string, Promise>.
Also applies to: 276-296, 301-313
149-150: Reduce console noise in production.Wrap logs behind a debug flag or use a logger with levels to avoid noisy consoles in prod.
Also applies to: 194-196, 229-231, 254-255
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/playground/auto-discovery.ts(1 hunks)src/components/playground/playground-header.tsx(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/playground/playground-header.tsx (2)
src/components/playground/playground-context.tsx (1)
usePlayground(257-263)src/components/playground/auto-discovery.ts (3)
ComponentListItem(265-270)getComponentList(276-296)discoverComponent(224-260)
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 86-86: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(<${pascalName}[^>]*(?:>|\\/>)[\\s\\S]*?(?:<\\/${pascalName}>|(?=\\n\\s*\\))), 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/playground/auto-discovery.ts (1)
156-220: Note: Eager import function still exported.The
discoverComponents()function imports all components up front (lines 175-183), which past reviews flagged as bloating initial load. WhilegetComponentList()now provides a lightweight alternative, this function remains exported.If
discoverComponents()is no longer needed for the primary use case, consider:
- Deprecating it with a JSDoc
@deprecatedtag directing users togetComponentList()+discoverComponent()- Documenting when it should be used (e.g., bulk pre-loading scenarios)
- Making it internal (not exported) if there's no valid use case
Would you like me to generate a deprecation notice or alternative approach?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/playground/auto-discovery.ts(1 hunks)
🧰 Additional context used
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 102-105: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<${escapedName}[^>]{0,500}(?:>|\\/>)(?:[^<]{0,2000}|(?:<(?!\\/${escapedName}>)[^>]{0,100}>){0,50})*?(?:<\\/${escapedName}>|(?=\\n\\s*\\))),
'i'
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (6)
src/components/playground/auto-discovery.ts (6)
1-78: LGTM! Solid foundation with proper error handling.The type definitions, fetch utilities, and helper functions are well-structured. Error handling is appropriate with try-catch blocks and null returns. The
regexEscapefunction properly addresses injection concerns.
84-129: Regex ReDoS mitigations in place; static analysis warning acknowledged.The implementation includes multiple defenses against ReDoS attacks:
- Input size capped at 50KB (line 95)
- Variable escaped with
regexEscape(line 99)- Bounded quantifiers throughout (lines 103-106)
Static analysis tools still flag the RegExp constructor pattern, but the mitigations significantly reduce the risk. The bounded quantifiers (
{0,500},{0,2000}, etc.) prevent catastrophic backtracking.Based on static analysis hints
134-151: Verify registry integrity and consider path validation.The dynamic import on line 136 constructs paths from registry data (
componentName). While the path is scoped to@/components/billingsdk/, if the registry JSON is compromised, it could reference arbitrary modules within that directory.Consider adding explicit path validation to ensure
componentNamecontains only expected characters:async function importComponent(componentName: string): Promise<any> { try { + // Validate component name format (kebab-case only) + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(componentName)) { + console.error(`Invalid component name format: ${componentName}`); + return null; + } + const module = await import(`@/components/billingsdk/${componentName}`);This ensures only properly formatted kebab-case component names can be imported, reducing the attack surface if the registry is compromised.
226-242: LGTM! Proper PascalCase → kebab-case normalization.The function correctly handles the case conversion issue flagged in previous reviews. It properly decamelizes PascalCase/camelCase with transition detection, normalizes spacing, and cleans up edge cases.
Based on past review comments (now resolved)
248-284: LGTM! Clean single-component discovery.The function properly normalizes component names, handles import failures gracefully, and provides useful logging. Error handling is appropriate with try-catch and null returns.
289-337: LGTM! Lightweight API addresses eager-loading concern.The
getComponentList()function andComponentListIteminterface provide metadata-only access without importing actual components, effectively solving the bundle bloat issue raised in past reviews. This is the correct pattern for initial loading.Based on past review comments (now resolved)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
src/components/playground/auto-discovery.ts (5)
134-151: Dynamic import path creates a large context chunk; prefer explicit module map.
import(\@/components/billingsdk/${componentName}`)` makes bundlers include the entire directory in a single context chunk, undermining code‑splitting and bloating first load.
- If using Vite: use import.meta.glob to build a map and load only the matched module.
- If using Next/Webpack: generate a loader map at build time (from the registry) or a narrow require.context.
Please confirm bundler. I can provide a concrete patch (Vite glob or Next/Webpack map) once confirmed. Example (Vite):
// top-level const modules = import.meta.glob('@/components/billingsdk/**/*.{tsx,jsx,ts,js}', { eager: false }); async function importComponent(componentName: string) { const entry = Object.keys(modules).find( (k) => k.endsWith(`/${componentName}.tsx`) || k.endsWith(`/${componentName}/index.tsx`) ); return entry ? (await modules[entry]())?.default ?? (await modules[entry]())[toPascalCase(componentName)] : null; }
226-242: Strengthen name normalization (handle acronyms and digit boundaries).Current regex misses cases like “UIForm”, “XMLHttp”, and number→letter. Use a more robust pipeline.
Apply:
-function normalizeComponentName(name: string): string { - return name - // Insert hyphens between lower→upper transitions (decamelize) - .replace(/([a-z])([A-Z])/g, '$1-$2') - // Insert hyphens between letter→number transitions - .replace(/([a-zA-Z])([0-9])/g, '$1-$2') - // Lowercase everything - .toLowerCase() - // Replace whitespace with hyphens - .replace(/\s+/g, '-') - // Remove non a-z0-9- characters - .replace(/[^a-z0-9-]/g, '') - // Collapse multiple hyphens - .replace(/-+/g, '-') - // Trim leading/trailing hyphens - .replace(/^-+|-+$/g, ''); -} +function normalizeComponentName(name: string): string { + return name + .trim() + .replace(/[_\s]+/g, '-') + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // lower/digit → Upper + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') // ACRONYM→Capitalized + .replace(/([a-zA-Z])([0-9])/g, '$1-$2') // letter → digit + .replace(/([0-9])([a-zA-Z])/g, '$1-$2') // digit → letter + .toLowerCase() + .replace(/[^a-z0-9-]/g, '') + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, ''); +}
27-36: Add timeout and lightweight cache for fetches; consider SSR-safe URLs.Avoid hanging fetches and duplicate manifest downloads; also ensure SSR calls don’t use relative “/r/...”.
Apply caching + timeout to manifest; replicate to metadata as needed:
+let manifestCache: { data: RegistryManifest | null; ts: number } = { data: null, ts: 0 }; +const MANIFEST_TTL_MS = 60_000; + async function fetchRegistryManifest(): Promise<RegistryManifest | null> { - try { - const response = await fetch('/r/registry.json'); + const now = Date.now(); + if (manifestCache.data && now - manifestCache.ts < MANIFEST_TTL_MS) return manifestCache.data; + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 8000); + // If SSR, prefer absolute base (e.g., NEXT_PUBLIC_BASE_URL); keep relative for CSR. + const url = typeof window === 'undefined' && process.env.NEXT_PUBLIC_BASE_URL + ? new URL('/r/registry.json', process.env.NEXT_PUBLIC_BASE_URL).toString() + : '/r/registry.json'; + const response = await fetch(url, { signal: controller.signal } as RequestInit); + clearTimeout(timer); if (!response.ok) return null; - return await response.json(); + const data = await response.json(); + manifestCache = { data, ts: now }; + return data; } catch (error) { console.error('Failed to fetch registry manifest:', error); return null; } }And for metadata:
async function fetchComponentMetadata(componentName: string): Promise<any> { try { - const response = await fetch(`/r/${componentName}.json`); + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 8000); + const url = typeof window === 'undefined' && process.env.NEXT_PUBLIC_BASE_URL + ? new URL(`/r/${componentName}.json`, process.env.NEXT_PUBLIC_BASE_URL).toString() + : `/r/${componentName}.json`; + const response = await fetch(url, { signal: controller.signal } as RequestInit); + clearTimeout(timer); if (!response.ok) return null; return await response.json(); } catch (error) {Confirm runtime (Next vs Vite) so we can align the base URL approach.
Also applies to: 41-50
314-320: Return lists in stable, sorted order.Sorting improves UX and determinism.
Apply:
return componentItems.map(item => ({ id: item.name, name: item.title, description: item.description, category: categorizeComponent(item.name), - })); + })).sort((a, b) => a.name.localeCompare(b.name));return manifest.items .filter(item => item.type === 'registry:block' && item.name !== 'index' && item.name !== 'all' && item.name !== 'hello-world' ) - .map(item => item.name); + .map(item => item.name) + .sort((a, b) => a.localeCompare(b));Also applies to: 329-337
173-174: Gate console logs behind a debug flag.Avoid noisy logs in production; keep them when developing.
Example:
const DEBUG = process.env.NODE_ENV !== 'production'; // then: if (DEBUG) console.log(...)Also applies to: 218-219, 253-254, 278-279
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/playground/auto-discovery.ts(1 hunks)
🧰 Additional context used
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 102-105: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<${escapedName}[^>]{0,500}(?:>|\\/>)(?:[^<]{0,2000}|(?:<(?!\\/${escapedName}>)[^>]{0,100}>){0,50})*?(?:<\\/${escapedName}>|(?=\\n\\s*\\))),
'i'
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (3)
src/components/playground/auto-discovery.ts (3)
114-117: Regex escaping fixed correctly.The fallback pattern’s escapes are now correct (single-escaped in string literal). Resolves the prior critical issue.
101-107: Regex hardening looks good.Variable is escaped, input is capped (50KB), and bounded quantifiers reduce backtracking. This addresses the ReDoS concerns.
Based on static analysis hints
156-220: Ensure this batch importer isn’t used on initial render paths.This imports all components and fetches all metadata in parallel; great for prefetch/dev, heavy for initial page load.
Please confirm header/playground only call getComponentList() initially and discoverComponent() on selection (not discoverComponents()).
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
@yashranaway can we need to get rid of |
sure |
|
@tsahil01 lmk if anything else needs updating, major cleaning lol |
|
I will check and fix this |
|
@tsahil01 it is now fixed, some components when first time loaded take around ~1-2 s to load but that is i think due to local hosting, but overall the issue is gone |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/playground/auto-discovery.ts (1)
94-126: Consider reducing console verbosity in production.The multi-path import strategy is excellent and the logging helps debugging. However, the extensive console.log statements (lines 97, 102, 111, 116) may clutter production consoles.
Consider wrapping debug logs in a condition:
const DEBUG = process.env.NODE_ENV === 'development'; if (DEBUG) console.log(`[Import] Attempting: @/components/billingsdk/${componentName}`);Or use a logging library with configurable levels.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/playground/auto-discovery.ts(1 hunks)src/components/playground/playground-header.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/playground/playground-header.tsx
🧰 Additional context used
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 70-73: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<${escapedName}[^>]{0,500}(?:>|\\/>)(?:[^<]{0,2000}|(?:<(?!\\/${escapedName}>)[^>]{0,100}>){0,50})*?(?:<\\/${escapedName}>|(?=\\n\\s*\\))),
'i'
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (6)
src/components/playground/auto-discovery.ts (6)
1-28: LGTM! Clean type definitions and fetch logic.The registry interfaces are well-structured, and
fetchRegistryManifesthas appropriate error handling.
41-58: LGTM! Utility functions are well-implemented.The PascalCase conversion, categorization logic, and regex escaping are all correct.
60-92: Regex patterns have good ReDoS protections, but remain complex.The mitigations (50KB input cap, escaped variables, bounded quantifiers) significantly reduce ReDoS risk. However, the nested quantifiers with negative lookahead (line 72) can still cause backtracking on pathological inputs. The current safeguards are acceptable for production use.
Consider testing the regex with edge cases such as deeply nested component trees or malformed JSX to confirm performance remains acceptable. Based on static analysis hints
128-182: LGTM! Robust parallel discovery with proper error handling.The use of
Promise.allSettledensures all components are processed even if some fail, and per-item error handling is comprehensive.
184-193: Excellent normalization logic that handles all edge cases.The multi-step normalization correctly converts PascalCase, handles numbers, and cleans up edge cases. This fully addresses the previous review feedback.
195-224: LGTM! Robust fallback strategy for missing metadata.The function correctly proceeds with sensible defaults when metadata is unavailable, only returning null if the component import itself fails. This addresses previous review feedback.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/components/playground/auto-discovery.ts (2)
101-133: Consider adding a debug flag or using a logging library.The function contains extensive console logging (lines 104, 109, 113, 115, 118, 123, 127, 129, 131) that may be verbose in production. Consider wrapping these in a debug flag or using a proper logging library with configurable levels.
55-61: Categorization logic could be more maintainable.The cascading
ifstatements with keyword checks work but could be refactored into a configuration map for easier maintenance and extension.Example structure:
const categoryPatterns = [ { patterns: ['pricing-table'], category: 'pricing' }, { patterns: ['subscription', 'plan', 'cancel', 'update'], category: 'subscription' }, { patterns: ['payment', 'invoice', 'billing'], category: 'payment' }, { patterns: ['usage', 'meter', 'proration', 'upcoming-charges', 'detailed-usage'], category: 'usage' }, ]; function categorizeComponent(name: string): string { for (const { patterns, category } of categoryPatterns) { if (patterns.some(p => name.includes(p))) return category; } return 'ui'; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/playground/auto-discovery.ts(1 hunks)
🧰 Additional context used
🪛 ast-grep (0.39.6)
src/components/playground/auto-discovery.ts
[warning] 77-80: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<${escapedName}[^>]{0,500}(?:>|\\/>)(?:[^<]{0,2000}|(?:<(?!\\/${escapedName}>)[^>]{0,100}>){0,50})*?(?:<\\/${escapedName}>|(?=\\n\\s*\\))),
'i'
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (4)
src/components/playground/auto-discovery.ts (4)
19-24: Good refactor—DRY principle applied.Extracting the filtering predicate into a reusable helper addresses the previous feedback about duplicate logic.
63-99: Regex safety mitigations in place; static analysis warning persists.The implementation now includes proper escaping (line 77), content size limits (line 75), and bounded quantifiers (lines 78-80, 87) to mitigate ReDoS risks. The static analysis warning at lines 77-80 is expected when constructing regex from variables, but the mitigations reduce the actual risk.
186-195: Robust normalization—addresses previous feedback.The implementation now properly handles PascalCase/camelCase → kebab-case conversion (line 188), letter-number transitions (line 189), and edge cases like multiple hyphens and trimming.
197-226: Graceful fallback for missing metadata—good improvement.The function now proceeds with sensible defaults when metadata is missing (lines 202-204) and uses optional chaining (lines 212-213, 217) to safely access metadata properties. This ensures deep links work even when metadata fetch fails.
|
@tsahil01 can you take a look |
|
@tsahil01 any update? |
|
lgtm @yashranaway! There are some issues when loading certain components, though. It may not be because of this PR, but we will need to consider that as well.
Just cleanup the console logs and I think we should be good to go |
That is actually planned for a separate PR since not all components are playground compatible I'll be making a new PR fixing all of them I'll cover this in that. |
There was a problem hiding this comment.
lgtm! @yashranaway please cleanup those console.logs as well when switching a component
done |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/proration-preview-demo.tsx (1)
97-104: Add WebKit scrollbar hiding and consider accessibility.The inline styles hide scrollbars for Firefox and IE/Edge but not for Chrome/Safari (WebKit browsers). Additionally, hiding scrollbars without a visual cue (fade effect, shadow, or indicator) may reduce discoverability for users who need to know additional tabs exist.
To hide scrollbars across all browsers, add a global CSS rule targeting this component:
.scrollable-tabs::-webkit-scrollbar { display: none; }Then update the JSX:
- <TabsList className="flex w-full overflow-x-auto gap-1 p-1 rounded-lg" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}> + <TabsList className="scrollable-tabs flex w-full overflow-x-auto gap-1 p-1 rounded-lg" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>Additionally, consider adding a subtle fade or shadow at the edges to indicate scrollability:
- <div className="overflow-hidden"> + <div className="relative overflow-hidden after:absolute after:right-0 after:top-0 after:h-full after:w-8 after:bg-gradient-to-l after:from-background after:to-transparent after:pointer-events-none">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/proration-preview-demo.tsx(7 hunks)src/registry/billingsdk/demo/proration-preview-demo.tsx(6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T13:12:09.970Z
Learnt from: rajdesai17
Repo: dodopayments/billingsdk PR: 278
File: src/registry/billingsdk/demo/limited-offer-dialog-demo.tsx:1-55
Timestamp: 2025-10-03T13:12:09.970Z
Learning: In the dodopayments/billingsdk repository, demo files should be duplicated in both `src/components/` and `src/registry/billingsdk/demo/` directories with identical implementations. Do not suggest re-exporting or consolidating these demo files.
Applied to files:
src/registry/billingsdk/demo/proration-preview-demo.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Typecheck and Build
🔇 Additional comments (1)
src/components/proration-preview-demo.tsx (1)
122-123: LGTM — No-op callbacks appropriate for demo.Replacing console.log with empty callbacks removes unnecessary side effects in the demo and aligns with the auto-discovery system's cleaner demo behavior.
Also applies to: 153-154, 176-177, 197-198, 220-221, 241-242



Summary
Implement a zero-maintenance playground component loader by replacing the manual registry with an auto-discovery system that scans the public registry JSON and loads components dynamically.
Changes
src/components/playground/auto-discovery.tsto fetch registry metadata, import components on demand, and infer usage snippets.src/components/playground/playground-header.tsxandsrc/components/playground/playground.tsxto rely on the discovery utility for the selector and deep-link handling.AUTO_DISCOVERY.mdto document architecture, benefits, setup, and troubleshooting.Screenshots/Recordings (if UI)
No UI changes requiring screenshots.
How to Test
npm cinpm run typechecknpm run buildChecklist
Summary by CodeRabbit
New Features
Bug Fixes
Chores