Skip to content

Replace the generator flow with the user page editor#138

Open
RLAlpha49 wants to merge 861 commits intomainfrom
user-flow
Open

Replace the generator flow with the user page editor#138
RLAlpha49 wants to merge 861 commits intomainfrom
user-flow

Conversation

@RLAlpha49
Copy link
Copy Markdown
Owner

@RLAlpha49 RLAlpha49 commented Dec 24, 2025

Summary

This PR replaces the old generator-first workflow with a search-first user page editor. Instead of opening a generator modal, users now search for an AniList profile, land on a canonical /user/[username] page, and manage their card setup directly in the editor.

What changed

User page editor + new user flow

  • Added UserPageEditor and the supporting store/hooks/components for:
    • first-visit starter card setup for new users
    • autosave, local draft recovery, and stale-write conflict recovery
    • global and per-card settings panels
    • bulk copy/download actions, category grouping, filtering, reorder mode, command palette, help dialog, guided tour, and live previews
    • settings templates/import/export and example-to-editor style handoff

Search, home, and examples flow

  • Made /search the main entry point into card creation/editing
  • Updated home/examples CTAs and related navigation to route to /search instead of opening the old generator
  • Rebuilt the examples experience into a filterable gallery with category navigation, sticky filters, improved hero/CTA content, and reusable style presets that can be queued into the editor
  • Added canonical profile routing at /user/[username] while preserving lookup-based entry points through redirect logic

Persistence, rendering, and API updates

  • Reworked card persistence to support:
    • disabled cards
    • merged globalSettings
    • card ordering
    • ifMatchUpdatedAt optimistic concurrency
    • better preservation of per-card overrides
  • Reworked user persistence around split Redis records with stale-write protection and shared request/telemetry helpers
  • Expanded rendering/preview utilities for gradients, borders, previews, math tooltips, and settings IO
  • Added broader API hardening around validation, request guards, telemetry, error handling, and error reporting

Cleanup and removals

  • Removed the legacy generator surfaces, old user-page client flow, and the /settings and /license pages
  • Retired legacy card surfaces like Activity Heatmap and Recent Activity Feed
  • Kept /StatCards/{username}/{key}.svg as a compatibility notice route instead of a live stat-card render

Tests, docs, and infra

  • Refreshed docs, OpenAPI, diagrams, and CI/workflow config to match the new flow
  • Added/updated broad unit and e2e coverage for the editor, autosave/conflicts, examples, API behavior, rendering, and supporting utilities

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 24, 2025

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

Project Deployment Actions Updated (UTC)
anicards Ready Ready Preview, Comment Apr 23, 2026 1:08am

Copy link
Copy Markdown
Owner Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@gooroo-dev
Copy link
Copy Markdown

gooroo-dev Bot commented Dec 24, 2025

Please double check the following review of the pull request:

🐞Mistake 🤪Typo 🚨Security 🚀Performance 💪Best Practices 📖Readability ❓Others
2 0 0 2 3 4 1

Changes in the diff

  • ✅ Added support for disabled cards with minimal data in store-cards API.
  • ✅ Optimized border color saving logic to avoid saving borderColor when border is disabled.
  • 🚀 Improved performance by avoiding unnecessary DB lookups when full color params are provided with custom preset.
  • 📖 Improved readability by trimming username in user page metadata and search form.
  • 📖 Improved readability by renaming some variables and cleaning up code.
  • 🛠️ Removed unused components and pages (Settings, License, StatCardGenerator).
  • 🛠️ Updated navigation to redirect to search page instead of opening generator modal.
  • ➕ Added MathTooltipContent component for rendering LaTeX math in tooltips.
  • ➕ Added BulkActionsToolbar and CardCategorySection components.
  • ➕ Added UserPageEditor and related components for new user flow.
  • ➕ Added AlertDialog component for confirmation dialogs.
  • ➕ Added Tabs component for tabbed UI.
  • ➕ Added ColorPreviewCard for color previews.
  • ➕ Added SettingsContent and CardSettingsPanel for settings UI.
  • ➕ Added CardSettingsDialog for per-card settings dialog.
  • ➕ Added useCardAutoSave hook for debounced auto-saving.
  • ➕ Added user-page-editor Zustand store for user page state management.
  • 🛠️ Updated API and utils to handle globalSettings in CardsRecord.
  • 🛠️ Updated tests to reflect new user flow and features.
  • 🐞 Fixed validation to accept disabled boolean field in cards.
  • 🐞 Fixed bug in color override logic to respect useCustomSettings flag.

Identified Issues

ID Type Details Severity Confidence
1 🐞Mistake store-cards API did not accept or validate disabled boolean field in card config. 🔴High 🔴High
2 🐞Mistake Border color saved even when border is disabled globally, causing unnecessary data save. 🟠Medium 🔴High
3 🚀Performance DB lookup avoided when full color params are provided with custom preset, improving perf. 🟠Medium 🔴High
4 💪Best Practices Usernames trimmed before usage in metadata and search to avoid issues with whitespace. 🟡Low 🔴High
5 📖Readability Renamed variables and cleaned up code for clarity in store-cards and user page editor. 🟡Low 🟠Medium
6 ❓Others Removed many unused components and pages to simplify codebase. 🟡Low 🟠Medium

ID 1: store-cards API did not accept or validate disabled boolean field in card config.

Explanation:
The API did not validate the disabled field as a boolean, which could cause errors or invalid data storage. The new code adds validation for the disabled field and supports storing minimal data for disabled cards.

Fix:

// In lib/api-utils.ts, validateCardRequiredFields:
const isDisabled = card["disabled"] === true;
const fieldsToValidate = isDisabled ? ["cardName"] : requiredStringFields;
const reqStrErr = validateRequiredStringFields(card, fieldsToValidate);
if (reqStrErr) return reqStrErr;

// In app/api/store-cards/route.ts, buildCardConfig:
if (incoming.disabled === true) {
  return {
    cardName: incoming.cardName,
    disabled: true,
  };
}

Explanation of fix:
This ensures disabled cards only require minimal fields and are stored with minimal data, avoiding validation errors.


ID 2: Border color saved even when border is disabled globally, causing unnecessary data save.

Explanation:
The API saved borderColor in globalSettings and card configs even when border was disabled, causing redundant data and potential confusion.

Fix:

// In app/api/store-cards/route.ts, POST handler:
const effectiveBorderEnabled =
  globalSettings?.borderEnabled ?? existingGlobalSettings?.borderEnabled;

const effectiveBorderColor = effectiveBorderEnabled
  ? (globalSettings.borderColor ?? existingGlobalSettings?.borderColor)
  : undefined;

const mergedGlobalSettings: GlobalCardSettings | undefined = globalSettings
  ? {
      ...globalSettings,
      borderEnabled: effectiveBorderEnabled,
      borderColor: effectiveBorderColor,
      borderRadius: effectiveBorderRadius,
    }
  : existingGlobalSettings;

// In buildCardConfig:
const effectiveBorderColor = globalBorderEnabled
  ? incoming.borderColor
  : undefined;

Explanation of fix:
This logic prevents saving borderColor if border is disabled, reducing unnecessary data storage.


ID 3: DB lookup avoided when full color params are provided with custom preset, improving performance.

Explanation:
The GET card API now skips DB lookup when colorPreset is "custom" but all individual colors are provided in URL, improving response time.

Fix:
Test added in tests/unit/api/card/card.test.ts to verify this behavior.


ID 4: Usernames trimmed before usage in metadata and search to avoid issues with whitespace.

Explanation:
Metadata generation and search form now trim usernames to avoid issues with leading/trailing spaces.

Fix:
In app/user/page.tsx and components/search/SearchForm.tsx, usernames are trimmed before use.


ID 5: Renamed variables and cleaned up code for clarity in store-cards and user page editor.

Explanation:
Improved variable naming and code structure for better maintainability and readability.


ID 6: Removed many unused components and pages to simplify codebase.

Explanation:
Removed deprecated or unused components like Settings page, License page, StatCardGenerator modal, and related test fixtures.


Summary

The PR implements a new user flow with a user page editor replacing the old generator modal, improves performance by optimizing DB lookups, adds support for disabled cards, cleans up and removes unused code, and improves UI and UX with better navigation and tooltips.

Missing Tests

  • Add tests for disabled cards in store-cards API (already present).
  • Add tests for border color saving logic when border is disabled/enabled (already present).
  • Add tests for new user flow in UserPageEditor (should be covered by integration tests).
  • Add tests for MathTooltipContent rendering LaTeX (unit tests).
  • Add tests for BulkActionsToolbar functionality (unit or integration tests).
  • Add tests for CardSettingsDialog and GlobalSettingsPanel UI and behavior.

Conclusion

The PR is well-structured and improves the codebase significantly. The identified issues are addressed properly. Adding some UI component tests for new components like MathTooltipContent and BulkActionsToolbar would further improve coverage.

Summon me to re-review when updated! Yours, Gooroo.dev
Please react or reply to this review!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a complete redesign of the user flow, transitioning from a multi-step wizard-based generator to a direct search-to-profile approach. The changes include a new user page editor with auto-save functionality, support for disabled cards and gradient colors, and comprehensive test updates.

Key Changes:

  • Refactored user flow from generator wizard to direct profile editing
  • Added new user auto-setup on first visit
  • Implemented auto-save for card configurations
  • Added support for disabled cards and gradient colors
  • Removed legacy generator and settings E2E tests

Reviewed changes

