feat: add new flux models 4b and 9b - #27
Simplereally wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis pull request introduces a comprehensive development infrastructure update, adding development guidelines, enhanced ESLint rules, new UI components (sidebar system, image lightbox), Convex backend modules for generated images, SEO solution configurations, and model registry improvements with accompanying documentation and automation scripts. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes The scope is substantial and heterogeneous—spanning documentation, UI components with intricate interaction patterns (lightbox with zoom/pan/drag, sidebar system with collapsible modes, prompt section with debounce/keyboard handling), backend Convex modules with complex database queries and authorization logic, configuration systems with multiple data shapes, and testing infrastructure. While individual areas follow consistent patterns, the diversity of changes across the stack and logic density in several components (lightbox-components at 413 lines, sidebar-menu at 272 lines, generatedImages queries at 433 lines) requires careful, separate reasoning for each cohort. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/ui/slideshow.test.tsx (1)
27-27: Minor typo in test description."renders rendering null" appears to be a typo — should be "renders null if no slides".
📝 Suggested fix
- it("renders rendering null if no slides", () => { + it("renders null if no slides", () => {
🤖 Fix all issues with AI agents
In `@components/images/image-lightbox/index.tsx`:
- Around line 189-213: Update the LightboxImage interface to use the correct
Convex Id type and remove the unnecessary cast: change the _id property in the
LightboxImage interface (in hooks/use-image-lightbox.ts) from _id?: string to
_id?: Id<"generatedImages">, ensure the Id type is imported where the interface
is declared, and then remove the redundant cast in useLightboxData (the
image?._id as Id<"generatedImages">) so the code uses the typed _id directly.
In `@components/studio/controls/prompt-section-parts.tsx`:
- Around line 23-43: The ClearPromptButton renders an icon-only button without
an accessible name; update the ClearPromptButton component (function
ClearPromptButton) to provide an accessible label for screen readers by adding
an aria-label (e.g., aria-label="Clear prompt") to the Button or by including
visually hidden text inside the Button (e.g., a span with a screen-reader-only
class and the text "Clear prompt"), ensuring the control has a programmatic name
while keeping the visual icon-only appearance.
- Around line 50-95: The history toggle Button in the PromptHeader component
(the Button with data-testid="history-toggle" that calls onToggleHistory and
renders the History icon) is icon-only and needs an accessible name; add an
aria-label (e.g., aria-label="Show recent prompts" or similar) or include
visually-hidden text associated with the Button so screen readers can announce
it, ensuring the Button remains variant="ghost" size="icon" and behavior
unchanged.
- Around line 213-235: The SuggestionChips component currently renders
interactive suggestion items using the Badge component (which outputs a span)
making them inaccessible via keyboard; replace each Badge with the Button
component (ensure Button is imported) inside SuggestionChips, preserve props
like key, variant/intent, className ("cursor-pointer hover:bg-primary/20
transition-colors text-xs"), and onClick by using Button type="button" and
onClick={() => onSuggestionClick(suggestion)}; also include an appropriate
aria-label (e.g., `aria-label={`Use suggestion ${suggestion}`}`) so screen
readers announce the action and keep the Lightbulb usage and
isLoadingSuggestions styling unchanged.
In `@components/studio/layout/sidebar-generate-button.test.tsx`:
- Around line 10-23: The test mock uses React.ReactNode but lacks an explicit
React type import; add a type-only import "import type { ReactNode } from
'react';" at the top and update the mock prop typing to use ReactNode instead of
React.ReactNode (the mock component named Button in the test file should be
updated accordingly) so TypeScript with the modern JSX runtime can resolve the
type.
In `@convex/generatedImages/queries.ts`:
- Around line 307-312: The filter currently only excludes null isSensitive
values in the query inside the .filter((q) => q.and(...)) block; update the
predicate that references q.field("isSensitive") (in the same .filter lambda) to
exclude both null and undefined so legacy rows are omitted — e.g., add a second
condition alongside q.neq(q.field("isSensitive"), null) to also check
q.neq(q.field("isSensitive"), undefined) or replace with an existence check
(e.g., q.exists(q.field("isSensitive"))), ensuring the change is applied where
q.field("isSensitive") is used.
- Around line 231-237: The filter currently uses q.neq(q.field("isSensitive"),
null) which still allows undefined values; change the predicate to explicitly
require a boolean value (e.g. use q.in(q.field("isSensitive"), [true, false]) or
an explicit boolean check) so only analyzed records with isSensitive true/false
pass the filter; update the filter expression that references
q.field("isSensitive") and replace q.neq(...) with the boolean inclusion check
(or equivalent q.eq/q.or combination) to exclude undefined legacy records.
- Around line 318-323: The paginatedResult.page mapping currently uses a type
cast "as EnrichedImage[]" on enrichedPage; replace the cast with an explicit
typed variable declaration so TypeScript infers safety—declare enrichedPage:
EnrichedImage[] = paginatedResult.page.map(...) rather than casting, keeping the
same mapping logic that spreads image and adds ownerName/ownerPictureUrl and
referencing paginatedResult, enrichedPage, EnrichedImage, and user to locate the
change.
In `@lib/config/aspect-ratios.ts`:
- Around line 16-49: The FLUX_SCHNELL_ASPECT_RATIOS constant contains entries
whose width×height values (e.g., entries with value "16:9", "9:16", "4:3",
"3:4", "21:9", "9:21") are not divisible by 65,536 as claimed in the header;
verify the actual backend constraint and either (A) change those entries in
FLUX_SCHNELL_ASPECT_RATIOS to dimensions that preserve the aspect ratio, remain
multiples of 8, do not exceed 768 on either side, and yield (width × height)
divisible by 65,536, or (B) update the header documentation to reflect the true
constraint; update the array entries and the comment together and re-run
withAspectRatioTags to ensure consistency.
In `@lib/seo/solution-types.ts`:
- Around line 17-35: The Solution, SolutionFeature, SolutionStep, and
SolutionFAQ interfaces are duplicated; update the module that currently
re-defines them to instead import and re-export those types from the canonical
definitions (Solution, SolutionFeature, SolutionStep, SolutionFAQ) so there is a
single source of truth; locate the duplicate definitions in the other module and
replace them with statements that import { Solution, SolutionFeature,
SolutionStep, SolutionFAQ } from the original module and then export them
(export type { Solution, SolutionFeature, SolutionStep, SolutionFAQ }) so all
consumers import the types from the same place and remove the duplicated type
declarations.
In `@pull-conflicts.md`:
- Around line 1-136: The file pull-conflicts.md contains local merge transcripts
and sensitive local info and must be removed from the repository: delete
pull-conflicts.md from the repo, remove it from the index (git rm --cached or
git rm) so it’s not committed, commit that deletion, and add an entry to
.gitignore to prevent re-adding similar local conflict transcripts; ensure no
other code references pull-conflicts.md (search for its filename) before
committing.
In `@scripts/fix-all-test-any.ps1`:
- Around line 10-13: When replacing '@ts-ignore' with '@ts-expect-error' in
scripts/fix-all-test-any.ps1, ensure you also insert a rationale comment after
the directive (e.g., append " // TODO: explain reason and add tracking link" or
the repo's required explanation) so the generated '@ts-expect-error' includes
the mandatory explanation; update the replacement logic that manipulates
$content (where it currently does $content -replace '@ts-ignore',
'@ts-expect-error') to produce '@ts-expect-error // <reason or tracking link>'
and keep setting $modified = $true.
- Around line 47-54: The script scripts/fix-all-test-any.ps1 currently replaces
'(children: any)' and '{ children }: any' with React.ReactNode but doesn't
ensure an import for React, which breaks TS; update the script to first detect
existing imports of React or ReactNode (e.g., "import React" or "import type {
ReactNode } from 'react'") and only perform the replacement if an appropriate
import exists, otherwise inject a minimal type import (preferably "import type {
ReactNode } from 'react'") at the top of the file and use the non-namespaced
replacement '(children: ReactNode)' and '{ children }: { children: ReactNode }';
ensure the detection/insertion logic is applied before doing replacements for
the patterns '(children: any)' and '{ children }: any' so files like
components/ui/*.test.tsx get correct imports.
🟡 Minor comments (14)
components/studio/features/generation/controls-view-badges.tsx-164-171 (1)
164-171: Consider grammatical correctness for singular count.When
countis 1, the badge will display "1 images" which is grammatically incorrect.📝 Proposed fix for pluralization
export function BatchModeBadge({ enabled, count }: BatchModeBadgeProps): React.ReactElement | null { if (!enabled) return null; + const label = count === 1 ? "image" : "images"; return ( <span className={cn(BADGE_BASE_CLASS, "tabular-nums")}> - {count} images + {count} {label} </span> ); }components/ui/sidebar.test.tsx-162-175 (1)
162-175: Test assertion doesn't match the test name.The test is named "does not apply translate class when mobile sidebar is open" but only asserts
data-mobile="true". Consider adding an assertion to verify the translate class behavior:Proposed fix
const sidebar = screen.getByTestId("sidebar") // When open=true is passed, the mobile state should be synced via useEffect expect(sidebar).toHaveAttribute("data-mobile", "true") + expect(sidebar).toHaveAttribute("data-state", "expanded") + expect(sidebar).not.toHaveClass("-translate-x-full") + expect(sidebar).not.toHaveClass("translate-x-full")components/studio/controls/megapixel-budget.test.tsx-1-9 (1)
1-9: ImportReactElementexplicitly to keep TS type resolution consistent.
UsingReact.ReactElementwithout importing the type is inconsistent with the codebase pattern (seecomponents/pollen-balance/pollen-balance-display.test.tsx). Import the type explicitly for clarity and to avoid potential TS resolution issues.♻️ Suggested fix
import { describe, it, expect } from "vitest" import { render, screen } from "@testing-library/react" +import type { ReactElement } from "react" import { MegapixelBudget } from "./megapixel-budget" // Wrap component with TooltipProvider for tooltip tests -function renderWithTooltip(ui: React.ReactElement) { +function renderWithTooltip(ui: ReactElement) { // TooltipProvider is likely already in the test setup via vitest.setup.ts // If not, the component should still render its visible content return render(ui) }lib/config/model-registry.ts-306-325 (1)
306-325: Turbo maxPixels appears off by one.
768×768is589_824, but the registry uses589_825, which is inconsistent and could confuse validation logic.🔧 Suggested fix
- maxPixels: 589_825, + maxPixels: 589_824,lib/schemas/pollinations-pricing.schema.ts-156-182 (1)
156-182: Pricing inconsistency confirmed for Klein models.All other image models in the schema maintain the relationship:
perImage = 1 / approximatePerPollen. However, Klein and Klein-large break this pattern:
- Klein:
1 ÷ 150 = 0.00667, butperImageis 0.008 (≈20% discrepancy)- Klein-large:
1 ÷ 85 = 0.01176, butperImageis 0.012 (≈2% discrepancy)Every other model (Flux, ZImage, Turbo, Seedream, Kontext, etc.) maintains consistency between
approximatePerPollenandperImage. Align these values and update the comment to match the authoritative pricing source..eslintcache-1-1 (1)
1-1: Avoid committing ESLint cache artifacts.This file is environment-specific and will create noisy diffs. Please remove it from the repo and add it to
.gitignore.🧹 Proposed .gitignore update
+.eslintcachescripts/fix-test-types.mjs-63-66 (1)
63-66: Hardcoded table name may produce incorrect ID types.Line 65 always replaces
"..._id" as anywithId<"generatedImages">, but IDs may reference other tables (e.g.,users,favorites,promptLibrary). This could introduce type errors or mask real issues.🔧 Suggested improvement
Consider inferring the table from the variable name or making this pattern more explicit:
- { from: /"[^"]+_id" as any/g, to: (match) => match.replace(' as any', ' as unknown as Id<"generatedImages">') }, + // Note: This pattern requires manual review - table name may vary + { from: /"[^"]+_id" as any/g, to: (match) => { + // TODO: Consider inferring table from context or variable name + console.warn(` ⚠ Manual review needed for ID type: ${match}`); + return match.replace(' as any', ' as unknown as Id<"generatedImages">'); + }},.github/skills/adding-convex-table/SKILL.md-141-143 (1)
141-143: Add a language identifier to the fenced block.The closing output-format code fence is missing a language tag, which triggers markdownlint (MD040). Add a language (e.g.,
markdown) to keep lint clean..github/skills/creating-client-component/SKILL.md-20-28 (1)
20-28: Add a language identifier to the decision-tree fence.Markdownlint’s MD040 is triggered because this fence has no language.
📝 Suggested fix
-``` +```text Need any of these? ├─ useState, useEffect, useRef → Client ├─ onClick, onChange, onSubmit → Client ├─ useRouter, usePathname → Client ├─ useQuery, useMutation (Convex) → Client ├─ Browser APIs (localStorage, window) → Client └─ None of above → Server Component (no 'use client') -``` +```eslint.config.mjs-16-18 (1)
16-18: Update the “test files” comment to match actual scope.
This block now applies to all TS/TSX files, so the comment is misleading.📝 Suggested edit
- // Override rules for test files + // TypeScript strict rules (applies to all TS/TSX files)LINT_STATUS.md-14-14 (1)
14-14: Align the max-lines note with the actual ESLint config.
The config setsmax-linesto 400 for all TS/TSX with no test override; either update this line or add a test-specific override.📝 Possible doc tweak
-- `max-lines`: 400 (600 for tests) +- `max-lines`: 400 (tests currently use the same limit unless overridden)lint_reports/COMPLETION_SUMMARY.md-105-126 (1)
105-126: Add languages to fenced code blocks (MD040).
markdownlint flags unlabeled fences. Add a language such astextorshellfor each block.📝 Example fix (apply to all fenced blocks)
-``` +```text ✅ prompt-section.test.tsx: 22 tests passed ✅ controls-view.test.tsx: 16 tests passed ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total: 38/38 tests passing (100%)</details> </blockquote></details> <details> <summary>convex/generatedImages/helpers.ts-1-4 (1)</summary><blockquote> `1-4`: **Module comment says “no database access,” but `enrichImages` queries users.** Please align the header comment with actual behavior to avoid confusion for future readers. <details> <summary>🛠️ Suggested tweak</summary> ```diff - * Pure functions that transform data - no database access. + * Helper functions for generated images queries. + * Note: enrichImages performs DB lookups for owner display data.Also applies to: 28-45
convex/generatedImages/mutations.ts-40-52 (1)
40-52: Use providedaspectRatiowhen width/height are missing.
aspectRatiois accepted in args but never stored; if width/height aren’t provided, the field becomes undefined and downstream filters won’t work as intended.🛠️ Proposed fix
- aspectRatio: args.width && args.height - ? Math.max(args.width, args.height) / Math.min(args.width, args.height) - : undefined, + aspectRatio: args.aspectRatio ?? ( + args.width && args.height + ? Math.max(args.width, args.height) / Math.min(args.width, args.height) + : undefined + ),
🧹 Nitpick comments (30)
components/ui/slideshow.test.tsx (1)
10-19: Consider fixinganytype in theuseSlideshowmock for consistency.Since this PR improves type safety in the NextImage mock, consider also addressing the
anytype on line 12 to align with coding guidelines and maintain consistency across mocks.♻️ Suggested fix
// Mock hook vi.mock("@/hooks/use-slideshow", () => ({ - useSlideshow: ({ totalSlides }: any) => ({ + useSlideshow: ({ totalSlides }: { totalSlides: number }) => ({ activeIndex: 0, setActiveIndex: vi.fn(), next: vi.fn(), prev: vi.fn(), setIsHovering: vi.fn(), }) }));components/gallery/feed-client.test.tsx (1)
335-350: Consider using fake timers for more deterministic test behavior.Increasing the timeout to 500ms improves reliability but adds wall-clock time to test execution and remains timing-dependent. Using
vi.useFakeTimers()would make this test fully deterministic.♻️ Suggested refactor using fake timers
it("does not call load more when already loading", async () => { + vi.useFakeTimers(); // Make the server action slow mockLoadPublicFeedPage.mockImplementation( - () => new Promise<PaginatedFeedResult>((resolve) => setTimeout(() => resolve(mockSecondPage), 500)) + () => new Promise<PaginatedFeedResult>((resolve) => setTimeout(() => resolve(mockSecondPage), 100)) ); const user = userEvent.setup(); render(<FeedClient feedType="public" initialPage={mockInitialPage} />); // Click load more twice quickly await user.click(screen.getByTestId("load-more-btn")); await user.click(screen.getByTestId("load-more-btn")); // Should only be called once expect(mockLoadPublicFeedPage).toHaveBeenCalledTimes(1); + + // Cleanup: advance timers and restore + await vi.runAllTimersAsync(); + vi.useRealTimers(); });components/studio/features/generation/controls-view-badges.tsx (1)
52-70: Non-null assertion is safe but consider simplifying.The
modelData.logoaccess on line 58 is safe becausehasLogoguards it. However, the pattern could be slightly cleaner by extracting the logo variable earlier.✨ Optional simplification
export function ModelBadge({ modelData, modelId }: ModelBadgeProps): React.ReactElement { - const hasLogo = !!modelData?.logo; + const logo = modelData?.logo; return ( <span className={cn(BADGE_BASE_CLASS, "truncate max-w-[140px]")}> - {hasLogo ? ( + {logo ? ( <Image - src={modelData.logo} + src={logo} alt="" width={14} height={14} - className={cn("shrink-0", shouldInvertLogo(modelData.logo) && "dark:invert")} + className={cn("shrink-0", shouldInvertLogo(logo) && "dark:invert")} /> ) : (components/studio/features/generation/controls-view-sections.tsx (1)
83-130: Consider converting render functions to React components.The
renderVideoSectionsfunction returnsReact.ReactNodeand receives props, which is the signature of a React component. Converting these to proper components (e.g.,VideoSections) would:
- Enable React DevTools to display meaningful component names
- Allow React to optimize reconciliation better
- Follow idiomatic React patterns
This is a minor stylistic preference and the current implementation is functionally correct.
✨ Example conversion to component
-export function renderVideoSections({ +export function VideoSections({ videoFramesProps, videoSettingsProps, videoFrameCount, supportsInterpolation, supportsAudio, isGenerating, onClearVideoFrames, -}: VideoSectionsProps): React.ReactNode { +}: VideoSectionsProps): React.ReactElement { return ( <> {/* ... rest unchanged ... */} </> ); }components/studio/layout/sidebar-content.test.tsx (2)
42-55: Mock doesn't forwardviewportRef, leaving ref-based scroll logic untested.The
viewportRefprop is declared in the mock's type signature but never forwarded to the rendered div. This means tests won't verify that the component correctly wires up the ref for scroll state initialization.🔧 Suggested improvement
vi.mock("@/components/ui/scroll-area", () => ({ ScrollArea: ({ children, onScroll, + viewportRef, }: { children: React.ReactNode; onScroll?: (e: React.UIEvent<HTMLDivElement>) => void; viewportRef?: React.Ref<HTMLDivElement>; }) => ( - <div data-testid="scroll-area" onScroll={onScroll}> + <div data-testid="scroll-area" onScroll={onScroll} ref={viewportRef}> {children} </div> ), }));
189-195: Fade overlay test relies on implementation detail (class name).Querying by
.pointer-events-nonecouples the test to a Tailwind utility class. If styling changes, this test could fail even though functionality remains correct. Consider addingdata-testidattributes to the overlay elements for more stable assertions.components/studio/layout/sidebar-content.tsx (1)
85-91: Consider a more reliable approach than arbitrary timeout.The 100ms timeout works but is a magic number. Content might take longer to render in some cases, or the delay might be unnecessary in others.
A
ResizeObserveron the scroll viewport would be more reliable—it fires when content actually changes size, ensuring fades update correctly regardless of render timing.🔧 Alternative using ResizeObserver
React.useEffect(() => { const el = scrollViewportRef.current; if (!el) return; const observer = new ResizeObserver(() => { updateScrollFades(el); }); observer.observe(el); // Initial check updateScrollFades(el); return () => observer.disconnect(); }, [updateScrollFades]);convex/lib/batchTypes.ts (1)
84-100: Duplicate validator — consider reusing fromconvex/generatedImages/types.ts.This
generationParamsValidatoris identical to the one exported fromconvex/generatedImages/types.ts(lines 12-27). To avoid drift and reduce maintenance burden, consider importing and re-exporting from that module instead.♻️ Suggested refactor
-export const generationParamsValidator = v.object({ - prompt: v.string(), - negativePrompt: v.optional(v.string()), - model: v.optional(v.string()), - width: v.optional(v.number()), - height: v.optional(v.number()), - seed: v.optional(v.number()), - enhance: v.optional(v.boolean()), - private: v.optional(v.boolean()), - safe: v.optional(v.boolean()), - image: v.optional(v.string()), - // Video-specific parameters - duration: v.optional(v.number()), - audio: v.optional(v.boolean()), - aspectRatio: v.optional(v.string()), - lastFrameImage: v.optional(v.string()), -}) +// Re-export from canonical source to avoid duplication +export { generationParamsValidator } from "../generatedImages/types"components/studio/gallery/image-gallery-parts.tsx (1)
209-248: Consider UX: "Select All" is inaccessible when nothing is selected.The dropdown trigger is disabled when
selectedIds.size === 0, which prevents access to the "Select All" option precisely when users might want it. SinceSelectAllControlprovides a separate checkbox, this may be intentional—but if users expect to select all from this menu, they'll find it disabled.Consider either:
- Keeping current behavior if
SelectAllControlis the primary way to select all- Moving "Select All"/"Deselect All" outside the disabled gate (e.g., separate button or always-enabled trigger)
components/ui/image-card-parts.tsx (1)
18-34: Consider addingReadonlyto props for consistency.This file's props interfaces don't use
Readonly<>, whileimage-gallery-parts.tsxconsistently wraps all props withReadonly. Consider aligning the pattern for consistency across the codebase.Example for this component
-export function SelectionCheckbox({ isSelected, onCheckedChange, onClick }: SelectionCheckboxProps) { +export function SelectionCheckbox({ isSelected, onCheckedChange, onClick }: Readonly<SelectionCheckboxProps>) {components/ui/sidebar/sidebar-main.tsx (1)
52-84: Consider adding keyboard accessibility for the mobile overlay.The mobile overlay correctly closes the sidebar on click and has
aria-hidden="true". However, for full accessibility compliance, consider adding keyboard support for closing the sidebar when the overlay is focused (e.g., Escape key handling). The keyboard shortcut exists in the context but only toggles—it doesn't specifically handle the Escape key for closing.💡 Optional: Add Escape key handler for mobile overlay
This could be added in the
SidebarProvideror as a local effect in the mobile section:if (isMobile) { + // Note: Consider adding useEffect for Escape key to close mobile sidebar return ( <> {/* Overlay - clickable backdrop to close sidebar */} {openMobile && ( <div className="fixed inset-0 z-40 bg-black/50 animate-in fade-in-0 duration-200" onClick={() => setOpenMobile(false)} + onKeyDown={(e) => e.key === "Escape" && setOpenMobile(false)} + tabIndex={-1} aria-hidden="true" /> )}components/ui/sidebar/sidebar-context.tsx (1)
97-101: Potential infinite loop risk in mobile sync effect.The effect depends on
openMobilebut also reads it for comparison, which could cause unnecessary re-runs. While the conditionopenProp !== openMobileprevents actual state updates when values match, the effect still runs on everyopenMobilechange.💡 Consider using a ref to track previous value
+ const prevOpenPropRef = React.useRef(openProp) + React.useEffect(() => { - if (isMobile && openProp !== undefined && openProp !== openMobile) { + if (isMobile && openProp !== undefined && openProp !== prevOpenPropRef.current) { setOpenMobileInternal(openProp) } - }, [isMobile, openProp, openMobile]) + prevOpenPropRef.current = openProp + }, [isMobile, openProp])This ensures the effect only runs when
openPropactually changes from external sources, rather than checking against current state on every render cycle.components/ui/sidebar/sidebar-menu.tsx (1)
91-95: Avoid mutating thetooltipparameter.Reassigning a function parameter to an object mutates the external reference in some edge cases and can be confusing. Consider using a separate variable for the normalized tooltip props.
♻️ Refactor to avoid parameter mutation
- if (typeof tooltip === "string") { - tooltip = { - children: tooltip, - } - } + const tooltipProps = typeof tooltip === "string" + ? { children: tooltip } + : tooltip return ( <Tooltip> <TooltipTrigger asChild>{button}</TooltipTrigger> <TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile} - {...tooltip} + {...tooltipProps} /> </Tooltip> )lib/seo/solutions/transparent-png-generator.ts (1)
6-14: Minor path inconsistency between slug and asset folder.The
slugis"transparent-png-generator"but asset paths reference"transparent-generator"folder. This works if assets exist at those paths, but could cause maintenance confusion.🔧 Consider aligning folder names with slug
Either rename the assets folder to match the slug, or keep as-is if the current paths are intentional. No functional impact if assets exist.
components/pricing/model-value-data.ts (1)
1-2: Drop"use client"from this data-only module.The file exports only types and constants with no client-specific code or imports. Removing the directive keeps it server-compatible and avoids forcing a client boundary unnecessarily. It will still execute on the client when imported by client components, without the explicit directive.
♻️ Proposed change
-"use client" -components/images/image-lightbox/lightbox-components.tsx (1)
383-401: Avoidas unknown astype cast.Per coding guidelines, avoid
ascasts in TypeScript. The Next.jsImagecomponent'sonLoadevent in v16+ should already provideReact.SyntheticEvent<HTMLImageElement>, making this double cast unnecessary.♻️ Proposed fix
onLoad={(e) => { - handleImageLoad(e as unknown as React.SyntheticEvent<HTMLImageElement>) + handleImageLoad(e) setIsFullResLoaded(true) }}If there's a type mismatch causing a compile error, please verify the actual type from
next/imageand consider adjusting thehandleImageLoadsignature to match, or add a@ts-expect-errorcomment with an explanation linking to the relevant Next.js issue.components/images/image-lightbox/lightbox-helpers.ts (1)
117-123: Redundant conditional branching.Both branches of the
ifstatement return identical values, making thehasSeparateThumbnailcheck unnecessary.♻️ Simplified implementation
export function getFullResOpacityClass(hasSeparateThumbnail: boolean, isFullResLoaded: boolean): string { - if (hasSeparateThumbnail) { - return isFullResLoaded ? "opacity-100" : "opacity-0" - } return isFullResLoaded ? "opacity-100" : "opacity-0" }If there was intended to be different behavior when there's no separate thumbnail (e.g., no fade transition), please update the implementation accordingly.
components/images/image-lightbox/index.tsx (1)
300-325: Consider simplifying the empty state rendering.When
!displayImage, the component still renders a full Dialog with PromptLibrary. While functional, rendering an empty Dialog may cause unnecessary DOM elements.If the Dialog is only needed when an image exists, consider returning just the PromptLibrary in the empty state:
if (!displayImage) { return ( - <> - <Dialog open={isOpen} onOpenChange={onClose}> - <DialogContent - className="!fixed !inset-0 !flex !items-center !justify-center !border-none !bg-transparent !p-0 !shadow-none !w-screen !h-screen !max-w-none !translate-x-0 !translate-y-0 !outline-none !duration-75" - showCloseButton={false} - onOpenAutoFocus={(e) => e.preventDefault()} - > - <VisuallyHidden> - <DialogTitle>Fullscreen Preview</DialogTitle> - <DialogDescription>Loading image preview</DialogDescription> - </VisuallyHidden> - </DialogContent> - </Dialog> - <PromptLibrary - isOpen={libraryOpen} - onClose={handleLibraryClose} - promptType="positive" - onInsert={handleLibraryInsert} - initialSaveContent={saveContent} - onInsertComplete={onClose} - /> - </> + <PromptLibrary + isOpen={libraryOpen} + onClose={handleLibraryClose} + promptType="positive" + onInsert={handleLibraryInsert} + initialSaveContent={saveContent} + onInsertComplete={onClose} + /> ) }However, if the empty Dialog is intentional for maintaining focus trap or animation continuity, this can be kept as-is.
lib/config/model-registry.ts (1)
434-435: Dropas constto avoid type assertion.
The explicitRecord<string, ModelDefinition>annotation already constrains the shape; removing the const assertion aligns with the TypeScript guideline to avoidascasts. As per coding guidelines, avoidascasts.♻️ Suggested tweak
-} as const; +};scripts/fix-test-types.mjs (2)
43-47:@ts-expect-errorreplacements should include explanatory comments.Per project guidelines,
@ts-expect-errordirectives should include a comment explaining why and a TODO to remove. The blanket replacement may leave suppressions without context.♻️ Suggested improvement
// Replace `@ts-ignore` with `@ts-expect-error` if (content.includes('@ts-ignore')) { - content = content.replace(/@ts-ignore/g, '@ts-expect-error'); + content = content.replace(/@ts-ignore/g, '@ts-expect-error -- TODO: Add proper types and remove this suppression'); modified = true; }Based on learnings:
ts-expect-errorallowed only with a comment explaining why + link/TODO to remove.
38-47: Add file existence check to prevent unhelpful errors.If a file in the list doesn't exist (e.g., renamed or deleted),
fs.readFileSyncwill throw an opaque error.♻️ Suggested improvement
function fixFile(filePath) { console.warn(`Fixing ${filePath}...`); + if (!fs.existsSync(filePath)) { + console.warn(` ⚠ File not found, skipping: ${filePath}`); + return; + } let content = fs.readFileSync(filePath, 'utf8');.github/skills/writing-convex-functions/SKILL.md (1)
210-235: Minor: Consider adding language specifier to the nested code block.The Output Format section contains a nested code block (line 217-221) without a language specifier. While the static analysis hint may be referring to this, it's a minor formatting issue within documentation.
📝 Suggested fix
## Output Format ```markdown ## Summary Added `[functionType]` `[name]` for [purpose]. ## Function -```typescript +\`\`\`typescript export const myFunction = query({ args: { ... }, handler: async (ctx, args) => { ... }, }); -``` +\`\`\`Note: Nested fenced code blocks in Markdown require escaping or using different fence styles (e.g.,
~~~for outer, ``` for inner)..github/skills/testing-components/SKILL.md (1)
124-132: Avoidanyin documentation examples.The Next/Image mock example uses
: anywhich contradicts the project's strict TypeScript guidelines. Documentation examples should model best practices.♻️ Suggested fix
### Next/Image ```typescript vi.mock("next/image", () => ({ - default: ({ src, alt, ...props }: any) => ( + default: ({ src, alt, ...props }: { src: string; alt: string; [key: string]: unknown }) => ( <img src={src} alt={alt} {...props} /> ), }));</details> Based on learnings: No `any` type in TypeScript. Use `unknown` and narrow instead. </blockquote></details> <details> <summary>.github/skills/adding-convex-table/SKILL.md (1)</summary><blockquote> `64-68`: **Align `userId` validator with the reference (`v.id("users")`).** The template uses `userId: v.string()` while the reference table recommends `v.id("users")`. This inconsistency could lead to schema drift and weaker referential guarantees. Consider updating the template or explicitly documenting when a raw string is acceptable. </blockquote></details> <details> <summary>components/landing/landing-header.test.tsx (1)</summary><blockquote> `96-114`: **Prefer a resilient selector for the mobile toggle.** The current lookup depends on class names and internal DOM structure, which makes the test fragile to styling refactors. Consider adding an accessible label or a test id on the toggle and querying by role+name (or by test id) instead. </blockquote></details> <details> <summary>.github/skills/brainstorming-ui-and-design/SKILL.md (1)</summary><blockquote> `10-43`: **Minor wording polish (optional).** A couple of small phrasing tweaks would improve clarity and tone. <details> <summary>✍️ Suggested edits</summary> ```diff -Refer to `.agent/skills/styling-ui/SKILL.md` for repo specific tailwind/css rules. +Refer to `.agent/skills/styling-ui/SKILL.md` for repo-specific Tailwind/CSS rules. @@ -Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. +Remember: Claude is capable of extraordinary creative work. Don't hold back—show what can truly be created when pushing beyond conventions and committing fully to a distinctive vision.eslint.config.mjs (1)
55-63: Keep ts-comment descriptions even in tests.
Disabling@typescript-eslint/ban-ts-commententirely allows@ts-expect-errorwithout rationale. Consider allowing only with descriptions so the guideline still holds in tests.As per coding guidelines, prefer documented `@ts-expect-error` usages only.♻️ Possible rule tweak
- "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-comment": [ + "error", + { "ts-expect-error": "allow-with-description" } + ],components/studio/controls/prompt-section-helpers.test.ts (1)
73-113: Avoidascasts for keyboard event fixtures.
To align with the no-asguideline, consider narrowing the helper’s parameter type to just the fields it uses, then pass a typed literal withsatisfies.Based on learnings, avoid `as` casts in TS/TSX.♻️ Suggested refactor (helper + tests)
// components/studio/controls/prompt-section-helpers.ts -export function isSubmitKeyboardShortcut( - e: React.KeyboardEvent<HTMLTextAreaElement> -): boolean { +export type SubmitShortcutEvent = Pick< + React.KeyboardEvent<HTMLTextAreaElement>, + "ctrlKey" | "metaKey" | "key" +>; + +export function isSubmitKeyboardShortcut(e: SubmitShortcutEvent): boolean { return (e.ctrlKey || e.metaKey) && e.key === "Enter"; }// components/studio/controls/prompt-section-helpers.test.ts -import { +import { cancelScheduledRaf, clearScheduledDebounce, getPromptSetterByType, isSubmitKeyboardShortcut, maybeInitializeDisplayState, notifyContentChangeDebounced, + type SubmitShortcutEvent, } from "./prompt-section-helpers"; -const event = { ctrlKey: true, metaKey: false, key: "Enter" } as React.KeyboardEvent<HTMLTextAreaElement>; +const event = { ctrlKey: true, metaKey: false, key: "Enter" } satisfies SubmitShortcutEvent;components/studio/controls/prompt-section.test.tsx (1)
59-66: Scope fake timers or wire userEvent to them.
Global fake timers can cause subtle userEvent issues. Consider scoping timers to the debounce test or usinguserEvent.setup({ advanceTimers: vi.advanceTimersByTime })and the returneduserinstance.convex/generatedImages/types.ts (1)
7-28: DeduplicateMAX_BULK_OPERATION_SIZEandgenerationParamsValidator.These are already defined in
convex/lib/batchTypes.ts; keeping two sources risks drift. Re-export the shared definitions instead.♻️ Suggested refactor
-import { v } from "convex/values" +import { MAX_BULK_OPERATION_SIZE, generationParamsValidator } from "../lib/batchTypes" import type { Doc } from "../_generated/dataModel" @@ -export const MAX_BULK_OPERATION_SIZE = 100 - -export const generationParamsValidator = v.object({ - prompt: v.string(), - negativePrompt: v.optional(v.string()), - model: v.optional(v.string()), - width: v.optional(v.number()), - height: v.optional(v.number()), - seed: v.optional(v.number()), - enhance: v.optional(v.boolean()), - private: v.optional(v.boolean()), - safe: v.optional(v.boolean()), - image: v.optional(v.string()), - duration: v.optional(v.number()), - audio: v.optional(v.boolean()), - aspectRatio: v.optional(v.string()), - lastFrameImage: v.optional(v.string()), -}) +export { MAX_BULK_OPERATION_SIZE, generationParamsValidator }
| function useLightboxData(image: LightboxImage | null): UseLightboxDataResult { | ||
| const imageId = image?._id as Id<"generatedImages"> | undefined | ||
| const needsFullData = Boolean(image && !image.prompt && imageId) | ||
| const fullImageData = useImageDetails(needsFullData ? imageId : null) | ||
|
|
||
| const displayImage: LightboxImage | null = image | ||
| ? mergeImageWithDetails(image, fullImageData) | ||
| : null | ||
|
|
||
| const isVideo = Boolean(displayImage && isVideoContent(displayImage.contentType, displayImage.url)) | ||
| const isLoadingDetails = needsFullData && fullImageData === undefined | ||
|
|
||
| const thumbnailUrl = displayImage?.url | ||
| const fullResUrl = displayImage?.originalUrl ?? displayImage?.url ?? "" | ||
| const hasSeparateThumbnail = Boolean(displayImage && hasSeparateThumbnailUrl(displayImage)) | ||
|
|
||
| return { | ||
| displayImage, | ||
| isVideo, | ||
| isLoadingDetails, | ||
| thumbnailUrl, | ||
| fullResUrl, | ||
| hasSeparateThumbnail, | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for LightboxImage type definition
rg -n "type LightboxImage|interface LightboxImage" --type ts -A 15Repository: Simplereally/bloomstudio
Length of output: 5048
🏁 Script executed:
# Search in the same file to understand context
fd -e tsx -e ts -path "*/image-lightbox/*" -exec cat -n {} +Repository: Simplereally/bloomstudio
Length of output: 236
🏁 Script executed:
# Find where LightboxImage is imported/defined
rg -n "LightboxImage" --type ts --type tsx -B 2 -A 2 | head -100Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Get the complete LightboxImage interface
cat -n hooks/use-image-lightbox.ts | head -40Repository: Simplereally/bloomstudio
Length of output: 1417
🏁 Script executed:
# Find and examine the exact file and lines in question
find . -path "*/components/images/image-lightbox/index.tsx" -exec cat -n {} +Repository: Simplereally/bloomstudio
Length of output: 15541
🏁 Script executed:
# Find useImageDetails hook definition
rg -n "useImageDetails" --type ts -A 10 | head -50Repository: Simplereally/bloomstudio
Length of output: 3578
🏁 Script executed:
# Look at the query hook file
cat -n hooks/queries/use-image-history.ts | head -50Repository: Simplereally/bloomstudio
Length of output: 2376
🏁 Script executed:
# Search for places where LightboxImage is instantiated or _id is assigned
rg -n "url.*string|_id.*:" --type ts --type tsx -B 2 | grep -A 5 -B 5 "_id" | head -80Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Look for mergeImageWithDetails to understand how images are created
rg -n "mergeImageWithDetails" --type ts -A 15Repository: Simplereally/bloomstudio
Length of output: 4125
🏁 Script executed:
# Get the complete mergeImageWithDetails function
cat -n components/images/image-lightbox/lightbox-helpers.ts | head -60Repository: Simplereally/bloomstudio
Length of output: 2571
🏁 Script executed:
# Check where LightboxImage objects are created - look for object literals with _id
rg -n "{\s*url\s*:" --type ts -A 20 | grep -B 5 -A 10 "_id" | head -100Repository: Simplereally/bloomstudio
Length of output: 154
🏁 Script executed:
# Search for where LightboxImage objects are created/passed
rg -n "LightboxImage" --type ts | grep -v "import\|export\|interface\|type" | head -50Repository: Simplereally/bloomstudio
Length of output: 2494
🏁 Script executed:
# Look for the generatedImages API response to see the actual data structure
rg -n "generatedImages" -A 5 convex/ --type ts | head -100Repository: Simplereally/bloomstudio
Length of output: 6879
🏁 Script executed:
# Check the return type of generatedImages.getById and other queries
cat -n convex/generatedImages/types.tsRepository: Simplereally/bloomstudio
Length of output: 5192
🏁 Script executed:
# Search for where LightboxImage is created from API responses
rg -n "openLightbox\|setLightboxImage\|LightboxImage.*=" --type ts -B 3 -A 3 | head -100Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Search for where images are passed to ImageLightbox or openLightbox
rg -n "ImageLightbox|openLightbox" --type ts -B 5 -A 2 | head -120Repository: Simplereally/bloomstudio
Length of output: 7848
🏁 Script executed:
# Look for studio or gallery components that use the lightbox
find . -name "*studio*" -o -name "*gallery*" | head -20Repository: Simplereally/bloomstudio
Length of output: 1028
🏁 Script executed:
# Check the gallery component to see how it passes images to the lightbox
cat -n components/studio/gallery/image-gallery.tsx | head -100Repository: Simplereally/bloomstudio
Length of output: 4228
🏁 Script executed:
# Search for where images from the API are converted to LightboxImage
rg -n "ThumbnailImage|DisplayImage|PublicFeedImage" --type ts -B 2 -A 5 | head -150Repository: Simplereally/bloomstudio
Length of output: 9691
Fix the _id field type in LightboxImage interface.
The as Id<"generatedImages"> cast on line 190 is unnecessary. The LightboxImage interface defines _id?: string, but the Convex API returns _id as Id<"generatedImages"> through the data transformation functions (toDisplayImages, toThumbnails). Change _id?: string to _id?: Id<"generatedImages"> in the LightboxImage interface (hooks/use-image-lightbox.ts) to eliminate the cast entirely and align the type with actual data at runtime.
🤖 Prompt for AI Agents
In `@components/images/image-lightbox/index.tsx` around lines 189 - 213, Update
the LightboxImage interface to use the correct Convex Id type and remove the
unnecessary cast: change the _id property in the LightboxImage interface (in
hooks/use-image-lightbox.ts) from _id?: string to _id?: Id<"generatedImages">,
ensure the Id type is imported where the interface is declared, and then remove
the redundant cast in useLightboxData (the image?._id as Id<"generatedImages">)
so the code uses the typed _id directly.
| export interface ClearPromptButtonProps { | ||
| visible: boolean; | ||
| onClick: () => void; | ||
| } | ||
|
|
||
| /** Conditionally renders a clear button for the prompt input */ | ||
| export function ClearPromptButton({ visible, onClick }: ClearPromptButtonProps) { | ||
| if (!visible) return null; | ||
|
|
||
| return ( | ||
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon" | ||
| className="absolute right-2 top-2 h-6 w-6 opacity-50 hover:opacity-100" | ||
| onClick={onClick} | ||
| data-testid="clear-prompt" | ||
| > | ||
| <X className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| ); |
There was a problem hiding this comment.
Add an accessible label to the icon‑only clear button.
Screen readers won’t have a name for this control.
✅ Suggested fix
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-2 top-2 h-6 w-6 opacity-50 hover:opacity-100"
onClick={onClick}
data-testid="clear-prompt"
+ aria-label="Clear prompt"
>
<X className="h-3.5 w-3.5" />
</Button>🤖 Prompt for AI Agents
In `@components/studio/controls/prompt-section-parts.tsx` around lines 23 - 43,
The ClearPromptButton renders an icon-only button without an accessible name;
update the ClearPromptButton component (function ClearPromptButton) to provide
an accessible label for screen readers by adding an aria-label (e.g.,
aria-label="Clear prompt") to the Button or by including visually hidden text
inside the Button (e.g., a span with a screen-reader-only class and the text
"Clear prompt"), ensuring the control has a programmatic name while keeping the
visual icon-only appearance.
| export interface PromptHeaderProps { | ||
| promptHistoryLength: number; | ||
| onToggleHistory: () => void; | ||
| characterCount: number; | ||
| maxLength: number; | ||
| isNearLimit: boolean; | ||
| /** When true, the header is hidden */ | ||
| hidden?: boolean; | ||
| } | ||
|
|
||
| /** Renders the prompt label, history toggle, and character count */ | ||
| export function PromptHeader({ | ||
| promptHistoryLength, | ||
| onToggleHistory, | ||
| characterCount, | ||
| maxLength, | ||
| isNearLimit, | ||
| hidden = false, | ||
| }: PromptHeaderProps) { | ||
| if (hidden) return null; | ||
|
|
||
| const charCountClass = isNearLimit ? "text-destructive" : "text-muted-foreground"; | ||
|
|
||
| return ( | ||
| <div className="flex items-center justify-between"> | ||
| <Label htmlFor="prompt" className="text-sm font-medium flex items-center gap-2"> | ||
| <Wand2 className="h-3.5 w-3.5 text-primary" /> | ||
| Prompt | ||
| </Label> | ||
| <div className="flex items-center gap-1"> | ||
| {promptHistoryLength > 0 && ( | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-6 w-6" | ||
| onClick={onToggleHistory} | ||
| data-testid="history-toggle" | ||
| > | ||
| <History className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| </TooltipTrigger> | ||
| <TooltipContent side="top">Recent prompts</TooltipContent> | ||
| </Tooltip> |
There was a problem hiding this comment.
Add an accessible label to the history toggle icon button.
Icon‑only buttons need an accessible name.
✅ Suggested fix
<Button
type="button"
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={onToggleHistory}
data-testid="history-toggle"
+ aria-label="Toggle prompt history"
>
<History className="h-3.5 w-3.5" />
</Button>📝 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 interface PromptHeaderProps { | |
| promptHistoryLength: number; | |
| onToggleHistory: () => void; | |
| characterCount: number; | |
| maxLength: number; | |
| isNearLimit: boolean; | |
| /** When true, the header is hidden */ | |
| hidden?: boolean; | |
| } | |
| /** Renders the prompt label, history toggle, and character count */ | |
| export function PromptHeader({ | |
| promptHistoryLength, | |
| onToggleHistory, | |
| characterCount, | |
| maxLength, | |
| isNearLimit, | |
| hidden = false, | |
| }: PromptHeaderProps) { | |
| if (hidden) return null; | |
| const charCountClass = isNearLimit ? "text-destructive" : "text-muted-foreground"; | |
| return ( | |
| <div className="flex items-center justify-between"> | |
| <Label htmlFor="prompt" className="text-sm font-medium flex items-center gap-2"> | |
| <Wand2 className="h-3.5 w-3.5 text-primary" /> | |
| Prompt | |
| </Label> | |
| <div className="flex items-center gap-1"> | |
| {promptHistoryLength > 0 && ( | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <Button | |
| type="button" | |
| variant="ghost" | |
| size="icon" | |
| className="h-6 w-6" | |
| onClick={onToggleHistory} | |
| data-testid="history-toggle" | |
| > | |
| <History className="h-3.5 w-3.5" /> | |
| </Button> | |
| </TooltipTrigger> | |
| <TooltipContent side="top">Recent prompts</TooltipContent> | |
| </Tooltip> | |
| export interface PromptHeaderProps { | |
| promptHistoryLength: number; | |
| onToggleHistory: () => void; | |
| characterCount: number; | |
| maxLength: number; | |
| isNearLimit: boolean; | |
| /** When true, the header is hidden */ | |
| hidden?: boolean; | |
| } | |
| /** Renders the prompt label, history toggle, and character count */ | |
| export function PromptHeader({ | |
| promptHistoryLength, | |
| onToggleHistory, | |
| characterCount, | |
| maxLength, | |
| isNearLimit, | |
| hidden = false, | |
| }: PromptHeaderProps) { | |
| if (hidden) return null; | |
| const charCountClass = isNearLimit ? "text-destructive" : "text-muted-foreground"; | |
| return ( | |
| <div className="flex items-center justify-between"> | |
| <Label htmlFor="prompt" className="text-sm font-medium flex items-center gap-2"> | |
| <Wand2 className="h-3.5 w-3.5 text-primary" /> | |
| Prompt | |
| </Label> | |
| <div className="flex items-center gap-1"> | |
| {promptHistoryLength > 0 && ( | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <Button | |
| type="button" | |
| variant="ghost" | |
| size="icon" | |
| className="h-6 w-6" | |
| onClick={onToggleHistory} | |
| data-testid="history-toggle" | |
| aria-label="Toggle prompt history" | |
| > | |
| <History className="h-3.5 w-3.5" /> | |
| </Button> | |
| </TooltipTrigger> | |
| <TooltipContent side="top">Recent prompts</TooltipContent> | |
| </Tooltip> |
🤖 Prompt for AI Agents
In `@components/studio/controls/prompt-section-parts.tsx` around lines 50 - 95,
The history toggle Button in the PromptHeader component (the Button with
data-testid="history-toggle" that calls onToggleHistory and renders the History
icon) is icon-only and needs an accessible name; add an aria-label (e.g.,
aria-label="Show recent prompts" or similar) or include visually-hidden text
associated with the Button so screen readers can announce it, ensuring the
Button remains variant="ghost" size="icon" and behavior unchanged.
| export interface SuggestionChipsProps { | ||
| suggestions: string[]; | ||
| isLoadingSuggestions: boolean; | ||
| onSuggestionClick: (suggestion: string) => void; | ||
| } | ||
|
|
||
| /** Renders suggestion chips with lightbulb icon */ | ||
| export function SuggestionChips({ suggestions, isLoadingSuggestions, onSuggestionClick }: SuggestionChipsProps) { | ||
| const lightbulbClass = isLoadingSuggestions ? "text-yellow-400 animate-lightbulb-glow" : "text-muted-foreground"; | ||
|
|
||
| return ( | ||
| <div className="flex flex-wrap gap-1.5" data-testid="suggestions"> | ||
| <Lightbulb className={`h-3.5 w-3.5 mt-1 transition-all duration-300 ${lightbulbClass}`} /> | ||
| {suggestions.map((suggestion, index) => ( | ||
| <Badge | ||
| key={index} | ||
| variant="secondary" | ||
| className="cursor-pointer hover:bg-primary/20 transition-colors text-xs" | ||
| onClick={() => onSuggestionClick(suggestion)} | ||
| > | ||
| + {suggestion} | ||
| </Badge> | ||
| ))} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find Badge component in the UI components directory
fd -e 'tsx' -e 'ts' 'badge' components/ui 2>/dev/null || true
find components -name '*badge*' -type f 2>/dev/null || trueRepository: Simplereally/bloomstudio
Length of output: 562
🏁 Script executed:
#!/bin/bash
# Search for Badge component definition more broadly
rg -l "export.*Badge" components/ -t ts -t tsxRepository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
#!/bin/bash
# Look at the file structure to understand component organization
ls -la components/ui/ 2>/dev/null | head -20Repository: Simplereally/bloomstudio
Length of output: 1298
🏁 Script executed:
#!/bin/bash
# Read the Badge component implementation
cat -n components/ui/badge.tsxRepository: Simplereally/bloomstudio
Length of output: 2022
🏁 Script executed:
#!/bin/bash
# Check Button component
cat -n components/ui/button.tsx | head -60Repository: Simplereally/bloomstudio
Length of output: 2807
🏁 Script executed:
#!/bin/bash
# Re-examine the original code in prompt-section-parts.tsx to see if there are any keyboard handlers
cat -n components/studio/controls/prompt-section-parts.tsx | sed -n '213,235p'Repository: Simplereally/bloomstudio
Length of output: 1158
Make suggestion chips keyboard-accessible.
The Badge component renders a span element by default. A span with onClick is not keyboard-accessible—it cannot be reached via Tab key or triggered with Enter/Space, and screen readers won't announce it as interactive. Replace with Button component for semantic HTML and built-in keyboard support.
✅ Suggested fix (Button-based)
{suggestions.map((suggestion, index) => (
- <Badge
+ <Button
key={index}
+ type="button"
variant="secondary"
+ size="sm"
- className="cursor-pointer hover:bg-primary/20 transition-colors text-xs"
+ className="h-auto px-2 py-1 text-xs hover:bg-secondary/80"
onClick={() => onSuggestionClick(suggestion)}
>
+ {suggestion}
- </Badge>
+ </Button>
))}📝 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 interface SuggestionChipsProps { | |
| suggestions: string[]; | |
| isLoadingSuggestions: boolean; | |
| onSuggestionClick: (suggestion: string) => void; | |
| } | |
| /** Renders suggestion chips with lightbulb icon */ | |
| export function SuggestionChips({ suggestions, isLoadingSuggestions, onSuggestionClick }: SuggestionChipsProps) { | |
| const lightbulbClass = isLoadingSuggestions ? "text-yellow-400 animate-lightbulb-glow" : "text-muted-foreground"; | |
| return ( | |
| <div className="flex flex-wrap gap-1.5" data-testid="suggestions"> | |
| <Lightbulb className={`h-3.5 w-3.5 mt-1 transition-all duration-300 ${lightbulbClass}`} /> | |
| {suggestions.map((suggestion, index) => ( | |
| <Badge | |
| key={index} | |
| variant="secondary" | |
| className="cursor-pointer hover:bg-primary/20 transition-colors text-xs" | |
| onClick={() => onSuggestionClick(suggestion)} | |
| > | |
| + {suggestion} | |
| </Badge> | |
| ))} | |
| export interface SuggestionChipsProps { | |
| suggestions: string[]; | |
| isLoadingSuggestions: boolean; | |
| onSuggestionClick: (suggestion: string) => void; | |
| } | |
| /** Renders suggestion chips with lightbulb icon */ | |
| export function SuggestionChips({ suggestions, isLoadingSuggestions, onSuggestionClick }: SuggestionChipsProps) { | |
| const lightbulbClass = isLoadingSuggestions ? "text-yellow-400 animate-lightbulb-glow" : "text-muted-foreground"; | |
| return ( | |
| <div className="flex flex-wrap gap-1.5" data-testid="suggestions"> | |
| <Lightbulb className={`h-3.5 w-3.5 mt-1 transition-all duration-300 ${lightbulbClass}`} /> | |
| {suggestions.map((suggestion, index) => ( | |
| <Button | |
| key={index} | |
| type="button" | |
| variant="secondary" | |
| size="sm" | |
| className="h-auto px-2 py-1 text-xs hover:bg-secondary/80" | |
| onClick={() => onSuggestionClick(suggestion)} | |
| > | |
| {suggestion} | |
| </Button> | |
| ))} |
🤖 Prompt for AI Agents
In `@components/studio/controls/prompt-section-parts.tsx` around lines 213 - 235,
The SuggestionChips component currently renders interactive suggestion items
using the Badge component (which outputs a span) making them inaccessible via
keyboard; replace each Badge with the Button component (ensure Button is
imported) inside SuggestionChips, preserve props like key, variant/intent,
className ("cursor-pointer hover:bg-primary/20 transition-colors text-xs"), and
onClick by using Button type="button" and onClick={() =>
onSuggestionClick(suggestion)}; also include an appropriate aria-label (e.g.,
`aria-label={`Use suggestion ${suggestion}`}`) so screen readers announce the
action and keep the Lightbulb usage and isLoadingSuggestions styling unchanged.
| vi.mock("@/components/ui/button", () => ({ | ||
| Button: ({ | ||
| children, | ||
| onClick, | ||
| disabled, | ||
| }: { | ||
| children: React.ReactNode; | ||
| onClick?: () => void; | ||
| disabled?: boolean; | ||
| }) => ( | ||
| <button onClick={onClick} disabled={disabled} data-testid="generate-button"> | ||
| {children} | ||
| </button> | ||
| ), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and read the test file
cat -n components/studio/layout/sidebar-generate-button.test.tsxRepository: Simplereally/bloomstudio
Length of output: 9597
🏁 Script executed:
# Check the tsconfig to understand the JSX runtime
cat -n tsconfig.jsonRepository: Simplereally/bloomstudio
Length of output: 1135
🏁 Script executed:
# Check for any vitest config that might affect type checking
fd -t f -e config.ts -e vitest.config.ts -e vitest.config.js | head -10Repository: Simplereally/bloomstudio
Length of output: 155
🏁 Script executed:
# Check if vitest globals provide React globally
cat -n vitest.config.tsRepository: Simplereally/bloomstudio
Length of output: 875
🏁 Script executed:
# Also check if there's a global React type definition anywhere
fd -t f -e "*.d.ts" | xargs grep -l "React.ReactNode" | head -5Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Check the vitest setup file to see if React is globally declared there
cat -n vitest.setup.ts 2>/dev/null || echo "vitest.setup.ts not found or empty"Repository: Simplereally/bloomstudio
Length of output: 2427
🏁 Script executed:
# Try to verify if the code actually type-checks by looking for any type errors
# or check if there's a build/typecheck script
cat -n package.json | grep -A 20 '"scripts"'Repository: Simplereally/bloomstudio
Length of output: 915
🏁 Script executed:
# Check if React is declared globally in any type definitions
# Look for `@types` or global.d.ts
fd -t f "global.d.ts" -o -name "*.d.ts" | xargs grep -l "declare global" 2>/dev/null | head -5Repository: Simplereally/bloomstudio
Length of output: 298
🏁 Script executed:
# Check the actual type errors by looking at what's in node_modules for React types
# Specifically, check if React namespace is globally augmented
find . -path ./node_modules -prune -o -name "react.d.ts" -type f -print 2>/dev/null | head -3Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Most importantly, let's verify if there's actually a TypeScript error by checking
# if React is imported in vitest.setup.ts (maybe truncated in output)
wc -l vitest.setup.ts && echo "---" && head -20 vitest.setup.tsRepository: Simplereally/bloomstudio
Length of output: 710
🏁 Script executed:
# Check if React is somehow made globally available through tsconfig types or vitest globals
grep -r "declare global" . --include="*.d.ts" --include="*.ts" 2>/dev/null | grep -i react | head -5Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Check vitest.setup.ts for React import that might be missing (full file)
cat -n vitest.setup.tsRepository: Simplereally/bloomstudio
Length of output: 2427
🏁 Script executed:
# Most crucially, let's verify if this actually causes a TypeScript error
# by checking a small test file or looking at the actual issue
rg "React\." components/studio/layout/sidebar-generate-button.test.tsx -A 2 -B 2Repository: Simplereally/bloomstudio
Length of output: 169
🏁 Script executed:
# Check the very beginning of vitest.setup.ts to see all imports
head -30 vitest.setup.tsRepository: Simplereally/bloomstudio
Length of output: 922
🏁 Script executed:
# Verify if tsconfig automatically includes React types globally
rg "@types/react" package.jsonRepository: Simplereally/bloomstudio
Length of output: 133
🏁 Script executed:
# Check if there's a jsx.d.ts or react.d.ts auto-importing React
fd "jsx\.d\.ts|react\.d\.ts" --type fRepository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Let's check if TypeScript actually complains about React.ReactNode
# by examining error patterns or looking for related eslint/ts configs
cat -n .eslintrc* 2>/dev/null || echo "No eslintrc found"Repository: Simplereally/bloomstudio
Length of output: 85
🏁 Script executed:
# Most importantly, let's see if React is actually re-exported from vitest or setup
rg "^import.*React" vitest.setup.tsRepository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# The critical check: does React need to be imported for React.ReactNode?
# Let's create a minimal test to understand the TypeScript behavior
cat > /tmp/test-react-type.ts << 'EOF'
// Simulating the setup without React import
type Props = {
children: React.ReactNode;
};
EOF
# Now let's see if TypeScript would complain
cd /tmp && tsc --noEmit --lib dom,esnext test-react-type.ts 2>&1 || trueRepository: Simplereally/bloomstudio
Length of output: 110
🏁 Script executed:
# More conclusively, let's check if there are any existing type errors in the repo
# by looking at the actual tsconfig and checking strict mode
grep -A 5 '"strict"' tsconfig.jsonRepository: Simplereally/bloomstudio
Length of output: 226
🏁 Script executed:
# Check if React is somehow augmented globally in node_modules or type definitions
# by looking at how `@types/react` exports React namespace
rg "declare.*React" --type ts --type tsx | head -10Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Let's check if other test files properly import React types
rg "import.*React" components/ --type ts --type tsx | grep -i "\.test\." | head -10Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Check if React is re-exported from 'vitest' or any global setup
rg "from.*vitest" components/ --type ts --type tsx | grep ReactRepository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Let's check how React types are actually used in the broader codebase
rg "React\.ReactNode" . --type ts --type tsx | head -15Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Check if there's actually an import missing in vitest.setup.ts
# by looking at what React.ReactNode usage looks like elsewhere
rg "children.*React\.ReactNode" . --type ts --type tsx -B 3 | head -30Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Search for React.ReactNode usage without type filtering
rg "React\.ReactNode" . | head -20Repository: Simplereally/bloomstudio
Length of output: 50382
🏁 Script executed:
# Check how React is imported in other test files or components
rg "import.*React" components/ -g "*.tsx" -g "*.ts" | head -20Repository: Simplereally/bloomstudio
Length of output: 1536
🏁 Script executed:
# Check if the mock in vitest.setup.ts at line 65 also has React.ReactNode and how it works
grep -n "React\." vitest.setup.tsRepository: Simplereally/bloomstudio
Length of output: 153
🏁 Script executed:
# Let's check the actual setup more carefully - maybe React is imported somewhere we missed
cat vitest.setup.ts | head -10Repository: Simplereally/bloomstudio
Length of output: 331
🏁 Script executed:
# Check if vitest/globals provides React types
find node_modules/vitest -name "*.d.ts" 2>/dev/null | xargs grep -l "React" 2>/dev/null | head -5Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Check if there's a global React declaration in the project
fd "global.d.ts" --type fRepository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# See if React is somehow imported into the global namespace
rg "declare.*React|global.*React" . --type ts --type tsx 2>/dev/null | head -10Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Most importantly: check if the code actually has type errors by looking for tsconfig references
# Let's see if there are any build/typecheck scripts that might reveal issues
cat package.json | grep -A 50 '"scripts"' | head -30Repository: Simplereally/bloomstudio
Length of output: 1170
🏁 Script executed:
# Check `@types/react` to see if it declares React globally
# Look for index.d.ts or global declarations
rg "declare.*React|React.*ReactNode" node_modules/@types/react/index.d.ts 2>/dev/null | head -20Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Check if React is available in the global namespace through `@types/react`
# by looking at what's exported
cat node_modules/@types/react/index.d.ts 2>/dev/null | head -100Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Most directly: Check if there are actually any TypeScript errors reported for React.ReactNode usage
# Search through recent lint or type-check results
find . -name "*.json" -path "*/.*" -o -name "lint*.json" 2>/dev/null | xargs grep -l "React.ReactNode" 2>/dev/null | head -5Repository: Simplereally/bloomstudio
Length of output: 86
🏁 Script executed:
# Check the lint errors file for React.ReactNode issues
cat lint-errors.json | head -200Repository: Simplereally/bloomstudio
Length of output: 50382
🏁 Script executed:
# Also check if there are any errors specifically for sidebar-generate-button.test.tsx
cat lint-errors.json | grep -A 10 -B 5 "sidebar-generate-button"Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Final verification: Check if there's a React import at the very top of the test file
head -10 components/studio/layout/sidebar-generate-button.test.tsxRepository: Simplereally/bloomstudio
Length of output: 398
Add React type import for React.ReactNode usage.
Under the modern JSX runtime with strict TypeScript, React.ReactNode requires an explicit import—the JSX runtime change only eliminates the import requirement for JSX syntax, not for accessing the React namespace. Add a type-only import and use ReactNode directly.
Suggested fix
import { describe, it, expect, vi, beforeEach } from "vitest";
+import type { ReactNode } from "react";
import { render, screen, fireEvent } from "@testing-library/react";
// Mock UI components
vi.mock("@/components/ui/button", () => ({
Button: ({
children,
@@
}: {
- children: React.ReactNode;
+ children: ReactNode;
onClick?: () => void;
disabled?: boolean;
}) => (📝 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.
| vi.mock("@/components/ui/button", () => ({ | |
| Button: ({ | |
| children, | |
| onClick, | |
| disabled, | |
| }: { | |
| children: React.ReactNode; | |
| onClick?: () => void; | |
| disabled?: boolean; | |
| }) => ( | |
| <button onClick={onClick} disabled={disabled} data-testid="generate-button"> | |
| {children} | |
| </button> | |
| ), | |
| vi.mock("@/components/ui/button", () => ({ | |
| Button: ({ | |
| children, | |
| onClick, | |
| disabled, | |
| }: { | |
| children: ReactNode; | |
| onClick?: () => void; | |
| disabled?: boolean; | |
| }) => ( | |
| <button onClick={onClick} disabled={disabled} data-testid="generate-button"> | |
| {children} | |
| </button> | |
| ), |
🤖 Prompt for AI Agents
In `@components/studio/layout/sidebar-generate-button.test.tsx` around lines 10 -
23, The test mock uses React.ReactNode but lacks an explicit React type import;
add a type-only import "import type { ReactNode } from 'react';" at the top and
update the mock prop typing to use ReactNode instead of React.ReactNode (the
mock component named Button in the test file should be updated accordingly) so
TypeScript with the modern JSX runtime can resolve the type.
| /** | ||
| * Flux Schnell aspect ratios - Optimized for 768px max dimension | ||
| * | ||
| * Pollinations enforces: | ||
| * - max 589,824 pixels (768×768 cap) | ||
| * - width/height must be multiples of 8 | ||
| * - (width × height) must be divisible by 65,536 | ||
| * | ||
| * | Ratio | Width | Height | Pixels | | ||
| * |-------|-------|--------|-----------| | ||
| * | 1:1 | 768 | 768 | 589,824 | | ||
| * | 16:9 | 768 | 432 | 331,776 | | ||
| * | 9:16 | 432 | 768 | 331,776 | | ||
| * | 4:3 | 768 | 576 | 442,368 | | ||
| * | 3:4 | 576 | 768 | 442,368 | | ||
| * | 3:2 | 768 | 512 | 393,216 | | ||
| * | 2:3 | 512 | 768 | 393,216 | | ||
| * | 21:9 | 768 | 328 | 251,904 | | ||
| * | 9:21 | 328 | 768 | 251,904 | | ||
| */ | ||
| export const FLUX_SCHNELL_ASPECT_RATIOS: readonly AspectRatioOption[] = ( | ||
| [ | ||
| { label: "Square", value: "1:1", width: 768, height: 768, icon: "square", category: "square" }, | ||
| { label: "Landscape", value: "16:9", width: 768, height: 432, icon: "rectangle-horizontal", category: "landscape" }, | ||
| { label: "Portrait", value: "9:16", width: 432, height: 768, icon: "rectangle-vertical", category: "portrait" }, | ||
| { label: "Photo", value: "4:3", width: 768, height: 576, icon: "image", category: "landscape" }, | ||
| { label: "Portrait Photo", value: "3:4", width: 576, height: 768, icon: "frame", category: "portrait" }, | ||
| { label: "Photo Wide", value: "3:2", width: 768, height: 512, icon: "image", category: "landscape" }, | ||
| { label: "Photo Tall", value: "2:3", width: 512, height: 768, icon: "frame", category: "portrait" }, | ||
| { label: "Ultrawide", value: "21:9", width: 768, height: 328, icon: "monitor", category: "ultrawide" }, | ||
| { label: "Ultra Tall", value: "9:21", width: 328, height: 768, icon: "smartphone", category: "ultrawide" }, | ||
| { label: "Custom", value: "custom", width: 768, height: 768, icon: "sliders", category: "square" }, | ||
| ] as const | ||
| ).map(withAspectRatioTags); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n lib/config/aspect-ratios.tsRepository: Simplereally/bloomstudio
Length of output: 17012
🏁 Script executed:
python3 << 'EOF'
# Verify aspect ratio calculations against 65,536 divisibility constraint
aspect_ratios = [
("1:1", 768, 768),
("16:9", 768, 432),
("9:16", 432, 768),
("4:3", 768, 576),
("3:4", 576, 768),
("3:2", 768, 512),
("2:3", 512, 768),
("21:9", 768, 328),
("9:21", 328, 768),
]
constraint = 65536
violations = []
print("Aspect Ratio Constraint Check (divisible by 65,536):")
print("-" * 70)
for ratio, width, height in aspect_ratios:
pixels = width * height
is_divisible = (pixels % constraint) == 0
quotient = pixels / constraint
status = "✓ PASS" if is_divisible else "✗ FAIL"
print(f"{ratio:6} | {width:3}×{height:3} = {pixels:,} | ÷ 65,536 = {quotient:.4f} | {status}")
if not is_divisible:
violations.append(ratio)
print("-" * 70)
print(f"\nViolations found: {len(violations)}")
if violations:
print(f"Non-compliant ratios: {', '.join(violations)}")
EOFRepository: Simplereally/bloomstudio
Length of output: 154
🏁 Script executed:
python3 << 'EOF'
# Simple direct calculation without complex formatting
ratios = [
("1:1", 768, 768),
("16:9", 768, 432),
("9:16", 432, 768),
("4:3", 768, 576),
("3:4", 576, 768),
("3:2", 768, 512),
("2:3", 512, 768),
("21:9", 768, 328),
("9:21", 328, 768),
]
for ratio, w, h in ratios:
pixels = w * h
result = pixels / 65536
remainder = pixels % 65536
print(f"{ratio}: {w}x{h} = {pixels} -> {result:.4f} (rem: {remainder})")
EOFRepository: Simplereally/bloomstudio
Length of output: 462
🏁 Script executed:
# Check TypeScript types and imports
head -15 lib/config/aspect-ratios.tsRepository: Simplereally/bloomstudio
Length of output: 588
🏁 Script executed:
# Check if AspectRatioOption type has any type issues
rg "type AspectRatioOption" -A 10Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
fd "types" -type f -name "*.ts" | head -20Repository: Simplereally/bloomstudio
Length of output: 238
🏁 Script executed:
rg "AspectRatioOption" --type ts -B 2 -A 5Repository: Simplereally/bloomstudio
Length of output: 35834
Six FLUX_SCHNELL aspect ratios violate the documented 65,536 divisibility constraint.
Line 22 states "(width × height) must be divisible by 65,536," but 16:9 (331,776), 9:16 (331,776), 4:3 (442,368), 3:4 (442,368), 21:9 (251,904), and 9:21 (251,904) all fail this check. If Pollinations enforces this constraint, these sizes will be rejected. Verify the actual backend constraint and either adjust the dimensions or update the documentation.
🤖 Prompt for AI Agents
In `@lib/config/aspect-ratios.ts` around lines 16 - 49, The
FLUX_SCHNELL_ASPECT_RATIOS constant contains entries whose width×height values
(e.g., entries with value "16:9", "9:16", "4:3", "3:4", "21:9", "9:21") are not
divisible by 65,536 as claimed in the header; verify the actual backend
constraint and either (A) change those entries in FLUX_SCHNELL_ASPECT_RATIOS to
dimensions that preserve the aspect ratio, remain multiples of 8, do not exceed
768 on either side, and yield (width × height) divisible by 65,536, or (B)
update the header documentation to reflect the true constraint; update the array
entries and the comment together and re-run withAspectRatioTags to ensure
consistency.
| export interface Solution { | ||
| title: string; | ||
| shortTitle: string; | ||
| slug: string; | ||
| description: string; | ||
| heroPrefix?: string; | ||
| heroSuffix?: string; | ||
| features: SolutionFeature[]; | ||
| steps: SolutionStep[]; | ||
| faqs: SolutionFAQ[]; | ||
| heroImages?: string[]; | ||
| isVideo?: boolean; | ||
| showcase?: { | ||
| label: string; | ||
| aspectRatio: "square" | "portrait" | "landscape" | "landscape-wide" | "portrait-tall"; | ||
| className?: string; | ||
| src?: string; | ||
| }[]; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for Solution interface definitions across the codebase
rg -n "interface Solution\b" --type ts -A 20Repository: Simplereally/bloomstudio
Length of output: 2008
🏁 Script executed:
#!/bin/bash
# Check import statements in both files to see if they're linked
echo "=== Checking lib/seo/solution-types.ts for imports ==="
head -20 lib/seo/solution-types.ts
echo -e "\n=== Checking lib/seo-config.ts for imports ==="
head -20 lib/seo-config.tsRepository: Simplereally/bloomstudio
Length of output: 833
🏁 Script executed:
#!/bin/bash
# Verify file existence and get exact line counts
wc -l lib/seo/solution-types.ts lib/seo-config.tsRepository: Simplereally/bloomstudio
Length of output: 135
🏁 Script executed:
#!/bin/bash
# Find all imports of Solution or types from these files
echo "=== Imports from lib/seo/solution-types.ts ==="
rg "from.*['\"].*solution-types" --type ts
echo -e "\n=== Imports from lib/seo-config.ts ==="
rg "from.*['\"].*seo-config" --type ts
echo -e "\n=== References to Solution type ==="
rg "\bSolution\b" --type ts | grep -E "import|from" | head -20Repository: Simplereally/bloomstudio
Length of output: 3072
Consolidate duplicate type definitions to single source of truth.
The Solution, SolutionFeature, SolutionStep, and SolutionFAQ interfaces are identically defined in both lib/seo/solution-types.ts and lib/seo-config.ts. This duplication violates DRY principles and creates import fragmentation: solution implementations import from lib/seo/solution-types.ts while components import the same types from lib/seo-config.ts.
Have lib/seo-config.ts import and re-export these types from lib/seo/solution-types.ts to establish a single source of truth.
🤖 Prompt for AI Agents
In `@lib/seo/solution-types.ts` around lines 17 - 35, The Solution,
SolutionFeature, SolutionStep, and SolutionFAQ interfaces are duplicated; update
the module that currently re-defines them to instead import and re-export those
types from the canonical definitions (Solution, SolutionFeature, SolutionStep,
SolutionFAQ) so there is a single source of truth; locate the duplicate
definitions in the other module and replace them with statements that import {
Solution, SolutionFeature, SolutionStep, SolutionFAQ } from the original module
and then export them (export type { Solution, SolutionFeature, SolutionStep,
SolutionFAQ }) so all consumers import the types from the same place and remove
the duplicated type declarations.
| User@DESKTOP-EV6QN3L MINGW64 /c/Code/pixelstream (main) | ||
| $ git stash -u | ||
| warning: LF will be replaced by CRLF in lint_report.txt. | ||
| The file will have its original line endings in your working directory | ||
| warning: LF will be replaced by CRLF in lint_reports/max-lines.txt. | ||
| The file will have its original line endings in your working directory | ||
| warning: LF will be replaced by CRLF in scripts/fix-test-types.mjs. | ||
| The file will have its original line endings in your working directory | ||
| warning: LF will be replaced by CRLF in scripts/process-lint-json.ts. | ||
| The file will have its original line endings in your working directory | ||
| Saved working directory and index state WIP on main: 87041aa chore: add tests | ||
|
|
||
| User@DESKTOP-EV6QN3L MINGW64 /c/Code/pixelstream (main) | ||
| $ git pull | ||
| Updating 87041aa..444c520 | ||
| Fast-forward | ||
| convex/batchGeneration.test.ts | 121 ---------- | ||
| convex/batchProcessor.test.ts | 282 ---------------------- | ||
| convex/contentAnalysis.test.ts | 61 ----- | ||
| convex/crons.test.ts | 59 ----- | ||
| convex/detailsMigration.test.ts | 82 ------- | ||
| convex/favorites.test.ts | 189 --------------- | ||
| convex/follows.test.ts | 156 ------------ | ||
| convex/generatedImages.test.ts | 327 ------------------------- | ||
| convex/http.test.ts | 123 ---------- | ||
| convex/lib/crypto.test.ts | 47 ---- | ||
| convex/lib/groq.test.ts | 207 ---------------- | ||
| convex/lib/nsfwDetection.test.ts | 53 ---- | ||
| convex/lib/openrouter.test.ts | 346 -------------------------- | ||
| convex/lib/pollinations.test.ts | 376 ----------------------------- | ||
| convex/lib/promptInference.test.ts | 69 ------ | ||
| convex/lib/providerHealth.test.ts | 193 --------------- | ||
| convex/lib/providerHealthFunctions.test.ts | 159 ------------ | ||
| convex/lib/r2.test.ts | 69 ------ | ||
| convex/lib/retry.test.ts | 198 --------------- | ||
| convex/lib/subscription.test.ts | 201 --------------- | ||
| convex/lib/videoPreview.test.ts | 121 ---------- | ||
| convex/lib/videoThumbnail.test.ts | 129 ---------- | ||
| convex/lib/visionAnalysis.test.ts | 57 ----- | ||
| convex/orphanCleanup.test.ts | 197 --------------- | ||
| convex/orphanCleanupQueries.test.ts | 105 -------- | ||
| convex/promptInference.test.ts | 115 --------- | ||
| convex/promptLibrary.test.ts | 249 ------------------- | ||
| convex/rateLimits.test.ts | 106 -------- | ||
| convex/referenceImages.test.ts | 131 ---------- | ||
| convex/sensitivityMigration.test.ts | 128 ---------- | ||
| convex/singleGeneration.test.ts | 189 --------------- | ||
| convex/singleGenerationProcessor.test.ts | 133 ---------- | ||
| convex/stripe.test.ts | 145 ----------- | ||
| convex/tempTagStats.test.ts | 241 ------------------ | ||
| convex/thumbnailMigration.test.ts | 181 -------------- | ||
| convex/thumbnailMigrationActions.test.ts | 163 ------------- | ||
| convex/usernameGenerator.test.ts | 206 ---------------- | ||
| convex/users.test.ts | 337 -------------------------- | ||
| eslint.config.mjs | 12 + | ||
| tsconfig.json | 4 +- | ||
| 40 files changed, 15 insertions(+), 6252 deletions(-) | ||
| delete mode 100644 convex/batchGeneration.test.ts | ||
| delete mode 100644 convex/batchProcessor.test.ts | ||
| delete mode 100644 convex/contentAnalysis.test.ts | ||
| delete mode 100644 convex/crons.test.ts | ||
| delete mode 100644 convex/detailsMigration.test.ts | ||
| delete mode 100644 convex/favorites.test.ts | ||
| delete mode 100644 convex/follows.test.ts | ||
| delete mode 100644 convex/generatedImages.test.ts | ||
| delete mode 100644 convex/http.test.ts | ||
| delete mode 100644 convex/lib/crypto.test.ts | ||
| delete mode 100644 convex/lib/groq.test.ts | ||
| delete mode 100644 convex/lib/nsfwDetection.test.ts | ||
| delete mode 100644 convex/lib/openrouter.test.ts | ||
| delete mode 100644 convex/lib/pollinations.test.ts | ||
| delete mode 100644 convex/lib/promptInference.test.ts | ||
| delete mode 100644 convex/lib/providerHealth.test.ts | ||
| delete mode 100644 convex/lib/providerHealthFunctions.test.ts | ||
| delete mode 100644 convex/lib/r2.test.ts | ||
| delete mode 100644 convex/lib/retry.test.ts | ||
| delete mode 100644 convex/lib/subscription.test.ts | ||
| delete mode 100644 convex/lib/videoPreview.test.ts | ||
| delete mode 100644 convex/lib/videoThumbnail.test.ts | ||
| delete mode 100644 convex/lib/visionAnalysis.test.ts | ||
| delete mode 100644 convex/orphanCleanup.test.ts | ||
| delete mode 100644 convex/orphanCleanupQueries.test.ts | ||
| delete mode 100644 convex/promptInference.test.ts | ||
| delete mode 100644 convex/promptLibrary.test.ts | ||
| delete mode 100644 convex/rateLimits.test.ts | ||
| delete mode 100644 convex/referenceImages.test.ts | ||
| delete mode 100644 convex/sensitivityMigration.test.ts | ||
| delete mode 100644 convex/singleGeneration.test.ts | ||
| delete mode 100644 convex/singleGenerationProcessor.test.ts | ||
| delete mode 100644 convex/stripe.test.ts | ||
| delete mode 100644 convex/tempTagStats.test.ts | ||
| delete mode 100644 convex/thumbnailMigration.test.ts | ||
| delete mode 100644 convex/thumbnailMigrationActions.test.ts | ||
| delete mode 100644 convex/usernameGenerator.test.ts | ||
| delete mode 100644 convex/users.test.ts | ||
|
|
||
| User@DESKTOP-EV6QN3L MINGW64 /c/Code/pixelstream (main) | ||
| $ git stash pop | ||
| Removing use-image-lightbox-old.tsx | ||
| Auto-merging tsconfig.json | ||
| Removing scripts/optimize-solutions.ts | ||
| Removing scripts/migrate-local.ts | ||
| Removing image-lightbox-old.tsx | ||
| Auto-merging eslint.config.mjs | ||
| CONFLICT (content): Merge conflict in eslint.config.mjs | ||
| CONFLICT (modify/delete): convex/users.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/users.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/usernameGenerator.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/usernameGenerator.test.ts left in tree. | ||
| Removing convex/thumbnailMigrationActions.ts | ||
| Removing convex/thumbnailMigration.ts | ||
| CONFLICT (modify/delete): convex/stripe.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/stripe.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/singleGenerationProcessor.test.ts deleted in Updated upstream and modified in Stashed | ||
| changes. Version Stashed changes of convex/singleGenerationProcessor.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/singleGeneration.test.ts deleted in Updated upstream and modified in Stashed changes. | ||
| Version Stashed changes of convex/singleGeneration.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/sensitivityMigration.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/sensitivityMigration.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/referenceImages.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/referenceImages.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/promptLibrary.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/promptLibrary.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/promptInference.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/promptInference.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/orphanCleanupQueries.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/orphanCleanupQueries.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/orphanCleanup.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/orphanCleanup.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/videoThumbnail.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/lib/videoThumbnail.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/videoPreview.test.ts deleted in Updated upstream and modified in Stashed changes. | ||
| Version Stashed changes of convex/lib/videoPreview.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/subscription.test.ts deleted in Updated upstream and modified in Stashed changes. | ||
| Version Stashed changes of convex/lib/subscription.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/providerHealthFunctions.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/lib/providerHealthFunctions.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/openrouter.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/lib/openrouter.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/lib/groq.test.ts deleted in Updated upstream and modified in Stashed changes. Version | ||
| Stashed changes of convex/lib/groq.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/http.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/http.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/generatedImages.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/generatedImages.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/follows.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/follows.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/favorites.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/favorites.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/crons.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/crons.test.ts left in tree. | ||
| CONFLICT (modify/delete): convex/batchProcessor.test.ts deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of convex/batchProcessor.test.ts left in tree. | ||
| The stash entry is kept in case you need it again. No newline at end of file |
There was a problem hiding this comment.
Remove local merge transcript from the repo.
This file captures local paths/usernames and conflict output, which shouldn’t be committed. Please delete it (or keep it as a local note outside version control).
🤖 Prompt for AI Agents
In `@pull-conflicts.md` around lines 1 - 136, The file pull-conflicts.md contains
local merge transcripts and sensitive local info and must be removed from the
repository: delete pull-conflicts.md from the repo, remove it from the index
(git rm --cached or git rm) so it’s not committed, commit that deletion, and add
an entry to .gitignore to prevent re-adding similar local conflict transcripts;
ensure no other code references pull-conflicts.md (search for its filename)
before committing.
| # Replace @ts-ignore with @ts-expect-error | ||
| if ($content -match '@ts-ignore') { | ||
| $content = $content -replace '@ts-ignore', '@ts-expect-error' | ||
| $modified = $true |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure
git ls-files | head -20Repository: Simplereally/bloomstudio
Length of output: 923
🏁 Script executed:
# Find the PowerShell script and understand its full context
find . -name "fix-all-test-any.ps1" -type fRepository: Simplereally/bloomstudio
Length of output: 98
🏁 Script executed:
# Look for linting configuration files
fd -e ".eslintrc*" -e "eslint.config.*" -e "tsconfig.json"Repository: Simplereally/bloomstudio
Length of output: 50
🏁 Script executed:
# Read the full PowerShell script
cat -n ./scripts/fix-all-test-any.ps1Repository: Simplereally/bloomstudio
Length of output: 2663
🏁 Script executed:
# Look for CLAUDE.md and other documentation
find . -name "CLAUDE.md" -o -name "README.md" | head -10Repository: Simplereally/bloomstudio
Length of output: 110
🏁 Script executed:
# Check the coding standards
cat .agent/rules/coding-standards.mdRepository: Simplereally/bloomstudio
Length of output: 3728
🏁 Script executed:
# Search for `@ts-expect-error` usage in the codebase to understand the pattern
rg "@ts-expect-error" -A 1 --max-count 5Repository: Simplereally/bloomstudio
Length of output: 50382
Add rationale comment to @ts-expect-error replacement.
The script generates @ts-expect-error directives without required explanations, violating the repo's coding standards. Files processed by this script will fail linting. The suggested placeholder comment is appropriate:
🔧 Suggested fix
- if ($content -match '@ts-ignore') {
- $content = $content -replace '@ts-ignore', '@ts-expect-error'
+ if ($content -match '@ts-ignore') {
+ $content = $content -replace '@ts-ignore', '@ts-expect-error -- TODO: explain and add removal tracking'
$modified = $true
}Per the repo's TypeScript guidelines, @ts-expect-error must include a comment explaining why and a link/TODO for removal tracking.
📝 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.
| # Replace @ts-ignore with @ts-expect-error | |
| if ($content -match '@ts-ignore') { | |
| $content = $content -replace '@ts-ignore', '@ts-expect-error' | |
| $modified = $true | |
| # Replace `@ts-ignore` with `@ts-expect-error` | |
| if ($content -match '@ts-ignore') { | |
| $content = $content -replace '@ts-ignore', '@ts-expect-error -- TODO: explain and add removal tracking' | |
| $modified = $true |
🤖 Prompt for AI Agents
In `@scripts/fix-all-test-any.ps1` around lines 10 - 13, When replacing
'@ts-ignore' with '@ts-expect-error' in scripts/fix-all-test-any.ps1, ensure you
also insert a rationale comment after the directive (e.g., append " // TODO:
explain reason and add tracking link" or the repo's required explanation) so the
generated '@ts-expect-error' includes the mandatory explanation; update the
replacement logic that manipulates $content (where it currently does $content
-replace '@ts-ignore', '@ts-expect-error') to produce '@ts-expect-error //
<reason or tracking link>' and keep setting $modified = $true.
| if ($content -match '\(children: any\)') { | ||
| $content = $content -replace '\(children: any\)', '(children: React.ReactNode)' | ||
| $modified = $true | ||
| } | ||
|
|
||
| if ($content -match '\{ children \}: any') { | ||
| $content = $content -replace '\{ children \}: any', '{ children }: { children: React.ReactNode }' | ||
| $modified = $true |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n scripts/fix-all-test-any.ps1 | head -100Repository: Simplereally/bloomstudio
Length of output: 2663
🏁 Script executed:
# Find test files and check for React imports
fd -e test.ts -e test.tsx | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -20 "{}"'Repository: Simplereally/bloomstudio
Length of output: 6206
🏁 Script executed:
# Search for test files with React imports and children patterns
rg -l "children.*any" --type ts --type tsx | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -30 "{}"'Repository: Simplereally/bloomstudio
Length of output: 95
🏁 Script executed:
# Search for files containing the patterns being replaced
rg "children.*any" -l | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -40 "{}"'Repository: Simplereally/bloomstudio
Length of output: 50382
🏁 Script executed:
# Search for files with the specific patterns that will be replaced
rg '\(children:\s*any\)|\{\s*children\s*\}:\s*any' --type ts --type-add 'ts:include:*.{ts,tsx}' | head -20Repository: Simplereally/bloomstudio
Length of output: 127
🏁 Script executed:
# Search for files with the specific patterns that will be replaced
rg '\(children:\s*any\)|\{\s*children\s*\}:\s*any' --include='*.test.ts' --include='*.test.tsx' | head -20Repository: Simplereally/bloomstudio
Length of output: 509
🏁 Script executed:
# Search for files with the specific patterns that will be replaced
rg '\(children:\s*any\)|\{\s*children\s*\}:\s*any' -t ts | head -20Repository: Simplereally/bloomstudio
Length of output: 1727
🏁 Script executed:
# Check for React imports in the affected test files
for file in components/ui/rich-tooltip.test.tsx components/ui/enhance-button.test.tsx components/studio/delete-image-dialog.test.tsx; do
echo "=== $file ==="
head -20 "$file" | grep -E "^import|^from"
doneRepository: Simplereally/bloomstudio
Length of output: 759
🏁 Script executed:
# Search for React import more comprehensively
for file in components/ui/rich-tooltip.test.tsx components/ui/enhance-button.test.tsx components/studio/delete-image-dialog.test.tsx; do
echo "=== $file ==="
if grep -q "import.*React" "$file"; then
echo "HAS React import"
grep "import.*React" "$file"
else
echo "NO React import found"
fi
echo ""
doneRepository: Simplereally/bloomstudio
Length of output: 281
React.ReactNode injection without import handling will cause type-checking failures.
The script will replace { children }: any patterns in test files with React.ReactNode (lines 47-54), but has no logic to ensure React is imported. This creates broken TypeScript in affected test files like components/ui/rich-tooltip.test.tsx, components/ui/enhance-button.test.tsx, and others that currently lack React imports.
Add import detection before replacement. Check if a file already imports React before injecting React.ReactNode, or use a non-namespaced approach (e.g., import ReactNode directly and use ReactNode instead of React.ReactNode).
🔧 Suggested approach
+ $needsReactNodeImport = $false
+
if ($content -match '\(children: any\)') {
$content = $content -replace '\(children: any\)', '(children: React.ReactNode)'
$modified = $true
+ $needsReactNodeImport = $true
}
if ($content -match '\{ children \}: any') {
$content = $content -replace '\{ children \}: any', '{ children }: { children: React.ReactNode }'
$modified = $true
+ $needsReactNodeImport = $true
}
+
+ if ($needsReactNodeImport -and ($content -notmatch 'from\s+["'']react["'']')) {
+ $content = "import type { ReactNode } from ""react"";`n" + $content
+ $content = $content -replace 'React\.ReactNode', 'ReactNode'
+ }📝 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 ($content -match '\(children: any\)') { | |
| $content = $content -replace '\(children: any\)', '(children: React.ReactNode)' | |
| $modified = $true | |
| } | |
| if ($content -match '\{ children \}: any') { | |
| $content = $content -replace '\{ children \}: any', '{ children }: { children: React.ReactNode }' | |
| $modified = $true | |
| $needsReactNodeImport = $false | |
| if ($content -match '\(children: any\)') { | |
| $content = $content -replace '\(children: any\)', '(children: React.ReactNode)' | |
| $modified = $true | |
| $needsReactNodeImport = $true | |
| } | |
| if ($content -match '\{ children \}: any') { | |
| $content = $content -replace '\{ children \}: any', '{ children }: { children: React.ReactNode }' | |
| $modified = $true | |
| $needsReactNodeImport = $true | |
| } | |
| if ($needsReactNodeImport -and ($content -notmatch 'from\s+["'']react["'']')) { | |
| $content = "import type { ReactNode } from ""react"";`n" + $content | |
| $content = $content -replace 'React\.ReactNode', 'ReactNode' | |
| } |
🤖 Prompt for AI Agents
In `@scripts/fix-all-test-any.ps1` around lines 47 - 54, The script
scripts/fix-all-test-any.ps1 currently replaces '(children: any)' and '{
children }: any' with React.ReactNode but doesn't ensure an import for React,
which breaks TS; update the script to first detect existing imports of React or
ReactNode (e.g., "import React" or "import type { ReactNode } from 'react'") and
only perform the replacement if an appropriate import exists, otherwise inject a
minimal type import (preferably "import type { ReactNode } from 'react'") at the
top of the file and use the non-namespaced replacement '(children: ReactNode)'
and '{ children }: { children: ReactNode }'; ensure the detection/insertion
logic is applied before doing replacements for the patterns '(children: any)'
and '{ children }: any' so files like components/ui/*.test.tsx get correct
imports.
Summary by CodeRabbit
New Features
Improvements
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.