Copilot reviewed 80 out of 81 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/unit/api/store-cards/store-cards.test.ts Added tests for disabled cards, backfilling, and global settings optimization
tests/unit/api/card/card.test.ts Added tests for color override and custom preset handling
tests/e2e/settings/settings.spec.ts Removed (deprecated settings page)
tests/e2e/home/home.spec.ts Updated to navigate to search instead of opening generator
tests/e2e/generator/*.spec.ts Removed (deprecated generator wizard)
tests/e2e/fixtures/test-utils.ts Removed GeneratorPage helper
package.json Added new dependencies: @radix-ui/react-alert-dialog, @radix-ui/react-tabs, katex
lib/utils.ts Added gradient color validation support
lib/types/records.ts Added disabled, useCustomSettings fields; made variation optional; added GlobalCardSettings
lib/stores/user-preferences.ts Removed defaultUsername field
lib/stores/user-page-editor.ts New comprehensive state management for user page editing
lib/stores/index.ts Updated exports for new store structure
lib/stores/card-settings.ts Removed (replaced by user-page-editor)
lib/seo.ts Removed settings and license page SEO configs
lib/card-info-tooltips.ts New file with card tooltips and LaTeX formula support
lib/card-groups.ts Updated to support gradient colors in URL params
lib/card-data/config.ts Updated color resolution logic for custom presets
lib/api-utils.ts Added disabled field validation
hooks/useStatCardSubmit.ts Removed (deprecated generator hook)
hooks/useCardAutoSave.ts New auto-save hook with debouncing
components/user/UserPageHeader.tsx New header component with save state indicators
components/user/UserPageEditor.tsx New comprehensive user page editor
components/user/UserPageClient.tsx Removed (replaced by UserPageEditor)
components/user/UserCard.tsx Removed (functionality moved to CardTile)
components/user/GlobalSettingsPanel.tsx New global settings panel
components/ui/Tabs.tsx New tabs component wrapper
components/search/SearchHeroSection.tsx Updated description to reflect new auto-setup flow

I have completed my review. No critical issues were found that require comments. The changes are well-structured and follow the project's patterns. The test coverage has been updated appropriately to reflect the new user flow, though the removal of E2E tests for the generator means that the new user page editor flow relies primarily on unit tests.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

Important

Review skipped

Too many files!

This PR contains 271 files, which is 121 over the limit of 150.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ce33d8d6-a71b-4000-91a9-58e66cf8575a

📥 Commits

Reviewing files that changed from the base of the PR and between 93da28a and 7aa5a2d.

⛔ Files ignored due to path filters (2)
  • app/favicon.ico is excluded by !**/*.ico
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (271)
  • .coderabbit.yaml
  • .editorconfig
  • .env.example
  • .github/PULL_REQUEST_TEMPLATE.md
  • .github/actions/setup-bun/action.yml
  • .github/agents/expert-nextjs-developer.agent.md
  • .github/agents/janitor.agent.md
  • .github/dependabot.yml
  • .github/instructions/nextjs-tailwind.instructions.md
  • .github/instructions/nextjs.instructions.md
  • .github/instructions/performance-optimization.instructions.md
  • .github/instructions/playwright-typescript.instructions.md
  • .github/instructions/reactjs.instructions.md
  • .github/instructions/security-and-owasp.instructions.md
  • .github/instructions/shadcn-nextjs-tailwind.instructions.md
  • .github/instructions/typescript.instructions.md
  • .github/prompts/implement-new-card-types.prompt.md
  • .github/prompts/new-card-types-research.prompt.md
  • .github/prompts/unified-card-types-combiner.prompt.md
  • .github/workflows/check-updates.yml
  • .github/workflows/ci.yml
  • .github/workflows/dependency-review.yml
  • .github/workflows/deployed-smoke.yml
  • .github/workflows/security-audits.yml
  • .gitignore
  • .husky/pre-commit
  • .husky/pre-push
  • .ncurc.json
  • .npmrc
  • .prettierignore
  • .serena/project.yml
  • AGENTS.md
  • README.md
  • TODO.md
  • app/StatCards/[username]/[key].svg/route.ts
  • app/about/page.tsx
  • app/api/anilist/route.ts
  • app/api/card/route.ts
  • app/api/convert/route.ts
  • app/api/cron/analytics-reporting/route.ts
  • app/api/cron/route.ts
  • app/api/error-reports/route.ts
  • app/api/get-cards/route.ts
  • app/api/get-user/route.ts
  • app/api/store-cards/route.ts
  • app/api/store-users/route.ts
  • app/card.png/route.ts
  • app/contact/loading.tsx
  • app/contact/page.tsx
  • app/error.tsx
  • app/examples/ExamplesPageClient.tsx
  • app/examples/[collection]/page.tsx
  • app/examples/examples-catalog.ts
  • app/examples/gallery/page.tsx
  • app/examples/loading.tsx
  • app/examples/page.tsx
  • app/global-error.tsx
  • app/globals.css
  • app/layout.tsx
  • app/loading.tsx
  • app/middleware.ts
  • app/not-found.tsx
  • app/page.tsx
  • app/privacy/loading.tsx
  • app/privacy/page.tsx
  • app/projects/loading.tsx
  • app/projects/page.tsx
  • app/providers.tsx
  • app/robots.ts
  • app/search/SearchHeroShell.tsx
  • app/search/loading.tsx
  • app/search/page.tsx
  • app/sitemap-profiles.xml/route.ts
  • app/sitemap-static.xml/route.ts
  • app/sitemap.xml/route.ts
  • app/user/[username]/loading.tsx
  • app/user/[username]/page.tsx
  • app/user/loading.tsx
  • app/user/page.tsx
  • bunfig.toml
  • components.json
  • components/AnalyticsProvider.tsx
  • components/AppSidebar.tsx
  • components/CTASection.tsx
  • components/CardPreviewPlaceholder.tsx
  • components/CspNonceContext.tsx
  • components/CustomScrollbar.tsx
  • components/DarkModeToggle.tsx
  • components/ErrorBoundary.tsx
  • components/ErrorPopup.tsx
  • components/FloatingCardsLayer.tsx
  • components/Footer.tsx
  • components/GithubCorner.tsx
  • components/GoogleAnalytics.tsx
  • components/GridPattern.tsx
  • components/Header.tsx
  • components/HeaderClient.tsx
  • components/HeroBadge.tsx
  • components/HeroHighlights.tsx
  • components/ImageWithSkeleton.tsx
  • components/LayoutShell.tsx
  • components/LoadingSpinner.tsx
  • components/MathTooltipContent.tsx
  • components/MathTooltipContentRenderer.tsx
  • components/PageShell.tsx
  • components/ResourceHints.tsx
  • components/SimpleIcons.tsx
  • components/SkipLink.tsx
  • components/StatCard.tsx
  • components/StructuredDataScript.tsx
  • components/contact/ContactCTA.tsx
  • components/contact/ContactChannels.tsx
  • components/contact/ContactHeroSection.tsx
  • components/contact/ContactReasons.tsx
  • components/examples/CTASection.tsx
  • components/examples/CategoryNavigation.tsx
  • components/examples/CategorySection.tsx
  • components/examples/ExampleCard.tsx
  • components/examples/ExamplesHeroSection.tsx
  • components/examples/SearchFilterBar.tsx
  • components/examples/index.ts
  • components/examples/types.ts
  • components/home/BentoFeatures.tsx
  • components/home/CardMarquee.tsx
  • components/home/FeaturesSection.tsx
  • components/home/HeroSection.tsx
  • components/home/HomeCTA.tsx
  • components/home/PreviewShowcase.tsx
  • components/home/ProcessSteps.tsx
  • components/home/StatsRibbon.tsx
  • components/marketing/MarketingBackdrop.tsx
  • components/marketing/SectionReveal.tsx
  • components/projects/FeaturedProject.tsx
  • components/projects/ProjectCard.tsx
  • components/projects/ProjectCollection.tsx
  • components/projects/ProjectEthos.tsx
  • components/projects/ProjectsCTA.tsx
  • components/projects/ProjectsHeroSection.tsx
  • components/projects/constants.ts
  • components/projects/types.ts
  • components/search/SearchCTA.tsx
  • components/search/SearchCapabilities.tsx
  • components/search/SearchForm.tsx
  • components/search/SearchHeroSection.tsx
  • components/search/SearchJourney.tsx
  • components/stat-card-generator/ColorPickerGroup.tsx
  • components/stat-card-generator/ColorPresetSelector.tsx
  • components/stat-card-generator/constants.ts
  • components/ui/Alert.tsx
  • components/ui/AlertDialog.tsx
  • components/ui/Button.tsx
  • components/ui/Card.tsx
  • components/ui/Checkbox.tsx
  • components/ui/ColorValueSwatch.tsx
  • components/ui/Dialog.tsx
  • components/ui/Input.tsx
  • components/ui/Label.tsx
  • components/ui/Motion.ts
  • components/ui/Popover.tsx
  • components/ui/Select.tsx
  • components/ui/Separator.tsx
  • components/ui/Sheet.tsx
  • components/ui/Sidebar.tsx
  • components/ui/Skeleton.tsx
  • components/ui/Switch.tsx
  • components/ui/Tabs.tsx
  • components/ui/Toaster.tsx
  • components/ui/Tooltip.tsx
  • components/user/BulkActionsToolbar.tsx
  • components/user/CardCategorySection.tsx
  • components/user/CardSettingsDialog.tsx
  • components/user/CardSettingsPanel.tsx
  • components/user/CardTile.tsx
  • components/user/ColorPreviewCard.tsx
  • components/user/CommandPalette.tsx
  • components/user/GlobalSettingsPanel.tsx
  • components/user/SettingsContent.tsx
  • components/user/SettingsTools.tsx
  • components/user/UserHelpDialog.tsx
  • components/user/UserPageEditor.tsx
  • components/user/UserPageHeader.tsx
  • components/user/UserPageLoading.tsx
  • components/user/VirtualizedCardGrid.tsx
  • components/user/bulk/BulkConfirmDialog.tsx
  • components/user/bulk/CopyUrlsPopover.tsx
  • components/user/bulk/DownloadPopover.tsx
  • components/user/bulk/DownloadStatusAlerts.tsx
  • components/user/bulk/SelectionCounter.tsx
  • components/user/editor/DraftRestoreNotice.tsx
  • components/user/editor/EditorNotices.tsx
  • components/user/editor/EditorTour.tsx
  • components/user/editor/EditorTourStyles.ts
  • components/user/editor/ReorderModeHint.tsx
  • components/user/editor/SaveConflictNotice.tsx
  • components/user/help/user-help-topics.ts
  • components/user/hooks/useCardFiltering.ts
  • components/user/hooks/useNewUserSetup.ts
  • components/user/hooks/useUserDataLoader.ts
  • components/user/share-utils.ts
  • components/user/tile/CardPreview.tsx
  • components/user/tile/CardTileHeader.tsx
  • components/user/tile/CopyPopover.tsx
  • components/user/tile/DisabledState.tsx
  • components/user/tile/DownloadPopover.tsx
  • components/user/tile/VariantSelector.tsx
  • components/user/tile/buildPreviewUrl.ts
  • components/user/tile/preview-cache.ts
  • components/user/tile/useCachedCardPreview.ts
  • dependency-license-policy.json
  • docs/API.md
  • docs/ARCHITECTURE.md
  • docs/CARD_REFERENCE.md
  • docs/DEVELOPMENT.md
  • docs/PRIVACY.md
  • docs/README.md
  • docs/SECURITY.md
  • docs/diagrams/analytics-consent-flow.drawio
  • docs/diagrams/card-generation-pipeline.drawio
  • docs/diagrams/card-type-taxonomy.drawio
  • docs/diagrams/component-hierarchy.drawio
  • docs/diagrams/data-lifecycle.drawio
  • docs/diagrams/development-workflow.drawio
  • docs/diagrams/error-handling-flow.drawio
  • docs/diagrams/public-api-surface.drawio
  • docs/diagrams/redis-persistence.drawio
  • docs/diagrams/runtime-architecture.drawio
  • docs/diagrams/security-request-flow.drawio
  • docs/diagrams/user-page-editor-flow.drawio
  • eslint.config.mjs
  • hooks/useAppRouterErrorBoundaryReporting.ts
  • hooks/useCardAutoSave.ts
  • hooks/useCopyFeedback.ts
  • hooks/useDownload.ts
  • hooks/useGoogleAnalytics.ts
  • hooks/useIsMobile.ts
  • hooks/useMotionPreferences.ts
  • hooks/usePageSEO.ts
  • hooks/usePreviewColorPreset.ts
  • hooks/useUserPageDraftBackup.ts
  • knip.json
  • lib/animations.ts
  • lib/api-utils.ts
  • lib/api/cards.ts
  • lib/api/client-fetch.ts
  • lib/api/clients.ts
  • lib/api/config.ts
  • lib/api/cors.ts
  • lib/api/errors.ts
  • lib/api/local-runtime.ts
  • lib/api/logging.ts
  • lib/api/primitives.ts
  • lib/api/production-env.ts
  • lib/api/protected-write-grants.ts
  • lib/api/rate-limit.ts
  • lib/api/request-body.ts
  • lib/api/request-context.ts
  • lib/api/request-guards.ts
  • lib/api/request-proof.ts
  • lib/api/telemetry.ts
  • lib/api/upstream.ts
  • lib/api/validation.ts
  • lib/batch-export.ts
  • lib/card-data/config.ts
  • lib/card-data/fetching.ts
  • lib/card-data/index.ts
  • lib/card-data/processing.ts
  • lib/card-data/validation.ts
  • lib/card-generator.ts
  • lib/card-groups.ts
  • lib/card-info-tooltips.ts
  • lib/card-preview-dimensions.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the legacy generator and settings with a new User Page Editor and editor store, adds per-card/global settings UI and tooling (tiles, previews, bulk actions, downloads, virtualized/reorderable grids), introduces lightbox/gallery and math rendering, Radix UI primitives, color/gradient utilities, expanded API validation/merge logic, and removes activityHeatmap/recentActivityFeed assets plus many generator/settings pages and tests.

Changes

Cohort / File(s) Summary
Editor store & core editor UI
lib/stores/user-page-editor.ts, lib/stores/index.ts, components/user/**, components/user/editor/**, components/user/hooks/**, hooks/useCardAutoSave.ts
New Zustand editor store, selectors and types; UserPageEditor UI, header, notices, tour, draft restore, help dialog, onboarding hooks, and autosave/save helpers.
Per-card tiles, previews & tooling
New: components/user/CardTile.tsx, components/user/ColorPreviewCard.tsx, components/user/tile/*, components/user/VirtualizedCardGrid.tsx, components/user/CardCategorySection.tsx
New CardTile ecosystem: CardPreview, VariantSelector, CardTileHeader, DisabledState, buildPreviewUrl, virtualized grid, category section with DnD and reorder support.
Bulk actions & downloads
New: components/user/BulkActionsToolbar.tsx, components/user/bulk/*, hooks/useDownload.ts, hooks/useCopyFeedback.ts
Bulk actions toolbar, copy/download popovers, download status alerts, batch export orchestration, and download/copy hooks with error reporting.
Settings & color UI
New: components/user/SettingsContent.tsx, components/user/CardSettingsPanel.tsx, components/user/GlobalSettingsPanel.tsx
Unified SettingsContent (Colors / Border / Advanced), CardSettingsPanel (global & per-card modes) and GlobalSettingsPanel with live color preview.
API, validation & persistence
app/api/store-cards/route.ts, lib/api-utils.ts, lib/api/cards.ts, openapi.yaml, hooks/useCardAutoSave.ts
Expanded store-cards POST flow: payload parsing/validation (discriminated result), sanitization, merging global/per-card settings, optimistic concurrency, clamping, strict color validation; openapi adds disabled to StoredCardConfig; fetch/save helpers added.
Generator & settings removals
components/StatCardGenerator.tsx, components/stat-card-generator/*, hooks/useStatCardSubmit.ts, components/settings/*, app/settings/page.tsx, app/license/page.tsx, lib/stores/card-settings.ts
Removes legacy stat-card generator, generator context/hooks/components, older settings UI modules and the previous card-settings store; deletes settings and license pages and many generator-related components.
SVG templates & card types
lib/svg-templates/activity-stats/*, lib/svg-templates/common/*, lib/card-generator.ts, lib/card-data/validation.ts, lib/server/user-data.ts, lib/card-groups.ts, components/stat-card-generator/constants.ts
Removes activityHeatmap and recentActivityFeed templates and related utilities; adds studioCollaboration, renames/normalizes many card labels, and enforces profileOverview variation defaults.
Examples gallery & lightbox
app/examples/page.tsx, components/examples/*, components/examples/CardLightboxModal.tsx, components/examples/index.ts
Refactors examples page to router navigation, adds CardLightboxModal, SearchFilterBar, CategoryNavigation, lightbox-driven ExampleCard flow, updates card catalog metadata.
Math, UI primitives & styles
components/MathTooltipContent.tsx, components/ui/* (Dialog, AlertDialog, Tabs, Select), components/ui/Toaster.tsx, app/globals.css, package.json
Adds KaTeX + DOMPurify math tooltip, Radix-based Dialog/AlertDialog/Tabs wrappers, styled Select, Toaster, scrollbar CSS, and new package dependencies.
Examples & hero changes
components/examples/ExamplesHeroSection.tsx, components/examples/CategorySection.tsx, components/examples/ExampleCard.tsx, components/examples/CTASection.tsx
Reworks hero and category UI for search and category filtering, replaces generator CTA with onStartCreating, integrates lightbox triggers and filtering props.
Tests & CI
tests/e2e/*, tests/unit/*, bunfig.toml, tests/e2e/fixtures/*
Removes many generator/settings E2E suites, updates E2E flows to new /search navigation and fixtures, and adds/expands unit tests for store-cards and card color handling.
Utilities & color helpers
lib/colorUtils.ts, lib/utils.ts, lib/card-data/config.ts, lib/card-groups.ts
New color utilities (hexToRgba, gradientToCss, normalizeForCompare), enhanced color validation/normalization, merging global/per-card settings into configs, update URL param mapping and favorites-grid clamping, improved batch-export failure reporting.
Providers & app shell
app/providers.tsx, components/PageShell.tsx, components/GridPattern.tsx, components/AppSidebar.tsx, next.config.ts
Add TooltipProvider and Toaster to providers, motion variant refactor, background fixed positioning, sidebar nav updated (remove Settings/License, add Search User), and new AniList remote image hosts in next config.
New UI primitives & editor UX
components/ui/AlertDialog.tsx, components/ui/Toaster.tsx, components/user/editor/*, components/user/CommandPalette.tsx
New AlertDialog wrapper, Toaster, editor notices, tour, draft restore, bulk live region, command palette, reorder hint, and other editor UX components.
Examples exports & types
components/examples/index.ts, components/examples/types.ts
New exports for CardLightboxModal, CategoryNavigation, SearchFilterBar and LightboxCardData type; adds CategoryInfo type.
Misc & docs
README.md, .gitignore, package.json
README updates, new dependencies, and extra .gitignore patterns.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Editor as Browser (Editor UI)
  participant API as /api/store-cards
  participant DB as Persistence
  participant AniList as External AniList

  Note over User,Editor: User edits cards or triggers new-user setup
  User->>Editor: modify cards / toggle settings / request save or setup
  Editor->>Editor: debounce & build payload (useCardAutoSave)
  Editor->>API: POST /api/store-cards { cards, globalSettings, ifMatch }
  API->>API: validateCardData() → sanitize, merge with existing, compute mergedGlobalSettings
  API->>DB: persist CardsRecord (cards + mergedGlobalSettings)
  DB-->>API: ack + updatedAt
  API-->>Editor: 200 OK + stored snapshot
  Editor->>Editor: hydrate editor store (useUserPageEditor) and clear dirty state
  alt New user onboarding
    Editor->>AniList: fetch user id & stats (useNewUserSetup)
    AniList-->>Editor: user id & stats
    Editor->>API: POST create user & initial cards
    API->>DB: persist setup snapshot
    DB-->>API: ack
    API-->>Editor: setup success
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

"🐇 I hopped through code with nimble feet,
Old generators cleared to make room neat.
Editor blooms, lightbox sings, colors play,
Bulk tools hum — I nibble carrots, yay!
Tidy burrow, ready for the day."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch user-flow

@RLAlpha49
Copy link
Copy Markdown
Owner Author

@gooroo-dev review

@gooroo-dev
Copy link
Copy Markdown

gooroo-dev Bot commented Dec 25, 2025

Please double check the following review of the pull request:

🐞Mistake 🤪Typo 🚨Security 🚀Performance 💪Best Practices 📖Readability ❓Others
1 0 0 2 3 3 1

Changes in the diff

  • 🛠️ Updated AGENTS.md with refined workflow and subagent usage instructions.
  • 🛠️ Refactored app/api/store-cards/route.ts to improve card config merging, support disabled cards, and handle global settings better.
  • 🛠️ Modified app/examples/page.tsx to replace stat card generator modal with navigation to /search.
  • ➖ Removed app/license/page.tsx and app/settings/page.tsx.
  • 🛠️ Updated app/page.tsx to remove stat card generator modal and navigate to /search on get started.
  • 🛠️ Updated app/user/page.tsx to use UserPageEditor instead of UserPageClient and sanitize username.
  • ➕ Added components/MathTooltipContent.tsx for rendering LaTeX math in tooltips.
  • ➖ Removed components/StatCardGenerator and related components.
  • 🛠️ Updated components/examples components to replace generator open handlers with navigation to /search.
  • 🛠️ Updated components/search/SearchForm.tsx to improve input validation, add loading state, and schedule navigation after paint.
  • 🛠️ Updated components/search/SearchHeroSection.tsx with improved copy.
  • ➖ Removed components/settings components.
  • ➖ Removed components/stat-card-generator components.
  • ➕ Added components/ui/AlertDialog.tsx for confirmation dialogs.
  • 🛠️ Updated components/ui/Dialog.tsx to add props for hiding close button.
  • 🛠️ Updated components/ui/Select.tsx for improved styles and accessibility.
  • ➕ Added components/ui/Tabs.tsx for tab UI.
  • ➕ Added components/user/BulkActionsToolbar.tsx for bulk card actions.
  • ➕ Added components/user/CardCategorySection.tsx for collapsible card groups.
  • ➖ Removed components/user/CardList.tsx and UserCard.tsx.
  • ➕ Added components/user/CardSettingsDialog.tsx and CardSettingsPanel.tsx for card settings UI.
  • ➕ Added components/user/CardTile.tsx for individual card UI with preview and controls.
  • ➕ Added components/user/ColorPreviewCard.tsx for color preview.
  • ➕ Added components/user/GlobalSettingsPanel.tsx for global card settings.
  • ➕ Added components/user/SettingsContent.tsx shared by global and card settings.
  • ➕ Added components/user/UserPageEditor.tsx as main user page editor component.
  • ➕ Added components/user/UserPageHeader.tsx for user info header.
  • ➕ Added hooks/useCardAutoSave.ts for debounced auto-save of card settings.
  • ➖ Removed hooks/useStatCardSubmit.ts.
  • 🛠️ Updated lib/api-utils.ts to allow disabled cards and validate accordingly.
  • 🛠️ Updated lib/card-data/config.ts to improve color param detection and merging global settings.
  • 🛠️ Updated lib/card-groups.ts to improve color serialization and URL param mapping.
  • ➕ Added lib/card-info-tooltips.ts for card info tooltips with LaTeX support.
  • ➖ Removed lib/stores/card-settings.ts.
  • 🛠️ Updated lib/stores/index.ts to remove card-settings export and add user-page-editor export.
  • ➕ Added lib/stores/user-page-editor.ts as new store for user page editor state.
  • 🛠️ Updated lib/stores/user-preferences.ts to remove defaultUsername and resetUserPreferences.

Identified Issues

ID Type Details Severity Confidence
1 🐞Mistake In app/api/store-cards/route.ts, disabled cards' disabled field is accepted but type validation missing. 🔴High 🔴High
2 🚀Performance In app/api/store-cards/route.ts, merging incoming cards and ensuring all supported cards present improves data integrity and performance. 🟠Medium 🟢High
3 🚀Performance In components/search/SearchForm.tsx, navigation is scheduled after paint to allow loading overlay to render, improving UX. 🟡Low 🟢High
4 💪Best Practices In app/user/page.tsx, username is trimmed before use to avoid errors and improve consistency. 🟠Medium 🟢High
5 💪Best Practices In lib/card-data/config.ts, color param detection improved to allow skipping DB lookup when full colors provided even with custom preset. 🟠Medium 🟢High
6 💪Best Practices In lib/api-utils.ts, validation allows disabled cards to omit color fields, improving flexibility. 🟠Medium 🟢High
7 📖Readability Many components and tests are removed to simplify codebase and focus on new user flow. 🟠Medium 🟢High
8 📖Readability Added detailed comments and typings in new components and stores improve maintainability. 🟠Medium 🟢High
9 ❓Others Some legacy code and tests removed, may require migration or replacement in documentation. 🟡Low 🟢High

Issue 1: Disabled field type validation missing in store-cards API

Explanation:
In app/api/store-cards/route.ts, the API accepts a disabled field on cards but does not validate its type strictly (should be boolean). This can cause invalid data to be stored or unexpected behavior.

Fix:

Add validation to reject non-boolean disabled values in the POST handler.

// In POST handler, after parsing body.cards:
for (const card of incomingCards as StoredCardConfig[]) {
  if ("disabled" in card && typeof card.disabled !== "boolean") {
    return new NextResponse(
      JSON.stringify({ error: "Invalid 'disabled' field type" }),
      { status: 400 },
    );
  }
}

Explanation of fix:
This ensures only boolean values are accepted for disabled, preventing invalid data storage.

Missing Tests

  • Test that disabled field rejects non-boolean values in store-cards API.
  • Test that cards with disabled: true preserve previous settings.
  • Test that global borderColor is omitted when borderEnabled is false.
  • Test that URL color params override DB colors when custom preset is used but DB config is required.
  • Test that navigation from examples and home page CTAs goes to /search.
  • Test that username trimming works in user page metadata and search form.

Summary

The diff introduces a new user flow with a revamped user page editor, removes legacy generator components and settings page, improves API handling of card configs including disabled cards and global settings, and adds new UI components for card management and bulk actions. It also improves UX in search and example pages by navigating to /search instead of opening modals. The code is cleaner with better typing and comments. A minor validation issue with the disabled field type in the store-cards API should be fixed.

Summon me to re-review when updated! Yours, Gooroo.dev
Feel free to react or reply to this review!

@RLAlpha49
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/stat-card-generator/ColorPickerGroup.tsx (2)

315-327: Inconsistent use of stops vs localStops for disabled state.

The "Add Stop" button's disabled prop (line 320) uses stops.length instead of localStops.length. Since the component now renders and operates on localStops, this should be consistent.

🔎 Proposed fix
           <Button
             type="button"
             variant="outline"
             size="sm"
             onClick={addStop}
-            disabled={stops.length >= 5}
+            disabled={localStops.length >= 5}
             aria-label={`Add gradient color stop ${stops.length + 1} of 5`}

410-421: Same inconsistency for remove button disabled state.

Line 415 uses stops.length for the remove button's disabled check, but should use localStops.length for consistency with the refactored state management.

🔎 Proposed fix
               <Button
                 type="button"
                 variant="ghost"
                 size="sm"
                 onClick={() => removeStop(index)}
-                disabled={stops.length <= 2}
+                disabled={localStops.length <= 2}
                 aria-label={`Remove gradient stop ${index + 1}`}
🧹 Nitpick comments (14)
components/search/SearchForm.tsx (1)

73-105: Consider handling navigation edge cases.

The loading state is set to true but never reset to false. While this works for successful navigation (component unmounts), if router.push fails silently or the user navigates back, the form could remain in a loading state.

This is a minor edge case since Next.js client-side navigation is generally reliable, but you could add a safety mechanism if needed.

🔎 Optional: Reset loading state after navigation attempt
      // Ensure the loading overlay has a chance to render before navigation.
      scheduleAfterPaint(() => {
        router.push(`/user?${params.toString()}`);
+       // Reset loading state after a brief delay to handle edge cases
+       // where navigation might not unmount this component
+       setTimeout(() => {
+         setLoading(false);
+         onLoadingChange?.(false);
+       }, 2000);
      });
components/user/UserPageHeader.tsx (1)

174-195: Consider simplifying the nested conditional className logic.

The nested conditionals for save status styling are correct but complex. Consider extracting the logic into a helper function for better readability.

🔎 Proposed refactor
+function getSaveStatusClass(saveState: SaveState): string {
+  if (saveState.isSaving) {
+    return "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400";
+  }
+  if (saveState.saveError) {
+    return "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400";
+  }
+  if (saveState.isDirty) {
+    return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400";
+  }
+  if (saveState.lastSavedAt) {
+    return "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400";
+  }
+  return "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400";
+}

 <div
   className={cn(
     "flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all",
-    saveState.isSaving &&
-      "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
-    saveState.saveError &&
-      "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400",
-    !saveState.isSaving &&
-      !saveState.saveError &&
-      saveState.isDirty &&
-      "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400",
-    !saveState.isSaving &&
-      !saveState.saveError &&
-      !saveState.isDirty &&
-      saveState.lastSavedAt &&
-      "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400",
-    !saveState.isSaving &&
-      !saveState.saveError &&
-      !saveState.isDirty &&
-      !saveState.lastSavedAt &&
-      "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400",
+    getSaveStatusClass(saveState),
   )}
 >
components/user/ColorPreviewCard.tsx (1)

53-75: Hex parsing assumes valid 6-character format.

The hex-to-rgba conversion assumes a 6-character hex code. This could produce incorrect results for shorthand hex (e.g., #FFF) or invalid values.

🔎 Proposed defensive fix
       if (opacity < 1) {
         // Convert hex to rgba
-        const hex = stop.color.replace("#", "");
+        let hex = stop.color.replace("#", "");
+        // Expand shorthand hex (e.g., "FFF" -> "FFFFFF")
+        if (hex.length === 3) {
+          hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
+        }
         const r = Number.parseInt(hex.substring(0, 2), 16);
         const g = Number.parseInt(hex.substring(2, 4), 16);
         const b = Number.parseInt(hex.substring(4, 6), 16);
components/user/GlobalSettingsPanel.tsx (1)

50-66: Consider extracting default values to a shared constant.

The reset handler uses hardcoded default values. If defaults change elsewhere (e.g., in the store initialization), these could drift out of sync.

🔎 Suggested approach
// In a shared constants file or exported from the store
export const DEFAULT_GLOBAL_SETTINGS = {
  colorPreset: "default",
  borderEnabled: false,
  borderColor: "#e4e2e2",
  borderRadius: 5,
  advancedSettings: {
    useStatusColors: true,
    showPiePercentages: true,
    showFavorites: true,
    gridCols: 3,
    gridRows: 3,
  },
} as const;

// Then use in the handler:
const handleResetToDefaults = useCallback(() => {
  setGlobalColorPreset(DEFAULT_GLOBAL_SETTINGS.colorPreset);
  // ... etc
}, [...]);
components/user/CardSettingsDialog.tsx (2)

106-107: localBorderEnabled may become stale if external state changes.

The useState initializer only runs once when the component mounts. If hasBorder changes while the dialog is open (e.g., from another source), localBorderEnabled won't update to reflect the new state.

Consider synchronizing with an effect or deriving directly from the config:

🔎 Option: Sync with effect
   const [localBorderEnabled, setLocalBorderEnabled] = useState(hasBorder);

+  // Sync local state when external border state changes
+  useEffect(() => {
+    setLocalBorderEnabled(hasBorder);
+  }, [hasBorder]);

175-182: Consider memoizing advancedSettings object.

This object is recreated on every render. While the impact is minimal, memoizing it would prevent unnecessary re-renders of child components that depend on referential equality.

🔎 Proposed fix
-  // Build advanced settings for SettingsContent
-  const advancedSettings = {
-    useStatusColors: config.advancedSettings.useStatusColors ?? false,
-    showPiePercentages: config.advancedSettings.showPiePercentages ?? false,
-    showFavorites: config.advancedSettings.showFavorites ?? false,
-    gridCols: config.advancedSettings.gridCols ?? 3,
-    gridRows: config.advancedSettings.gridRows ?? 3,
-  };
+  // Build advanced settings for SettingsContent
+  const advancedSettings = useMemo(
+    () => ({
+      useStatusColors: config.advancedSettings.useStatusColors ?? false,
+      showPiePercentages: config.advancedSettings.showPiePercentages ?? false,
+      showFavorites: config.advancedSettings.showFavorites ?? false,
+      gridCols: config.advancedSettings.gridCols ?? 3,
+      gridRows: config.advancedSettings.gridRows ?? 3,
+    }),
+    [config.advancedSettings],
+  );
components/user/BulkActionsToolbar.tsx (2)

173-180: Consider providing user feedback on clipboard errors.

When the clipboard operation fails (e.g., due to permissions or secure context requirements), the error is only logged to console. Users won't know why the copy didn't work.

🔎 Proposed enhancement
       try {
         await navigator.clipboard.writeText(urls.join("\n"));
         setCopiedFormat(format);
         setTimeout(() => setCopiedFormat(null), 2000);
       } catch (err) {
         console.error("Failed to copy to clipboard:", err);
+        // Consider adding a toast notification or visual feedback here
+        // e.g., toast.error("Failed to copy to clipboard")
       }

213-219: Consider user feedback for download failures.

Similar to the clipboard error, download failures only log to console. Users may not understand why their download didn't complete, especially if partial failures occur (line 209-213 handles this with a warning but no UI feedback).

components/user/SettingsContent.tsx (1)

250-304: Consider wrapping border settings animation in AnimatePresence for proper exit animation.

The motion.div uses exit prop but isn't wrapped in AnimatePresence, so exit animations won't trigger when borderEnabled becomes false.

🔎 Proposed fix
+import { AnimatePresence } from "framer-motion";

          {/* Border Settings - Only shown when border is enabled */}
-          {borderEnabled && (
+          <AnimatePresence>
+          {borderEnabled && (
             <motion.div
               initial={{ opacity: 0, height: 0 }}
               animate={{ opacity: 1, height: "auto" }}
               exit={{ opacity: 0, height: 0 }}
               className="space-y-4"
             >
               {/* ... content ... */}
             </motion.div>
-          )}
+          )}
+          </AnimatePresence>
tests/e2e/user/user.spec.ts (1)

200-205: Consider using a more specific selector for preview images.

Using page.locator("main").locator("img") is broad and could match unrelated images (avatars, icons). Consider using a more specific selector like [data-testid="card-preview"] if available.

components/user/CardTile.tsx (1)

356-370: Consider adding loading/error states for the preview image.

The Image component doesn't have an onError handler. If the preview URL fails to load, users see no feedback.

lib/stores/user-page-editor.ts (2)

238-241: Consider defensive handling for missing presets.

If colorPresets[presetName] doesn't exist and colorPresets.default also doesn't exist, this would return undefined. While unlikely in practice, adding explicit fallback would be safer.

🔎 Proposed defensive fallback
 function getPresetColors(presetName: string): ColorValue[] {
   const preset = colorPresets[presetName];
-  return preset?.colors || colorPresets.default.colors;
+  return preset?.colors || colorPresets.default?.colors || ["#fe428e", "#141321", "#a9fef7", "#fe428e"];
 }

560-571: Note: DEFAULT_GLOBAL_ADVANCED_SETTINGS is defined after its usage.

The constant is used in processServerCards at line 466 but defined at line 565. While JavaScript hoisting handles this for const at module level, placing the constant definition before its usage would improve readability.

components/user/UserPageEditor.tsx (1)

913-927: Consider debouncing search-triggered group expansion.

When normalizedQuery changes rapidly (user typing), this effect expands groups on each change. While not breaking, it could cause minor UI churn.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf0af7 and 75168a6.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (81)
  • AGENTS.md
  • app/api/store-cards/route.ts
  • app/examples/page.tsx
  • app/globals.css
  • app/license/page.tsx
  • app/page.tsx
  • app/settings/page.tsx
  • app/sitemap.xml/route.ts
  • app/user/page.tsx
  • components/AppSidebar.tsx
  • components/MathTooltipContent.tsx
  • components/StatCardGenerator.tsx
  • components/examples/CTASection.tsx
  • components/examples/CategorySection.tsx
  • components/examples/ExampleCard.tsx
  • components/search/SearchForm.tsx
  • components/search/SearchHeroSection.tsx
  • components/settings/DefaultCardSettings.tsx
  • components/settings/DefaultUsername.tsx
  • components/settings/ResetSettings.tsx
  • components/settings/SidebarBehavior.tsx
  • components/settings/ThemePreferences.tsx
  • components/stat-card-generator/AdvancedOptions.tsx
  • components/stat-card-generator/CardPreviewSkeleton.tsx
  • components/stat-card-generator/ColorPickerGroup.tsx
  • components/stat-card-generator/ColorPresetManager.tsx
  • components/stat-card-generator/ColorPresetSelector.tsx
  • components/stat-card-generator/GeneratorContext.tsx
  • components/stat-card-generator/LivePreview.tsx
  • components/stat-card-generator/LivePreviewSkeleton.tsx
  • components/stat-card-generator/StatCardPreview.tsx
  • components/stat-card-generator/StatCardTypeSelection.tsx
  • components/stat-card-generator/UpdateNotice.tsx
  • components/stat-card-generator/UserDetailsForm.tsx
  • components/stat-card-generator/UserFormSkeleton.tsx
  • components/stat-card-generator/WizardNavigation.tsx
  • components/stat-card-generator/constants.ts
  • components/ui/AlertDialog.tsx
  • components/ui/Dialog.tsx
  • components/ui/Select.tsx
  • components/ui/Tabs.tsx
  • components/user/BulkActionsToolbar.tsx
  • components/user/CardCategorySection.tsx
  • components/user/CardList.tsx
  • components/user/CardSettingsDialog.tsx
  • components/user/CardSettingsPanel.tsx
  • components/user/CardTile.tsx
  • components/user/ColorPreviewCard.tsx
  • components/user/GlobalSettingsPanel.tsx
  • components/user/SettingsContent.tsx
  • components/user/UserCard.tsx
  • components/user/UserPageClient.tsx
  • components/user/UserPageEditor.tsx
  • components/user/UserPageHeader.tsx
  • hooks/useCardAutoSave.ts
  • hooks/useStatCardSubmit.ts
  • lib/api-utils.ts
  • lib/card-data/config.ts
  • lib/card-groups.ts
  • lib/card-info-tooltips.ts
  • lib/seo.ts
  • lib/stores/card-settings.ts
  • lib/stores/index.ts
  • lib/stores/user-page-editor.ts
  • lib/stores/user-preferences.ts
  • lib/types/records.ts
  • lib/utils.ts
  • package.json
  • tests/e2e/examples/examples.spec.ts
  • tests/e2e/fixtures/index.ts
  • tests/e2e/fixtures/test-utils.ts
  • tests/e2e/generator/batch-generation.spec.ts
  • tests/e2e/generator/card-generation.spec.ts
  • tests/e2e/generator/customization.spec.ts
  • tests/e2e/generator/error-scenarios.spec.ts
  • tests/e2e/generator/wizard-navigation.spec.ts
  • tests/e2e/home/home.spec.ts
  • tests/e2e/settings/settings.spec.ts
  • tests/e2e/user/user.spec.ts
  • tests/unit/api/card/card.test.ts
  • tests/unit/api/store-cards/store-cards.test.ts
💤 Files with no reviewable changes (34)
  • app/sitemap.xml/route.ts
  • components/stat-card-generator/UserFormSkeleton.tsx
  • lib/seo.ts
  • components/settings/DefaultUsername.tsx
  • components/settings/SidebarBehavior.tsx
  • components/settings/DefaultCardSettings.tsx
  • tests/e2e/generator/batch-generation.spec.ts
  • components/stat-card-generator/ColorPresetManager.tsx
  • hooks/useStatCardSubmit.ts
  • components/stat-card-generator/UserDetailsForm.tsx
  • components/settings/ThemePreferences.tsx
  • components/user/UserCard.tsx
  • app/license/page.tsx
  • components/stat-card-generator/WizardNavigation.tsx
  • components/StatCardGenerator.tsx
  • lib/stores/card-settings.ts
  • app/settings/page.tsx
  • tests/e2e/generator/error-scenarios.spec.ts
  • tests/e2e/generator/card-generation.spec.ts
  • components/stat-card-generator/StatCardTypeSelection.tsx
  • components/stat-card-generator/AdvancedOptions.tsx
  • lib/stores/user-preferences.ts
  • components/user/CardList.tsx
  • components/stat-card-generator/GeneratorContext.tsx
  • components/settings/ResetSettings.tsx
  • tests/e2e/generator/wizard-navigation.spec.ts
  • tests/e2e/settings/settings.spec.ts
  • components/stat-card-generator/LivePreview.tsx
  • components/stat-card-generator/LivePreviewSkeleton.tsx
  • components/stat-card-generator/UpdateNotice.tsx
  • components/stat-card-generator/StatCardPreview.tsx
  • tests/e2e/generator/customization.spec.ts
  • components/stat-card-generator/CardPreviewSkeleton.tsx
  • components/user/UserPageClient.tsx
🧰 Additional context used
🧬 Code graph analysis (26)
components/examples/CTASection.tsx (2)
components/CTASection.tsx (1)
  • CTASection (29-136)
components/examples/index.ts (1)
  • CTASection (4-4)
tests/e2e/home/home.spec.ts (1)
tests/e2e/fixtures/test-utils.ts (1)
  • test (26-143)
components/ui/Tabs.tsx (1)
lib/utils.ts (1)
  • cn (178-180)
components/examples/ExampleCard.tsx (1)
components/examples/index.ts (1)
  • ExampleCard (3-3)
components/user/GlobalSettingsPanel.tsx (3)
lib/types/card.ts (1)
  • ColorValue (41-41)
components/ui/Dialog.tsx (3)
  • DialogHeader (191-191)
  • DialogTitle (193-193)
  • DialogDescription (194-194)
components/user/CardSettingsPanel.tsx (1)
  • CardSettingsPanel (64-182)
components/ui/Dialog.tsx (1)
lib/utils.ts (1)
  • cn (178-180)
app/user/page.tsx (1)
components/user/UserPageEditor.tsx (1)
  • UserPageEditor (605-1543)
lib/card-data/config.ts (3)
lib/card-data/index.ts (1)
  • isCustomPreset (48-48)
lib/types/records.ts (2)
  • StoredCardConfig (445-462)
  • CardsRecord (482-487)
lib/utils.ts (1)
  • clampBorderRadius (870-877)
components/user/CardCategorySection.tsx (1)
lib/utils.ts (1)
  • cn (178-180)
tests/unit/api/card/card.test.ts (4)
tests/unit/__setup__.test.ts (1)
  • sharedRedisMockGet (15-15)
app/api/card/route.ts (1)
  • GET (535-660)
lib/svg-templates/media-stats/shared.ts (1)
  • mediaStatsTemplate (242-382)
lib/svg-templates/extra-anime-manga-stats/shared.ts (1)
  • extraAnimeMangaStatsTemplate (39-661)
components/ui/AlertDialog.tsx (2)
lib/utils.ts (1)
  • cn (178-180)
components/ui/Button.tsx (1)
  • buttonVariants (73-73)
components/user/CardSettingsPanel.tsx (4)
components/user/SettingsContent.tsx (1)
  • SettingsContent (108-460)
lib/utils.ts (1)
  • cn (178-180)
components/ui/Button.tsx (1)
  • Button (73-73)
components/ui/Switch.tsx (1)
  • Switch (34-34)
hooks/useCardAutoSave.ts (4)
lib/types/card.ts (1)
  • ColorValue (41-41)
lib/stores/user-page-editor.ts (1)
  • ServerCardData (190-207)
lib/stores/index.ts (1)
  • ServerCardData (31-31)
lib/utils.ts (2)
  • parseResponsePayload (631-644)
  • getResponseErrorMessage (651-665)
components/ui/Select.tsx (1)
lib/utils.ts (1)
  • cn (178-180)
lib/types/records.ts (1)
lib/types/card.ts (1)
  • ColorValue (41-41)
app/api/store-cards/route.ts (3)
lib/card-data/validation.ts (1)
  • displayNames (79-134)
lib/types/records.ts (3)
  • StoredCardConfig (445-462)
  • GlobalCardSettings (465-479)
  • CardsRecord (482-487)
lib/utils.ts (2)
  • safeParse (769-802)
  • clampBorderRadius (870-877)
app/examples/page.tsx (2)
components/examples/CTASection.tsx (1)
  • CTASection (11-49)
components/CTASection.tsx (1)
  • CTASection (29-136)
components/search/SearchForm.tsx (1)
lib/utils/google-analytics.ts (3)
  • safeTrack (95-101)
  • trackFormSubmission (222-228)
  • trackNavigation (181-192)
components/user/BulkActionsToolbar.tsx (4)
lib/stores/user-page-editor.ts (1)
  • useUserPageEditor (599-1110)
hooks/useIsMobile.ts (1)
  • useIsMobile (12-31)
lib/card-groups.ts (2)
  • mapStoredConfigToCardUrlParams (458-546)
  • buildCardUrlWithParams (599-647)
lib/utils.ts (3)
  • ConversionFormat (152-152)
  • BatchExportCard (155-159)
  • batchConvertAndZip (1180-1221)
tests/e2e/user/user.spec.ts (2)
tests/e2e/fixtures/test-utils.ts (2)
  • expect (182-182)
  • test (26-143)
tests/e2e/fixtures/mock-data.ts (1)
  • mockCardsRecord (254-277)
tests/unit/api/store-cards/store-cards.test.ts (3)
tests/unit/__setup__.test.ts (3)
  • sharedRatelimitMockLimit (65-67)
  • sharedRedisMockSet (16-16)
  • sharedRedisMockGet (15-15)
app/api/store-cards/route.ts (1)
  • POST (357-496)
lib/card-data/validation.ts (1)
  • displayNames (79-134)
components/user/UserPageEditor.tsx (7)
lib/anilist/queries.ts (2)
  • USER_ID_QUERY (2-8)
  • USER_STATS_QUERY (529-536)
lib/utils.ts (2)
  • parseResponsePayload (631-644)
  • getResponseErrorMessage (651-665)
lib/error-messages.ts (1)
  • getErrorDetails (316-359)
lib/error-tracking.ts (1)
  • trackUserActionError (73-92)
lib/types/records.ts (1)
  • ReconstructedUserRecord (432-442)
lib/stores/user-page-editor.ts (3)
  • ServerCardData (190-207)
  • ServerGlobalSettings (213-227)
  • useUserPageEditor (599-1110)
hooks/useCardAutoSave.ts (1)
  • useCardAutoSave (174-260)
components/stat-card-generator/ColorPickerGroup.tsx (1)
lib/types/card.ts (1)
  • GradientStop (5-14)
components/user/UserPageHeader.tsx (2)
components/PageShell.tsx (2)
  • containerVariants (13-19)
  • itemVariants (21-24)
lib/utils.ts (1)
  • cn (178-180)
lib/card-groups.ts (2)
lib/utils.ts (1)
  • buildApiUrl (57-62)
lib/types/card.ts (1)
  • ColorValue (41-41)
lib/utils.ts (1)
lib/svg-templates/common/color-utils.ts (1)
  • isValidHexColor (4-4)
🪛 ast-grep (0.40.3)
components/MathTooltipContent.tsx

[warning] 35-35: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

components/user/ColorPreviewCard.tsx

[warning] 153-153: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
components/MathTooltipContent.tsx

[error] 36-36: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

components/user/ColorPreviewCard.tsx

[error] 154-154: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 LanguageTool
AGENTS.md

[style] ~20-~20: Try moving the adverb to make the sentence clearer.
Context: ... task — DO NOT create one Plan that attempts to fully plan multiple unrelated tasks at once. Each Plan must be focused and ...

(SPLIT_INFINITIVE)

🔇 Additional comments (103)
components/stat-card-generator/ColorPresetSelector.tsx (3)

18-18: LGTM: ChevronUp import added for toggle UI.

The import is correctly added to support the expand/collapse toggle functionality introduced later in the component.


404-404: LGTM: Consistent border radius on selection overlay.

The rounded-xl class ensures the selection overlay respects the parent button's rounded corners, preventing visual artifacts at the edges.


466-493: LGTM: Well-implemented toggle behavior for expand/collapse.

The toggle implementation correctly:

  • Uses hasMorePresets to conditionally render the button
  • Toggles isExpanded state bidirectionally
  • Provides clear visual feedback with appropriate chevron icons
  • Displays intuitive button text ("Show less" vs. "Show all X presets")

This is a solid UX improvement over the previous expand-only behavior, allowing users to collapse the list when needed.

components/search/SearchHeroSection.tsx (1)

67-69: LGTM!

The updated onboarding text clearly communicates the new user flow—directing users to enter a username/ID and noting automatic setup for first-time users. This aligns well with the PR's shift from modal-based generation to direct navigation.

tests/e2e/fixtures/index.ts (1)

1-6: LGTM!

Clean simplification of the fixture exports following the removal of the GeneratorPage Page Object. The updated comment accurately reflects the reduced scope.

lib/utils.ts (1)

472-511: LGTM with a minor observation on naming semantics.

The JSON-encoded gradient support is a good addition for handling stored/transported gradient definitions. The trimming and empty string checks are solid defensive improvements.

One minor semantic note: getColorInvalidReason returning "gradient JSON string is valid" (line 497) when the value is actually valid may be slightly confusing since the function name implies it returns reasons for invalid values. However, looking at the overall pattern, this seems intentional as a diagnostic message for edge cases where shared validation differs from individual checks. No action required.

components/AppSidebar.tsx (1)

33-40: LGTM!

The new "Search User" navigation item is well-structured with consistent styling patterns (gradient, bgLight, textColor). The /search route aligns with the PR's shift to route-based user discovery.

tests/e2e/fixtures/test-utils.ts (1)

1-21: LGTM!

Clean removal of the GeneratorPage Page Object and associated imports. The remaining mock fixtures (mockSuccessfulApi, mockRateLimitedApi, mockUserNotFoundApi, mockNetworkError) are well-organized and sufficient for testing the new route-based flow.

components/search/SearchForm.tsx (1)

25-33: Nice paint-scheduling pattern.

The scheduleAfterPaint helper with requestAnimationFrame and setTimeout fallback is a clean approach to ensure the loading overlay renders before navigation.

components/ui/Tabs.tsx (1)

1-55: LGTM!

Well-structured Radix UI Tabs wrapper following established patterns in the codebase. Good use of forwardRef for all subcomponents, proper displayName assignments, and comprehensive styling including dark mode and focus-visible states.

components/stat-card-generator/ColorPickerGroup.tsx (1)

238-244: Good local state pattern for controlled edits.

Using localStops to manage edits locally before propagating via onStopsChange is a clean pattern that avoids prop mutation and provides better control over state updates.

components/ui/Dialog.tsx (2)

52-64: LGTM! Clean API extension.

The new DialogContentProps interface provides a clear escape hatch for hiding or customizing the close button, with good documentation.


71-110: LGTM! Conditional close button rendering is correctly implemented.

The implementation properly:

  • Destructures the new props with sensible defaults
  • Conditionally renders the close button based on hideCloseButton
  • Applies custom styling via closeButtonClassName when provided
  • Maintains all existing functionality
AGENTS.md (1)

1-55: LGTM! Documentation restructure improves clarity.

The refactored workflow documentation is more actionable with:

  • Clear quick-start steps
  • Concrete subagent workflow example
  • Better organized sections for modes, libraries, and memory policy

The static analysis hint about the split infinitive ("to fully plan") is a minor style preference that doesn't impact clarity.

components/user/UserPageHeader.tsx (2)

64-71: LGTM! Time formatting logic is clear and correct.

The function properly handles null input and provides sensible time buckets (5s, 60s, 3600s) with appropriate formatting.


114-122: The unoptimized prop is necessary and should be retained. AniList avatar domains are not configured in next.config.ts—only internal domains (api.anicards.alpha49.com, anicards.alpha49.com, and localhost variants) are in remotePatterns. Without explicit configuration, Next.js image optimization would fail for AniList avatar URLs. The unoptimized prop is the correct approach for external images from unconfigured domains.

Likely an incorrect or invalid review comment.

components/ui/Select.tsx (3)

56-57: LGTM! Improved scroll button interactivity.

Changing from cursor-default to cursor-pointer and enhancing hover states provides better UX feedback for interactive scroll buttons.

Also applies to: 74-75


97-97: LGTM! Enhanced visual elevation for dropdown.

The updated styling with rounded-xl, backdrop blur, and theme-aware borders/shadows creates a more polished, modern dropdown appearance.


151-163: LGTM! Comprehensive item state styling.

The updated SelectItem styling properly handles:

  • Hover/focus states with appropriate backgrounds
  • Checked state with gradient accent
  • Disabled state with reduced opacity
  • Theme-aware colors for light/dark modes
hooks/useCardAutoSave.ts (5)

45-68: LGTM! Proper error handling in API call.

The function correctly:

  • Uses appropriate headers and body formatting
  • Parses response payload using provided utilities
  • Handles both network errors and HTTP errors
  • Returns consistent result shape

184-192: LGTM! Proper cleanup on unmount.

The effect correctly:

  • Sets mounted flag to true on mount
  • Returns cleanup function to clear timeouts and set mounted flag to false
  • Prevents state updates after unmount

229-250: LGTM! Correct debounce implementation.

The auto-save effect properly:

  • Guards against disabled state, missing userId, no changes, or active save
  • Clears existing timeout before setting new one
  • Returns cleanup function
  • Lists all relevant dependencies

94-110: No action needed. The client-side color logic in useCardAutoSave.ts is correctly aligned with the server-side handler in /api/store-cards/route.ts. Both use identical conditionals (!colorPreset || colorPreset === "custom") to determine whether to send individual colors, and the server properly handles undefined color fields by omitting them from storage. The design is intentional: when a preset is selected, the preset name is the source of truth and individual colors are not stored.


115-150: The merge logic is correct and properly aligned with server-side expectations. The code correctly handles custom vs global color preset selection (client sends effectivePreset which server mirrors in buildCardConfig), conditionally sends colors only when applicable (shouldSendColors logic), preserves per-card advanced settings through the merge process, and correctly handles the disabled flag. No changes needed.

lib/card-info-tooltips.ts (3)

9-9: Shannon entropy formula appears correct.

The normalized Shannon entropy formula H' = -Σ(p_i ln p_i) / ln k is the standard formula for diversity indices. The use of String.raw properly preserves backslashes in LaTeX notation.

Also applies to: 11-11, 13-13


77-89: LGTM! Simple and effective helper functions.

Both helpers provide clean APIs for querying tooltip availability and content.


7-14: KaTeX is properly configured and rendering LaTeX formulas correctly.

KaTeX (v0.16.27) is installed, with CSS imported globally in app/globals.css. The MathTooltipContent component handles rendering of both inline ($...$) and display ($$...$$) math delimiters using katex.renderToString() with appropriate error handling. This component is integrated with the tooltip system and receives content from CARD_INFO_TOOLTIPS.

tests/e2e/examples/examples.spec.ts (1)

38-47: LGTM! Test correctly validates route-based navigation.

The updated test properly:

  • Clicks the CTA button
  • Asserts navigation to /search route
  • Verifies the presence of the username input field

This aligns with the broader PR's shift from modal-based to route-based navigation.

app/user/page.tsx (2)

34-51: LGTM! Proper username normalization and encoding.

The changes correctly:

  • Trim whitespace from username for normalization
  • Use normalized value consistently in metadata
  • Apply encodeURIComponent for URL safety in canonical link

75-75: LGTM! Component replacement aligns with new user flow.

Replacing UserPageClient with UserPageEditor matches the PR's objective of implementing a new user flow with enhanced editing capabilities.

app/globals.css (2)

1-1: LGTM! KaTeX CSS imported for math rendering support.

The KaTeX import correctly supports the new MathTooltipContent component and aligns with the katex dependency added in package.json.


187-244: LGTM! Scrollbar utilities for consistent UI.

The .show-scrollbar and .overlay-scrollbar utilities provide consistent, theme-aware scrollbar styling. The approach using CSS variables ensures proper integration with the existing theming system.

lib/api-utils.ts (2)

618-652: LGTM! Disabled card validation correctly skips unnecessary fields.

The logic appropriately reduces validation requirements for disabled cards:

  • Only cardName is validated as a required string
  • Color validation (both preset and explicit color fields) is correctly skipped
  • Card type validation is still performed to ensure data integrity

This optimization aligns with the broader global/per-card settings architecture introduced in this PR.


674-674: LGTM! Disabled field correctly added to optional boolean fields.

The disabled field is appropriately validated as an optional boolean, ensuring type safety for per-card configuration.

app/examples/page.tsx (3)

4-4: LGTM! Navigation-based flow replaces modal-based generator.

The router-based navigation to /search is clean and aligns with the broader refactor away from modal-based workflows. The callback is properly memoized with useRouter dependency.

Also applies to: 489-493


629-629: LGTM! ErrorBoundary reset keys simplified.

The resetKeys correctly depends only on searchQuery now that the modal-based generator state has been removed.


657-657: LGTM! Prop name changes are consistent across components.

The prop rename from onOpenGenerator to onStartCreating accurately reflects the new navigation-based flow and is consistent with updates in CategorySection and CTASection components.

Also applies to: 667-667

tests/e2e/home/home.spec.ts (2)

4-14: LGTM! E2E test correctly validates navigation flow.

The test properly verifies:

  • Navigation to /search occurs after CTA click
  • The search input is visible on the target page

This aligns with the shift away from modal-based interactions to direct navigation.


38-47: LGTM! Improved viewport visibility assertion.

The updated viewport check is more robust by verifying both top and bottom positions, ensuring the showcase is actually visible in the viewport rather than just having a visible top coordinate.

package.json (1)

18-18: LGTM! Dependencies correctly added for new features.

The new dependencies support the introduced features:

  • Radix UI components for alert dialogs and tabs
  • KaTeX for LaTeX math rendering
  • TypeScript definitions for type safety

Version ranges are appropriate using caret notation for patch compatibility.

Also applies to: 27-27, 38-38, 57-57

components/examples/CTASection.tsx (1)

8-8: LGTM! Prop rename is complete and consistent.

The onOpenGeneratoronStartCreating rename is consistently applied throughout the component and aligns with the navigation-based flow changes in app/examples/page.tsx.

Also applies to: 11-11, 42-42

components/MathTooltipContent.tsx (2)

48-77: LGTM! LaTeX processing logic and error handling are sound.

The regex patterns correctly handle:

  • Display math $$...$$ processed first to avoid conflicts
  • Inline math $...$ processed second, excluding newlines
  • Error handling gracefully falls back to original delimiters

The containsMath helper efficiently detects LaTeX presence.

Also applies to: 85-87


25-77: The XSS concern does not apply here. All tooltip content comes from hardcoded values in CARD_INFO_TOOLTIPS (lib/card-info-tooltips.ts), which are application-generated strings containing only plain text and LaTeX math formulas—no user input or external HTML is involved.

The example <script>alert('xss')</script> cannot occur because the tooltip sources (countryDiversity, genreDiversity, etc.) are defined as static strings in the source code using String.raw, with no HTML tags. KaTeX's renderToString() safely renders math expressions, and content outside delimiters remains unchanged plain text. No sanitization is required when the content is trusted and controlled.

If the optional infoTooltip prop is used to override defaults, the responsibility for trusted content moves to the caller—the component should document this expectation if necessary.

Likely an incorrect or invalid review comment.

tests/unit/api/card/card.test.ts (2)

1538-1570: LGTM! Test correctly validates DB lookup optimization.

The test verifies that when:

  • colorPreset=custom
  • All required color params are in the URL

The route skips the cards DB lookup and uses only the user record, correctly passing URL-provided colors to the template. This validates an important performance optimization.


1572-1625: LGTM! Test validates URL color precedence with required DB config.

The test correctly verifies that when:

  • DB config is required for card-specific flags (e.g., showFavorites for animeStaff)
  • URL provides colorPreset=custom with full color params

URL-provided colors override DB-configured colors, even though the DB lookup still occurs for other fields. This validates the proper precedence rules in the color resolution logic.

app/page.tsx (1)

20-26: LGTM! Clean router-based navigation implementation.

The transition from modal-based flow to router navigation is well-implemented. The useCallback hook correctly includes router in its dependency array, and the analytics tracking is preserved.

components/examples/ExampleCard.tsx (1)

16-29: LGTM! Consistent prop rename.

The prop rename from onOpenGenerator to onStartCreating is cleanly applied across the interface definition and component destructuring. The button text update to "Create Your Cards" better reflects the new user flow.

components/examples/CategorySection.tsx (1)

37-42: LGTM! Proper prop propagation.

The onStartCreating prop rename is consistently applied through the interface, component signature, and child component invocation. The propagation chain aligns with the ExampleCard API changes.

Also applies to: 111-116, 184-192

components/user/ColorPreviewCard.tsx (1)

149-157: dangerouslySetInnerHTML usage is acceptable here.

The static analysis flagged this as a potential XSS vector. However, the SVG content is:

  1. Generated internally by animeStatsTemplate with controlled inputs
  2. Processed through stripTrustedSvgMarker which implies a trust boundary

Since this is not rendering user-provided HTML, the XSS risk is mitigated. Ensure that animeStatsTemplate properly sanitizes any user-controlled values (like username) if they're ever used with actual user input.

components/user/GlobalSettingsPanel.tsx (1)

75-113: LGTM! Well-structured global settings panel.

The component properly integrates with the Zustand store, uses appropriate memoization for handlers, and correctly wires up the CardSettingsPanel in global mode.

components/user/CardSettingsPanel.tsx (1)

55-94: LGTM! Clean discriminated union pattern.

The type narrowing via props.mode === "global" is idiomatic TypeScript, and the conditional rendering logic correctly handles all three states: global settings, card with custom settings enabled, and card with global settings fallback.

components/user/BulkActionsToolbar.tsx (2)

100-159: Well-structured URL building with proper memoization.

The selectedCards computation correctly integrates with the store's effective color/border getters and properly maps configurations to URL parameters. The dependency array appears complete, and the null-filtering type guard is correctly implemented.


65-71: Good SSR-safe portal pattern.

The isMounted state pattern correctly prevents hydration mismatches by deferring portal rendering until after client-side hydration. The early returns are properly ordered.

Also applies to: 224-227

components/user/SettingsContent.tsx (4)

1-99: Well-structured component with clear prop interface.

The component's props interface is comprehensive and well-documented with JSDoc comments. The separation of color, border, and advanced settings concerns is clean.


127-141: Visibility defaults and tab logic are correct.

The default visibility calculation based on mode and the hasAdvancedOptions check ensure the Advanced tab only appears when relevant options exist.


143-171: useMemo dependencies are complete.

The memoization correctly includes all dependencies used in the callback: idPrefix, mode, colors, and onColorChange.


398-434: Grid input handlers correctly clamp values.

The inline clamping logic Math.max(1, Math.min(5, Number(e.target.value) || 1)) ensures grid dimensions stay within valid bounds (1-5). The fallback to 1 when parsing fails is appropriate.

tests/e2e/user/user.spec.ts (3)

17-28: Error state expectations updated correctly.

The updated assertions align with the new UI text for error states. Using regex patterns (/something went wrong/i, /search for user/i) provides flexibility for minor text variations.


78-107: Bulk actions test is comprehensive.

The test properly:

  1. Selects specific cards via checkboxes
  2. Verifies toolbar visibility with data-testid
  3. Checks button order and labels
  4. Tests popover interactions with appropriate timeouts

Good coverage of the new bulk actions workflow.


137-206: New user onboarding test provides good coverage.

The test correctly mocks the full API sequence for a new user:

  • 404 from get-user triggers Anilist lookup
  • Successful get-anilist response
  • Store endpoints return success
  • get-cards returns empty array
  • Card endpoint returns valid SVG

The assertion that at least one preview image appears validates the initial cards snapshot behavior.

app/api/store-cards/route.ts (4)

52-69: Good defensive backfill for supported card types.

ensureAllSupportedCardTypesPresent ensures Redis records always contain all supported card types, preventing partial records. Cards not explicitly provided are marked as disabled.


166-186: Variation fallback preserves pie settings when disabling cards.

The logic correctly falls back to previous?.variation when incoming.variation is undefined, ensuring showPiePercentages settings aren't lost when toggling a card's disabled state without re-specifying variation.


267-320: buildCardConfig correctly respects useCustomSettings.

When useCustomSettings is false, color fields and borderColor are set to undefined, ensuring the card defers to global settings. The default of true maintains backward compatibility.


455-467: Global settings merge logic correctly handles conditional fields.

The merged global settings object properly:

  • Preserves existingGlobalSettings when no new globalSettings provided
  • Clears borderColor and borderRadius when borders disabled
  • Merges advanced settings with proper precedence
lib/card-data/config.ts (5)

13-25: Clean helper for validating complete color params.

hasAllRequiredColorParams provides a clear, reusable check for URL color completeness. The truthy check (!!params.titleColorParam) correctly handles both null and undefined.


48-56: Updated color resolution logic is correct.

The comment and implementation correctly document that colorPreset=custom without individual colors requires DB fetch, while colorPreset=custom with all colors can be resolved from URL alone.


194-203: Custom preset URL override enhancement is well-implemented.

Allowing URL color params to override when the preset is "custom" AND all required params are present enables sharable custom color links without DB dependency. The early return after applyUrlColorParams is correct.


436-440: Global settings merge placement is correct.

Merging global settings into the card config before applying URL overrides ensures:

  1. Cards without per-card settings inherit from globals
  2. URL params can still override inherited values

559-609: mergeGlobalSettingsIntoConfig uses correct non-clobbering pattern.

The setIfMissing helper elegantly ensures global settings only fill gaps without overwriting explicit per-card values. The conditional border application based on borderEnabled is appropriate.

components/user/CardCategorySection.tsx (3)

50-63: Controlled/uncontrolled pattern is well-implemented.

The component correctly:

  • Determines mode via typeof expanded === "boolean"
  • Only updates internal state when uncontrolled
  • Always calls onExpandedChange when provided

This follows React's controlled component conventions.


75-84: Good accessibility implementation.

The header button properly uses:

  • type="button" to prevent form submission
  • aria-expanded for screen readers
  • aria-controls linked to content via useId

142-159: AnimatePresence correctly wraps conditional content.

Unlike the issue in SettingsContent.tsx, this component properly wraps the conditional motion.div with AnimatePresence, enabling exit animations when the section collapses.

lib/stores/index.ts (2)

19-32: New user-page-editor exports provide comprehensive API.

The new exports include:

  • Hook: useUserPageEditor
  • Selectors: selectEnabledCardIds, selectHasEnabledCards, selectCardConfigsByGroup
  • Types: CardColorOverride, CardAdvancedSettings, CardEditorConfig, etc.

This consolidates card editing state management into a single store.


6-7: Types are not removed, only excluded from barrel export.

UserPreferencesState, UserPreferencesActions, and UserPreferencesStore remain exported from lib/stores/user-preferences.ts. They are simply not re-exported in the barrel at lib/stores/index.ts. This is not a breaking change—no external consumers depend on these types.

Likely an incorrect or invalid review comment.

components/ui/AlertDialog.tsx (2)

1-66: Standard Radix AlertDialog wrapper implementation.

The component follows established shadcn/ui patterns:

  • ForwardRef for components that need ref access
  • DisplayName assignments for debugging
  • Composition of Portal and Overlay in Content
  • Consistent animation classes

137-171: Action and Cancel buttons correctly styled.

AlertDialogAction uses default button styles while AlertDialogCancel uses outline variant with responsive margin (mt-2 sm:mt-0). This provides appropriate visual hierarchy for confirmation dialogs.

tests/unit/api/store-cards/store-cards.test.ts (6)

157-172: Good validation test for disabled field type.

Tests that non-boolean disabled values (like "yes") are properly rejected with 400 status. This ensures type safety at the API boundary.


267-290: Storage assertions updated for backfill behavior.

The test correctly verifies that:

  1. Total card count matches displayNames length (backfilled)
  2. All supported card types are present
  3. Explicitly provided cards retain their configuration

Good validation of the ensureAllSupportedCardTypesPresent behavior.


322-384: Preserving settings when disabling is well-tested.

This test validates a critical user experience scenario: toggling a card's disabled state shouldn't lose custom configurations. The mock setup and assertions are comprehensive.


946-1007: Border settings tests cover global/per-card interactions.

Good coverage of:

  • borderColor not saved when borderEnabled=false
  • Clearing existing borderColor when disabling
  • Preserving per-card borders when global is disabled

1241-1273: Test verifies borderRadius clamping during merge.

The test confirms that out-of-range values (150) are clamped to the maximum (100) when merging existing global settings. This validates the clampBorderRadius integration.


1353-1384: Grid dimension clamping tests are thorough.

Tests both:

  • Clamping existing out-of-range values (999 → 5, -10 → 1)
  • Clamping incoming values

Validates the clampGridDim function integration for grid settings.

components/user/CardTile.tsx (3)

97-142: LGTM - Well-structured component initialization.

Good use of Zustand store selectors and fallback config object with sensible defaults. The memoized effective colors/border calculations will help prevent unnecessary re-renders.


156-193: LGTM - Preview URL construction is well-memoized.

The dependency array correctly captures all values that affect the URL. The early return for missing userId prevents unnecessary computation.


232-252: LGTM - Download handler with proper error handling.

Good use of loading state to prevent concurrent downloads, and proper cleanup in finally block.

lib/types/records.ts (3)

1-2: LGTM - Proper type import for ColorValue.

The import enables gradient support for color fields throughout the records types.


444-462: LGTM - StoredCardConfig updates are well-designed.

The changes properly support:

  • Optional variation for backwards compatibility
  • ColorValue type for gradient support
  • useCustomSettings flag for explicit per-card override detection
  • disabled flag for card visibility state

464-486: LGTM - GlobalCardSettings interface properly encapsulates shared configuration.

The interface mirrors per-card settings appropriately, with borderEnabled as the key differentiator for global border state. Integration with CardsRecord via optional globalSettings field maintains backwards compatibility.

lib/card-groups.ts (3)

9-17: LGTM - colorToString correctly serializes ColorValue types.

The function properly handles both string colors and gradient objects (serialized as JSON). The undefined check prevents unnecessary processing.


458-478: LGTM - Type signature properly separates color fields for ColorValue support.

The Omit pattern with re-declared color fields typed as ColorValue allows the function to accept both string and gradient inputs while the original StoredCardConfig may have different typing. The new allowPresetColorOverrides option provides flexibility for different use cases.


492-522: LGTM - Border and color inclusion logic is well-designed.

Key behaviors:

  • borderRadius is only included when borderColor is present (sensible - radius without border is meaningless)
  • Colors are only included when shouldIncludeColors is true, respecting preset semantics
  • The hasNamedPreset check prevents color overrides when using named presets (unless explicitly allowed)
lib/stores/user-page-editor.ts (3)

358-445: LGTM - Robust server-to-editor config transformation.

The function handles:

  • Explicit useCustomSettings flag from server (preferred)
  • Legacy heuristic detection via color comparison for backwards compatibility
  • Proper normalization of ColorValue for comparison (string vs JSON)
  • Disabled state mapping to enabled flag

The logic correctly prioritizes explicit server flags over heuristics.


599-1110: LGTM - Comprehensive Zustand store implementation.

The store is well-organized with:

  • Clear action groupings (user data, global settings, card config, UI, bulk ops, save)
  • Proper use of devtools middleware for debugging
  • Consistent dirty state tracking on mutations
  • Effective color/border resolution methods that handle inheritance

1148-1161: selectCardConfigsByGroup is incomplete and unused—the function is dead code.

CardEditorConfig doesn't store group information, so this selector cannot group by statCardTypes without additional context. The function is exported but never referenced in the codebase. Either remove it or complete it by passing statCardTypes as a parameter and implementing the proper grouping logic using the group property from each card type.

components/stat-card-generator/constants.ts (3)

111-139: LGTM - Label simplifications improve readability.

The removal of verbose qualifiers like "(Top 5 Count)" and "(Pie/Donut/Bar Breakdowns)" makes the UI cleaner while the card functionality remains unchanged.


141-179: LGTM - Consistent label simplification across manga and activity cards.

Labels are now concise and consistent with the anime breakdown cards pattern.


243-267: LGTM - Core stats labels simplified.

"Anime Statistics" → "Anime Stats" etc. provides a cleaner UI without losing clarity.

components/user/UserPageEditor.tsx (6)

136-180: LGTM - Robust AniList user ID fetching with proper error handling.

Good handling of specific HTTP status codes (404, 429) with user-friendly messages. The try/catch properly handles network failures.


257-264: LGTM - Initial cards snapshot provides sensible defaults.

New users start with all cards enabled using the first variation and default preset, ensuring a complete initial experience.


384-496: LGTM - Comprehensive new user setup flow.

The orchestration properly:

  1. Resolves userId from username if needed
  2. Fetches stats with proper error tracking
  3. Extracts username from stats as fallback
  4. Saves user to database
  5. Saves initial cards (non-fatal on failure)
  6. Extracts avatar URL

Error tracking is thorough with appropriate categories.


652-847: LGTM - Data loading effect with StrictMode protection.

The lastLoadedUserRef pattern correctly prevents duplicate API calls during React StrictMode double-mounting. The ref is cleared on errors to allow retries.

Key behaviors:

  • Validates userId/username before proceeding
  • Handles 404 as "new user" trigger
  • Falls back to initial snapshot if persisted cards fetch fails
  • Proper cleanup and retry capability

728-780: Good fallback handling for missing persisted cards.

The code handles the edge case where the cards we just saved aren't immediately available (eventual consistency). Falling back to buildInitialCardsSnapshot() ensures the UI is never left in an inconsistent state.

The warning log and error tracking provide visibility into potential storage issues.


1043-1541: LGTM - Well-structured responsive UI.

The main render includes:

  • Accessible toolbar with proper ARIA roles
  • Responsive filter controls (mobile popover vs desktop inline)
  • Confirmation dialog for destructive reset action
  • Motion animations for smooth transitions
  • Proper empty state handling

Good use of cn() for conditional styling and consistent dark mode support.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/api-utils.ts (1)

619-653: Validation gap: disabled cards can store unvalidated color data.

The current logic skips color validation for disabled cards (line 639-653), which means a disabled card could be stored with malformed color values:

{
  "cardName": "animeStats",
  "disabled": true,
  "titleColor": "invalid-color-format"
}

This payload would pass validation but store invalid color data. When the card is later enabled, the invalid colors would be used.

Recommendation: Consider validating color fields even for disabled cards, or explicitly strip/ignore color fields when disabled: true to prevent storing invalid data.

Suggested validation improvement
  // Only validate color requirements when not disabled (disabled cards store minimal data)
  if (!isDisabled) {
    const rawPreset = card["colorPreset"];
    const preset =
      typeof rawPreset === "string" && rawPreset.trim().length > 0
        ? rawPreset
        : undefined;
    const requireColorFields = preset === undefined || preset === "custom";
    if (requireColorFields) {
      const reqColorErr = validateRequiredColorFields(
        card,
        requiredColorFields,
      );
      if (reqColorErr) return reqColorErr;
    }
+ } else {
+   // For disabled cards, validate that color fields are absent or valid if present
+   for (const field of requiredColorFields) {
+     const value = card[field];
+     if (value !== undefined && value !== null && !validateColorValue(value)) {
+       const reason = getColorInvalidReason(value);
+       console.warn(
+         `⚠️ [${endpoint}] Disabled card ${cardIndex} has invalid color format for ${field} (${reason})`,
+       );
+       return NextResponse.json(
+         { error: "Invalid data" },
+         { status: 400, headers: apiJsonHeaders(request) },
+       );
+     }
+   }
  }
🧹 Nitpick comments (2)
components/user/CardTile.tsx (1)

136-142: Consider moving config defaults to the store layer.

The component creates a default config object when the store doesn't have one. While this works, it places default-value responsibility in the component rather than the store. If defaults need to change or other components need the same defaults, you'll have duplication.

Consider having the store's selector or a getter method return a default config structure, keeping the source of truth centralized.

app/api/store-cards/route.ts (1)

351-391: Redundant validation: disabled field checked twice.

The validateDisabledBooleanField function (lines 356-391) validates that the disabled field is a boolean, but this validation is duplicated in validateCardOptionalFields (lib/api-utils.ts, lines 674-692) which is called later by validateCardData.

Validation flow:

  1. Line 416-422: validateDisabledBooleanField checks disabled is boolean
  2. Line 425: validateCardData is called
  3. Inside validateCardData: validateCardOptionalFields checks disabled is boolean again

Recommendation: Remove validateDisabledBooleanField since validateCardData already performs this validation. If early validation is desired for better error messages, document the rationale in a comment.

Suggested removal
- /**
-  * Validates that every provided `disabled` field (if present) is a boolean.
-  * Returns a 400 response via `jsonWithCors` when an invalid value is found and
-  * increments analytics to track failed requests.
-  */
- async function validateDisabledBooleanField(
-   incomingCards: unknown,
-   endpoint: string,
-   endpointKey: string,
-   request?: Request,
- ): Promise<NextResponse | null> {
-   if (!Array.isArray(incomingCards)) return null;
- 
-   for (let i = 0; i < incomingCards.length; i++) {
-     const card = incomingCards[i];
- 
-     // Skip non-object entries -- they will be rejected by validateCardData later.
-     if (typeof card !== "object" || card === null) continue;
- 
-     const cardObj = card as Record<string, unknown>;
-     if (
-       Object.prototype.hasOwnProperty.call(cardObj, "disabled") &&
-       typeof cardObj.disabled !== "boolean"
-     ) {
-       console.warn(
-         `⚠️ [${endpoint}] Card ${i} has non-boolean disabled field: ${String(
-           cardObj.disabled,
-         )}`,
-       );
-       await incrementAnalytics(
-         buildAnalyticsMetricKey(endpointKey, "failed_requests"),
-       );
-       return jsonWithCors(
-         { error: "Invalid 'disabled' field type" },
-         request,
-         400,
-       );
-     }
-   }
-   return null;
- }

And remove the call at lines 416-422:

-     const disabledError = await validateDisabledBooleanField(
-       incomingCards,
-       endpoint,
-       endpointKey,
-       request,
-     );
-     if (disabledError) return disabledError;
- 
      // Validate incoming data and obtain typed cards on success
      const validated = validateCardData(
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75168a6 and a57c3a6.

📒 Files selected for processing (6)
  • app/api/store-cards/route.ts
  • components/stat-card-generator/ColorPickerGroup.tsx
  • components/user/CardTile.tsx
  • lib/api-utils.ts
  • openapi.yaml
  • tests/unit/api/store-cards/store-cards.test.ts
🧰 Additional context used
🧬 Code graph analysis (4)
app/api/store-cards/route.ts (4)
lib/card-data/validation.ts (1)
  • displayNames (79-134)
lib/types/records.ts (3)
  • StoredCardConfig (445-462)
  • GlobalCardSettings (465-479)
  • CardsRecord (482-487)
lib/utils.ts (2)
  • safeParse (769-802)
  • clampBorderRadius (870-877)
lib/api-utils.ts (2)
  • incrementAnalytics (364-371)
  • buildAnalyticsMetricKey (379-387)
tests/unit/api/store-cards/store-cards.test.ts (2)
app/api/store-cards/route.ts (1)
  • POST (399-548)
lib/card-data/validation.ts (1)
  • displayNames (79-134)
components/stat-card-generator/ColorPickerGroup.tsx (1)
lib/types/card.ts (1)
  • GradientStop (5-14)
lib/api-utils.ts (1)
lib/types/records.ts (1)
  • StoredCardConfig (445-462)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: E2E Mobile Chrome
  • GitHub Check: E2E Chromium
🔇 Additional comments (17)
components/stat-card-generator/ColorPickerGroup.tsx (5)

238-240: LGTM!

The initial state correctly ensures each stop has an ID by mapping over the incoming stops prop and falling back to generateStopId() when the id field is absent.


266-275: LGTM!

The handleStopChange callback correctly operates on localStops, updates internal state, and propagates changes via onStopsChange. The dependency array is appropriate.


277-289: LGTM!

The addStop function correctly:

  • Guards against exceeding 5 stops using localStops.length
  • Generates a unique ID for the new stop
  • Updates both local state and propagates via callback

291-299: LGTM!

The removeStop function properly guards against removing stops when only 2 remain, operates on localStops, and propagates changes correctly.


340-341: LGTM!

The rendering logic correctly derives disabled states and iterates over localStops. The ARIA label dynamically reflects the current stop count.

Also applies to: 352-352, 435-435

components/user/CardTile.tsx (1)

210-228: LGTM! Clipboard error handling properly implemented.

The try-catch block correctly handles potential clipboard API failures. Error logging provides debugging visibility while the UI gracefully handles failures. This addresses the concern from the previous review.

openapi.yaml (1)

670-672: LGTM! Clean schema addition for disabled cards.

The disabled field is well-documented and appropriately optional, allowing cards to be marked as hidden/disabled by users without requiring changes to existing stored configurations.

lib/api-utils.ts (3)

12-12: Good addition for type safety.

Importing StoredCardConfig enables the improved return type for validateCardData, addressing the past review concern about type assertions bypassing static validation.


674-692: Correct validation for the disabled field.

The disabled field is properly validated as an optional boolean alongside other optional boolean fields like showFavorites, useStatusColors, and showPiePercentages.


1008-1092: Excellent improvement addressing past review feedback!

The updated validateCardData signature now returns StoredCardConfig[] on success instead of null, eliminating the type assertion issue flagged in the previous review (see past review comment at lines 410-411 in app/api/store-cards/route.ts).

Benefits:

  • Callers receive typed data directly without unsafe assertions
  • Static type checking now covers the validated payload
  • The typed construction (lines 1046-1089) properly handles all fields including the new disabled and useCustomSettings properties

The implementation comprehensively maps all fields with appropriate type coercion (coerceNum helper for numeric fields, explicit boolean checks, and preservation of ColorValue types for color fields).

app/api/store-cards/route.ts (6)

1-19: LGTM! Imports properly support new global settings model.

The updated imports bring in necessary types (GlobalCardSettings, StoredCardConfig, CardsRecord) and utilities (displayNames for card type enumeration, isValidCardType for validation) to support the enhanced per-card and global settings architecture.


52-69: Good backfilling strategy for data completeness.

This function ensures that every supported card type exists in storage, adding missing types with disabled: true. This prevents partial records and simplifies downstream logic that expects all card types to be present.

The approach aligns with test expectations (e.g., test at lines 407-422 verifies backfilling behavior).


126-255: Well-structured helper functions for settings merging.

These helpers provide clean separation of concerns:

  • parseExistingGlobalSettings: Safe extraction with fallback to undefined on parse errors
  • computeShowPiePercentages (lines 166-186): Correctly considers both incoming and previous variation to determine if pie settings should be saved, addressing the edge case where disabling a card might omit variation
  • computeShowFavorites: Properly checks card capability before merging
  • clampGridDim: Robust clamping with finite number checks
  • mergeGlobalAdvancedSettings: Coherent merge strategy that prefers incoming values and applies clamping

The logic correctly handles all edge cases including undefined values and out-of-range inputs.


257-320: Complex but well-structured card config builder.

buildCardConfig handles intricate merging logic for per-card settings:

  • useCustomSettings semantics (lines 274-282): Defaults to true, determines whether to save color data at all
  • Color preset handling (lines 278-282): Individual colors only saved when colorPreset is undefined or "custom"
  • Disabled card preservation (lines 295-296): disabled: true flag preserved, allowing settings to persist through disable/enable cycles
  • Border handling (lines 289-291): Per-card borderColor only saved when useCustomSettings is true, independent of global border state
  • Advanced settings (lines 313-317): Properly computes favorites, status colors, and pie percentages using dedicated helpers

The logic correctly merges incoming and previous values while respecting the new per-card/global settings model.


322-349: Clean helper functions for card application and border radius computation.

  • applyIncomingCards (lines 322-334): Straightforward iteration that applies incoming cards by merging with existing configs via buildCardConfig
  • computeEffectiveBorderRadius (lines 336-349): Correctly computes border radius only when borders are enabled, with proper clamping and fallback to existing values

Both functions are simple, focused, and correctly implemented.


399-548: Comprehensive POST flow integrating global settings and typed validation.

The updated POST handler successfully integrates:

  1. Global settings extraction (line 411): Accepts optional globalSettings in request body
  2. Typed validation (lines 425-437): Uses updated validateCardData that returns typed array, eliminating unsafe type assertions
  3. Existing data merging (lines 460-476): Safely parses existing cards and global settings
  4. Card application (lines 483-486): Applies incoming cards and ensures all supported types exist
  5. Global settings computation (lines 488-519): Merges incoming and existing global settings with proper clamping and conditional logic
  6. Persistence (lines 521-530): Stores complete record with merged global settings

Key improvements:

  • Line 431: if (!Array.isArray(validated)) correctly distinguishes validation failure (NextResponse) from success (typed array)
  • Lines 488-519: Complex but correct global settings merging with clamping for borderRadius and grid dimensions
  • Line 524: globalSettings properly included in persisted record

The flow handles all edge cases including missing data, out-of-range values, and disabled cards.

tests/unit/api/store-cards/store-cards.test.ts (1)

1-1770: Excellent comprehensive test coverage for new features.

The test suite thoroughly validates the enhanced global/per-card settings model:

Input Validation Coverage:

  • Non-object card entries (lines 129-141)
  • Non-boolean disabled fields (lines 171-193)
  • Null cards arrays (line 122)
  • Invalid JSON bodies (lines 155-169)

Disabled Cards Coverage:

  • Acceptance and persistence (lines 314-341)
  • Settings preservation when disabled (lines 343-405)
  • Backfilling all supported types when empty (lines 407-422)

Color & Settings Coverage:

  • Color preset handling (lines 847-911)
  • useCustomSettings=false skips color persistence (lines 912-941)
  • useCustomSettings=true preserves colors (lines 943-964)

Border Settings Coverage:

  • Global borderEnabled interactions (lines 968-1055)
  • Per-card border preservation when globally disabled (lines 1057-1137)
  • Border radius clamping and merging (lines 1262-1294)
  • useCustomSettings affects per-card borders (lines 1218-1260)

Global Advanced Settings Coverage:

  • Persistence of useStatusColors, showPiePercentages, showFavorites, grid dims (lines 1298-1333)
  • Preservation when incoming omits them (lines 1335-1372)
  • Clamping of out-of-range grid dimensions (lines 1374-1427)

Assertions verify:

  • Stored cards length matches Object.keys(displayNames).length (lines 288, 332, 416, 474, 711, etc.)
  • Specific cards exist with expected properties
  • Backfilling creates disabled entries for missing types
  • Merging preserves previous values when incoming omits them

The tests provide strong confidence in correctness and edge-case handling.

Comment thread components/stat-card-generator/ColorPickerGroup.tsx Outdated
@RLAlpha49
Copy link
Copy Markdown
Owner Author

@coderabbitai help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
    • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
    • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@RLAlpha49
Copy link
Copy Markdown
Owner Author

@coderabbitai configuration

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

Configuration used: defaults

CodeRabbit configuration
language: en-US
tone_instructions: ''
early_access: false
enable_free_tier: true
inheritance: false
reviews:
  profile: chill
  request_changes_workflow: false
  high_level_summary: true
  high_level_summary_instructions: ''
  high_level_summary_placeholder: '@coderabbitai summary'
  high_level_summary_in_walkthrough: false
  auto_title_placeholder: '@coderabbitai'
  auto_title_instructions: ''
  review_status: true
  commit_status: true
  fail_commit_status: false
  collapse_walkthrough: true
  changed_files_summary: true
  sequence_diagrams: true
  estimate_code_review_effort: true
  assess_linked_issues: true
  related_issues: true
  related_prs: true
  suggested_labels: true
  labeling_instructions: []
  auto_apply_labels: false
  suggested_reviewers: true
  auto_assign_reviewers: false
  in_progress_fortune: true
  poem: true
  enable_prompt_for_ai_agents: true
  path_filters: []
  path_instructions: []
  abort_on_close: true
  disable_cache: false
  auto_review:
    enabled: true
    auto_incremental_review: true
    ignore_title_keywords: []
    labels: []
    drafts: false
    base_branches: []
    ignore_usernames: []
  finishing_touches:
    docstrings:
      enabled: true
    unit_tests:
      enabled: true
  pre_merge_checks:
    docstrings:
      mode: warning
      threshold: 80
    title:
      mode: warning
      requirements: ''
    description:
      mode: warning
    issue_assessment:
      mode: warning
    custom_checks: []
  tools:
    ast-grep:
      rule_dirs: []
      util_dirs: []
      essential_rules: true
      packages: []
    shellcheck:
      enabled: true
    ruff:
      enabled: true
    markdownlint:
      enabled: true
    github-checks:
      enabled: true
      timeout_ms: 90000
    languagetool:
      enabled: true
      enabled_rules: []
      disabled_rules: []
      enabled_categories: []
      disabled_categories: []
      enabled_only: false
      level: default
    biome:
      enabled: true
    hadolint:
      enabled: true
    swiftlint:
      enabled: true
    phpstan:
      enabled: true
      level: default
    phpmd:
      enabled: true
    phpcs:
      enabled: true
    golangci-lint:
      enabled: true
    yamllint:
      enabled: true
    gitleaks:
      enabled: true
    checkov:
      enabled: true
    detekt:
      enabled: true
    eslint:
      enabled: true
    flake8:
      enabled: true
    fortitudeLint:
      enabled: true
    rubocop:
      enabled: true
    buf:
      enabled: true
    regal:
      enabled: true
    actionlint:
      enabled: true
    pmd:
      enabled: true
    clang:
      enabled: true
    cppcheck:
      enabled: true
    semgrep:
      enabled: true
    circleci:
      enabled: true
    clippy:
      enabled: true
    sqlfluff:
      enabled: true
    prismaLint:
      enabled: true
    pylint:
      enabled: true
    oxc:
      enabled: true
    shopifyThemeCheck:
      enabled: true
    luacheck:
      enabled: true
    brakeman:
      enabled: true
    dotenvLint:
      enabled: true
    htmlhint:
      enabled: true
    checkmake:
      enabled: true
    osvScanner:
      enabled: true
chat:
  art: true
  auto_reply: true
  integrations:
    jira:
      usage: auto
    linear:
      usage: auto
knowledge_base:
  opt_out: false
  web_search:
    enabled: true
  code_guidelines:
    enabled: true
    filePatterns: []
  learnings:
    scope: auto
  issues:
    scope: auto
  jira:
    usage: auto
    project_keys: []
  linear:
    usage: auto
    team_keys: []
  pull_requests:
    scope: auto
  mcp:
    usage: auto
    disabled_servers: []
code_generation:
  docstrings:
    language: en-US
    path_instructions: []
  unit_tests:
    path_instructions: []
issue_enrichment:
  auto_enrich:
    enabled: true
  planning:
    enabled: true
    auto_planning:
      enabled: true
      labels: []
  labeling:
    labeling_instructions: []
    auto_apply_labels: false

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a57c3a6 and 6ebece6.

📒 Files selected for processing (1)
  • components/stat-card-generator/ColorPickerGroup.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/stat-card-generator/ColorPickerGroup.tsx (1)
lib/types/card.ts (1)
  • GradientStop (5-14)
🔇 Additional comments (2)
components/stat-card-generator/ColorPickerGroup.tsx (2)

261-294: LGTM! Callbacks correctly use localStops.

All three callbacks (handleStopChange, addStop, removeStop) correctly:

  • Read from and update localStops state
  • Include localStops in their dependency arrays
  • Propagate changes through onStopsChange

This ensures the local state stays in sync with user interactions.


335-347: LGTM! UI correctly renders from localStops.

The component now renders based on localStops rather than the stops prop, ensuring:

  • Users see their changes immediately without waiting for parent re-render
  • Disabled states (add/remove buttons) reflect the current local state
  • ARIA labels provide accurate context

This is the correct pattern for controlled components with internal state management.

Also applies to: 430-430

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants