Skip to content

Comments

Staging#68

Merged
izadoesdev merged 8 commits intomainfrom
staging
Aug 9, 2025
Merged

Staging#68
izadoesdev merged 8 commits intomainfrom
staging

Conversation

@izadoesdev
Copy link
Member

@izadoesdev izadoesdev commented Aug 9, 2025

Pull Request

Description

Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

New Features

  • Introduced a comprehensive new pricing page with interactive cost estimation, plan comparison tables, and modular plan normalization utilities.
  • Added new landing page components: animated logo carousel, testimonials, FAQ accordion, feature grid cards, world map visualization, interactive demo container, and sci-fi styled UI elements.
  • Enhanced accessibility and responsive design across dashboard and documentation interfaces.

Improvements

  • Refined device type detection and resolution parsing for analytics.
  • Improved type safety and data consistency in analytics data processing.
  • Updated dashboard organization, member, and website management with better async handling, error feedback, and UI feedback.
  • Enhanced dashboard loading states and accessibility for website lists and charts.
  • Updated global styles with new color palettes, responsive utilities, and performance optimizations.

Bug Fixes

  • Fixed color and icon inconsistencies for dark mode and accessibility.
  • Improved error handling and user feedback in organization selection and website management.

Refactor

  • Modularized and refactored landing and pricing page components for clarity and maintainability.
  • Replaced static FAQ and testimonials with interactive, animated components.
  • Updated dashboard and documentation layouts for improved structure and navigation.

Chores

  • Added new dependencies for icons, animation, and sound in documentation.
  • Updated Next.js image configuration for remote icon support.

Removals

  • Deleted legacy pricing comparison and schema snapshot files.
  • Removed unused or redundant code and native dialogs in favor of custom modals and improved UI flows.

Database

  • Added new indexes and unique constraints for improved query performance.
  • Removed obsolete foreign key constraints and indexes.
  • Added new one-to-one relation between API keys and organizations.

izadoesdev and others added 6 commits August 8, 2025 00:27
* feat: navbar

* feat: hero

* feat: trusted by

* feat: cards

* feat: desc & faq

* feat: footer svg

* feat: footer & testimonials

* feat: landing

* fix: on click btns

* fix: responsive

* feat: light mode, dark mode, some icon fixes

* feat: pricing page

* feat: better pricing page

* trusted by more people bro

* feat: cleanup

---------

Co-authored-by: Hyteq <DerpsDer.es@gmail.com>
@vercel
Copy link

vercel bot commented Aug 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
dashboard (staging) ⬜️ Skipped (Inspect) Aug 9, 2025 10:15am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Warning

Rate limit exceeded

@izadoesdev has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 51 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5d07e3d and 283d04c.

📒 Files selected for processing (1)
  • apps/docs/components/landing/testimonials.tsx (10 hunks)

Walkthrough

This update introduces new pricing, plan normalization, and estimator utilities for the documentation site, including a modular, interactive pricing page. It removes the old pricing comparison implementation and related components, restructures the landing page with new sections and visual elements, and overhauls global styles for improved responsiveness and theming. Database schema files are deleted, and API, dashboard, and DB packages receive targeted enhancements for type safety, plugin configuration, and cache handling.

Changes

Cohort / File(s) Change Summary
Docs Pricing System: Data, Types, Normalization, Utilities, Estimator, Table
apps/docs/app/(home)/pricing/data.ts, apps/docs/app/(home)/pricing/@pricing/types.ts, apps/docs/app/(home)/pricing/@pricing/normalize.ts, apps/docs/app/(home)/pricing/@pricing/best-plan.ts, apps/docs/app/(home)/pricing/@pricing/estimator-scale.ts, apps/docs/app/(home)/pricing/@pricing/estimator-utils.ts, apps/docs/app/(home)/pricing/@pricing/estimator.tsx, apps/docs/app/(home)/pricing/@pricing/table.tsx
Introduces a new pricing data model, normalization, and utilities for tiered pricing, plus estimator and comparison table React components. Defines types, raw plan data, normalization logic, plan selection, slider scaling, cost estimation, and a modular UI for pricing display and calculation.
Docs Pricing Page Refactor
apps/docs/app/(home)/pricing/page.tsx, apps/docs/app/pricing/page.tsx, apps/docs/components/pricing/pricing-page.tsx
Adds a new interactive pricing page using the new pricing system, and deletes the previous pricing page and its supporting component.
Landing Page Overhaul: Sections, Components, Visuals
apps/docs/app/(home)/page.tsx, apps/docs/app/(home)/layout.tsx, apps/docs/app/layout.tsx, apps/docs/components/landing/card.tsx, apps/docs/components/landing/description.tsx, apps/docs/components/landing/faq.tsx, apps/docs/components/landing/footer-nav.tsx, apps/docs/components/landing/grid-cards.tsx, apps/docs/components/landing/grid-pattern.tsx, apps/docs/components/landing/hero.tsx, apps/docs/components/landing/logo-carousel.tsx, apps/docs/components/landing/map.tsx, apps/docs/components/landing/scifi-btn.tsx, apps/docs/components/landing/section-svg.tsx, apps/docs/components/landing/section.tsx, apps/docs/components/landing/spotlight.tsx, apps/docs/components/landing/testimonials.tsx, apps/docs/components/landing/trusted-by.tsx, apps/docs/components/landing/wordmark.tsx
Refactors the landing page to use modular sections and new components for hero, description, testimonials, grid cards, trusted by, FAQ, footer nav, and visual elements. Adds new interactive and animated components, iconography, and visual effects.
Docs Global Styles and Theme
apps/docs/app/global.css, apps/docs/components/theme-toggle.tsx
Overhauls CSS variables, color palette, font and radius tokens, adds responsive and performance utility classes, and introduces new animations. Updates theme toggle logic and icon usage.
Docs Navbar and Footer
apps/docs/components/navbar.tsx, apps/docs/components/footer.tsx
Updates Navbar and Footer to use new logo/wordmark, adjusts layout, adds theme toggle, and refines navigation structure.
Docs UI Components: Card, Input, Accordion
apps/docs/components/ui/card.tsx, apps/docs/components/ui/input.tsx, apps/docs/components/ui/accordion.tsx
Adjusts border radius and styling for Card and Input; refactors Accordion with new icons, animated triggers, and improved accessibility.
Docs Next.js Config and Dependencies
apps/docs/next.config.mjs, apps/docs/package.json
Adds remote image pattern for DuckDuckGo icons, optimizes package imports, and introduces new dependencies for icons and animation.
API Query & Plugin Type Safety
apps/api/src/query/types.ts, apps/api/src/query/utils.ts, apps/api/src/query/screen-resolution-to-device-type.ts
Refines plugin config typing, adds explicit DataRow typing, helper functions for safe value extraction, and robust screen resolution/device type mapping.
Dashboard: Organizations, Websites, UI
apps/dashboard/hooks/use-organizations.ts, apps/dashboard/hooks/use-websites.ts, apps/dashboard/components/layout/organization-selector.tsx, apps/dashboard/components/layout/logo.tsx, apps/dashboard/components/analytics/data-table.tsx, apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx, apps/dashboard/app/(main)/organizations/[slug]/components/member-list.tsx, apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx, apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx, apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx, apps/dashboard/app/(main)/organizations/page.tsx, apps/dashboard/app/(main)/websites/_components/mini-chart.tsx, apps/dashboard/app/(main)/websites/_components/website-card.tsx, apps/dashboard/app/(main)/websites/page.tsx, apps/dashboard/app/(main)/websites/[id]/_components/tabs/tracking-setup-tab.ts, apps/dashboard/app/(main)/websites/[id]/profiles/_components/profile-row.tsx, apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx, apps/dashboard/app/demo/layout.tsx
Improves type safety and plugin config in hooks and utilities, enhances organization and website management UI with filtering, async actions, accessibility, and visual refinements. Updates chart and card components for responsive and accessible mini-charts and trend stats.
Database Schema and Metadata Removal
packages/db/src/drizzle/0000_equal_amphibian.sql, packages/db/src/drizzle/meta/0000_snapshot.json, packages/db/src/drizzle/meta/_journal.json
Deletes the full SQL schema definition, its JSON snapshot, and migration journal, removing all table, enum, constraint, and index metadata.
Database Schema and Relations Update
packages/db/src/drizzle/schema.ts, packages/db/src/drizzle/relations.ts
Adds new indexes and unique constraints, removes some foreign keys and indexes, and introduces a new one-to-one relation between API keys and organizations.
API Mini-Charts and Websites Routers
packages/rpc/src/routers/mini-charts.ts, packages/rpc/src/routers/websites.ts
Refactors mini-charts API to support configurable days, stable cache keys, and robust input handling. Updates website router with stricter permission checks, improved cache invalidation, and trend calculation logic.
Markdown Formatting
.cursor/rules/01-MUST-DO.mdc
Minor formatting and content cleanup in markdown rules file.

Sequence Diagram(s)

Pricing Estimator Flow

sequenceDiagram
  participant User
  participant PricingPage
  participant Estimator
  participant PlanNormalizer
  participant BestPlanSelector
  participant OverageCalculator

  User->>PricingPage: Loads page
  PricingPage->>PlanNormalizer: normalizePlans(rawPlans)
  PlanNormalizer-->>PricingPage: NormalizedPlan[]
  PricingPage->>BestPlanSelector: selectBestPlan(monthlyEvents, plans)
  BestPlanSelector-->>PricingPage: bestPlan
  PricingPage->>OverageCalculator: estimateTieredOverageCostFromTiers(events, bestPlan.eventTiers)
  OverageCalculator-->>PricingPage: estimatedOverage
  PricingPage->>Estimator: Render with props (events, bestPlan, estimatedOverage)
  User->>Estimator: Adjusts event count
  Estimator->>PricingPage: setMonthlyEvents(newValue)
  loop On event count change
    PricingPage->>BestPlanSelector: selectBestPlan(newEvents, plans)
    PricingPage->>OverageCalculator: estimateTieredOverageCostFromTiers(newEvents, bestPlan.eventTiers)
    PricingPage->>Estimator: Update props
  end
Loading

Mini-Charts API Query Flow

sequenceDiagram
  participant Client
  participant miniChartsRouter
  participant Auth
  participant DataFetcher
  Client->>miniChartsRouter: getMiniCharts({ websiteIds, days })
  miniChartsRouter->>miniChartsRouter: normalizeWebsiteIds(websiteIds)
  miniChartsRouter->>Auth: getAuthorizedWebsiteIds(user, normalizedIds)
  Auth-->>miniChartsRouter: authorizedIds
  miniChartsRouter->>DataFetcher: getBatchedMiniChartData(authorizedIds, days)
  DataFetcher-->>miniChartsRouter: chartData[]
  miniChartsRouter-->>Client: chartData[]
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Fix type issues in organizations. #51: The main PR and the retrieved PR both focus on improving type safety and explicit typing in the organizations-related React components and hooks within the dashboard app, specifically refining props and type exports for organization, member, and invitation entities; thus, their changes are directly related at the code level.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR 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 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 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 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.
  • 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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 9, 2025

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
  • ⚠️ 2 packages with OpenSSF Scorecard issues.
See the Details below.

License Issues

apps/docs/package.json

PackageVersionLicenseIssue Type
react-icons^5.5.0NullUnknown License

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@phosphor-icons/react ^2.1.10 UnknownUnknown
npm/motion ^12.23.12 UnknownUnknown
npm/react-icons ^5.5.0 ⚠️ 2.7
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Code-Review⚠️ 2Found 4/15 approved changesets -- score normalized to 2
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Security-Policy⚠️ 0security policy file not detected
License🟢 9license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Vulnerabilities⚠️ 032 existing vulnerabilities detected
npm/use-sound ^5.0.0 ⚠️ 1.9
Details
CheckScoreReason
Code-Review⚠️ 1Found 5/30 approved changesets -- score normalized to 1
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow⚠️ -1no workflows found
Maintained⚠️ 01 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Token-Permissions⚠️ -1No tokens found
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Pinned-Dependencies⚠️ -1no dependencies found
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Vulnerabilities⚠️ 095 existing vulnerabilities detected

Scanned Files

  • apps/docs/package.json

if (href.includes('github')) {
return <FaGithub className="h-4 w-4" color="black" />;
}
if (href.includes('x.com') || item.title.toLowerCase().startsWith('x(')) {

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High documentation

'
x.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 7 months ago

To fix the problem, we should parse the URL and check the host component to see if it matches 'x.com' or 'www.x.com'. This avoids false positives from substring matches in other parts of the URL or in unrelated domains. The best way to do this in a React/TypeScript context is to use the built-in URL class, which is available in modern browsers and Node.js. We should update the check in getIconForLink to parse the URL (if possible) and compare the host directly. If parsing fails (e.g., for non-HTTP URLs), we can fall back to the original logic or handle gracefully.

Required changes:

  • In getIconForLink, replace the href.includes('x.com') check with a check that parses the URL and compares the host to 'x.com' or 'www.x.com'.
  • No new imports are needed, as URL is globally available in the browser and Node.js.
  • No changes to other files or logic.

Suggested changeset 1
apps/docs/components/landing/footer-nav.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/docs/components/landing/footer-nav.tsx b/apps/docs/components/landing/footer-nav.tsx
--- a/apps/docs/components/landing/footer-nav.tsx
+++ b/apps/docs/components/landing/footer-nav.tsx
@@ -18,7 +18,15 @@
 	if (href.includes('github')) {
 		return <FaGithub className="h-4 w-4" color="black" />;
 	}
-	if (href.includes('x.com') || item.title.toLowerCase().startsWith('x(')) {
+	let isXCom = false;
+	try {
+		const urlObj = new URL(item.link, 'http://dummy-base/');
+		isXCom = urlObj.host === 'x.com' || urlObj.host === 'www.x.com';
+	} catch (e) {
+		// If parsing fails, fallback to original substring check (for mailto, etc.)
+		isXCom = href.includes('x.com');
+	}
+	if (isXCom || item.title.toLowerCase().startsWith('x(')) {
 		return <FaXTwitter className="h-4 w-4" color="black" />;
 	}
 	return null;
EOF
@@ -18,7 +18,15 @@
if (href.includes('github')) {
return <FaGithub className="h-4 w-4" color="black" />;
}
if (href.includes('x.com') || item.title.toLowerCase().startsWith('x(')) {
let isXCom = false;
try {
const urlObj = new URL(item.link, 'http://dummy-base/');
isXCom = urlObj.host === 'x.com' || urlObj.host === 'www.x.com';
} catch (e) {
// If parsing fails, fallback to original substring check (for mailto, etc.)
isXCom = href.includes('x.com');
}
if (isXCom || item.title.toLowerCase().startsWith('x(')) {
return <FaXTwitter className="h-4 w-4" color="black" />;
}
return null;
Copilot is powered by AI and may make mistakes. Always verify output.
{testimonial.profession}
</p>
}) {
const socialIcon = testimonial.link?.includes('x.com') ? (

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High documentation test

'
x.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 7 months ago

The best way to fix this problem is to parse the URL and check the host component explicitly, rather than using a substring match. In this case, we want to show the XLogoIcon only if the host is exactly x.com (or possibly www.x.com, if desired). To do this, we should use the standard URL constructor to parse the link, and then check if the host property matches x.com or any allowed value. This change should be made in the TestimonialCardContent function, specifically where the icon is conditionally rendered. We will need to handle cases where the URL is invalid or missing gracefully. No new dependencies are needed, as the URL class is available in modern JavaScript/TypeScript environments.

Suggested changeset 1
apps/docs/components/landing/testimonials.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/docs/components/landing/testimonials.tsx b/apps/docs/components/landing/testimonials.tsx
--- a/apps/docs/components/landing/testimonials.tsx
+++ b/apps/docs/components/landing/testimonials.tsx
@@ -101,14 +101,25 @@
 }: {
 	testimonial: (typeof testimonials)[number];
 }): ReactElement {
-	const socialIcon = testimonial.link?.includes('x.com') ? (
-		<span
-			aria-hidden
-			className="text-muted-foreground transition-colors duration-300 group-hover:text-foreground"
-		>
-			<XLogoIcon className="h-5 w-5" weight="duotone" />
-		</span>
-	) : null;
+	let socialIcon = null;
+	if (testimonial.link) {
+		try {
+			const urlObj = new URL(testimonial.link);
+			// Only show icon if host is exactly 'x.com' or 'www.x.com'
+			if (urlObj.host === 'x.com' || urlObj.host === 'www.x.com') {
+				socialIcon = (
+					<span
+						aria-hidden
+						className="text-muted-foreground transition-colors duration-300 group-hover:text-foreground"
+					>
+						<XLogoIcon className="h-5 w-5" weight="duotone" />
+					</span>
+				);
+			}
+		} catch (e) {
+			// Invalid URL, do nothing
+		}
+	}
 
 	return (
 		<div className="group relative flex h-[200px] w-[300px] shrink-0 flex-col justify-between rounded border border-border bg-card/70 shadow-inner backdrop-blur-sm transition-all duration-300 hover:border-border/80 hover:shadow-primary/10 sm:h-[220px] sm:w-[350px] lg:h-[240px] lg:w-[400px]">
EOF
@@ -101,14 +101,25 @@
}: {
testimonial: (typeof testimonials)[number];
}): ReactElement {
const socialIcon = testimonial.link?.includes('x.com') ? (
<span
aria-hidden
className="text-muted-foreground transition-colors duration-300 group-hover:text-foreground"
>
<XLogoIcon className="h-5 w-5" weight="duotone" />
</span>
) : null;
let socialIcon = null;
if (testimonial.link) {
try {
const urlObj = new URL(testimonial.link);
// Only show icon if host is exactly 'x.com' or 'www.x.com'
if (urlObj.host === 'x.com' || urlObj.host === 'www.x.com') {
socialIcon = (
<span
aria-hidden
className="text-muted-foreground transition-colors duration-300 group-hover:text-foreground"
>
<XLogoIcon className="h-5 w-5" weight="duotone" />
</span>
);
}
} catch (e) {
// Invalid URL, do nothing
}
}

return (
<div className="group relative flex h-[200px] w-[300px] shrink-0 flex-col justify-between rounded border border-border bg-card/70 shadow-inner backdrop-blur-sm transition-all duration-300 hover:border-border/80 hover:shadow-primary/10 sm:h-[220px] sm:w-[350px] lg:h-[240px] lg:w-[400px]">
Copilot is powered by AI and may make mistakes. Always verify output.
@vercel vercel bot temporarily deployed to staging – dashboard August 9, 2025 10:15 Inactive
@izadoesdev izadoesdev merged commit 78dc208 into main Aug 9, 2025
5 of 6 checks passed
Copy link
Contributor

@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: 76

🔭 Outside diff range comments (23)
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx (1)

207-227: Remove commented-out Custom events block

Dead commented JSX adds noise; delete if not needed or guard with a flag.

apps/dashboard/components/analytics/data-table.tsx (5)

1193-1211: Use rounded instead of rounded-2xl on modal container

Standardize border radius per repo rules.

- <div className="relative flex h-[92vh] w-[92vw] scale-100 animate-scalein flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl">
+ <div className="relative flex h-[92vh] w-[92vw] scale-100 animate-scalein flex-col overflow-hidden rounded border border-border bg-background shadow-2xl">

13-19: Replace lucide icons with Phosphor icons (repo rule)

This file imports icons from lucide-react; the project mandates using @phosphor-icons/react. Replace ArrowDown, ArrowUp, ArrowUpDown, DatabaseIcon, Search, X with their Phosphor counterparts (e.g., ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, DatabaseIcon, MagnifyingGlassIcon, XIcon) and prefer weight="fill" for arrows, weight="duotone" otherwise.


873-878: Remove incorrect role="tablist" on table container

The table container div isn’t a tablist; the actual tablist is rendered above. Drop the role to avoid a11y confusion.

- <div
-   className={cn(
-     'relative transition-all duration-300 ease-out',
-     isTransitioning && 'scale-[0.98] opacity-40'
-   )}
-   onMouseLeave={handleMouseLeave}
-   onMouseMove={handleMouseMove}
-   ref={tableContainerRef}
-   role="tablist"
- >
+ <div
+   className={cn(
+     'relative transition-all duration-300 ease-out',
+     isTransitioning && 'scale-[0.98] opacity-40'
+   )}
+   onMouseLeave={handleMouseLeave}
+   onMouseMove={handleMouseMove}
+   ref={tableContainerRef}
+ >

390-406: Make sortable headers buttons for accessibility

Avoid adding role="button" and tabIndex to th elements. Wrap header content in a button, keep aria-sort on th. This improves semantics and keyboard focus.

Also applies to: 941-955


123-152: Border radius consistency in Skeletons

Standardize rounded-md/rounded-lg/rounded-sm/full usages to rounded to comply with styling rules.

apps/dashboard/public/databuddy.ts (1)

1347-1350: Remove console.log statements in production bundle.

Unconditional console output breaches the “Don’t use console.” rule and can leak implementation details.
Drop these logs or gate them behind a debug flag.

Also applies to: 1363-1365

apps/dashboard/components/layout/logo.tsx (1)

30-38: Add a <title> for accessibility.

Inline SVG should include a <title> element so screen-reader users hear “Databuddy logo” (or similar).

<svg
   aria-hidden="true"
+  role="img">
+  <title>Databuddy logo</title>
apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx (1)

18-23: Use rounded not rounded-lg

Project style-guide mandates the generic rounded class exclusively. Replace rounded-lg to keep radius consistent across the UI.

-				'flex w-full items-center gap-3 rounded-lg border p-3 text-left transition-all duration-200',
+				'flex w-full items-center gap-3 rounded border p-3 text-left transition-all duration-200',
apps/dashboard/app/demo/layout.tsx (1)

151-158: Interactive backdrop is hidden from assistive tech

aria-hidden="true" combined with click / key handlers makes this element inaccessible. Either:

  1. Remove aria-hidden and add role="button" tabIndex={0} (plus appropriate aria-label), or
  2. Replace with an actual <button> element.
-				<div
-					aria-hidden="true"
-					className="fixed inset-0 z-30 bg-black/20 md:hidden"
-					onClick={closeSidebar}
-					onKeyDown={closeSidebar}
-					onKeyPress={closeSidebar}
-					onKeyUp={closeSidebar}
-				/>
+				<div
+					role="button"
+					aria-label="Close sidebar"
+					tabIndex={0}
+					className="fixed inset-0 z-30 bg-black/20 md:hidden"
+					onClick={closeSidebar}
+					onKeyDown={closeSidebar}
+					onKeyPress={closeSidebar}
+					onKeyUp={closeSidebar}
+				/>
apps/docs/package.json (1)

44-44: Replace date-fns with Dayjs.

Guideline: “Use Dayjs NEVER date-fns.” Remove date-fns and migrate to dayjs for date utilities.

I can help generate a codemod/checklist to migrate the few helpers typically used in docs (format, parseISO, addDays, etc.).

apps/dashboard/app/(main)/organizations/[slug]/components/member-list.tsx (1)

45-52: Type mismatch: onRemoveMember is awaited but typed as sync.

handleRemove uses await onRemoveMember(...). Update the prop to return Promise<void> or drop await.

Suggested fix:

 interface MemberListProps {
   members: OrganizationMember[];
-  onRemoveMember: (memberId: string) => void;
+  onRemoveMember: (memberId: string) => Promise<void>;
   isRemovingMember: boolean;
   onUpdateRole: (member: UpdateMemberData) => void;
   isUpdatingMember: boolean;
   organizationId: string;
 }
apps/docs/components/footer.tsx (1)

137-143: Don’t use in Next.js; use next/image and whitelist domain.

Replace <img> with next/image for optimization and policy compliance. Also, add twelve.tools to images.remotePatterns (see next.config comment).

Apply:

+import Image from 'next/image';
@@
-  <img
-    alt="Featured on Twelve Tools"
-    className="h-auto max-w-[150px] sm:max-w-[200px]"
-    height="40"
-    src="https://twelve.tools/badge0-white.svg"
-    width="150"
-  />
+  <Image
+    alt="Featured on Twelve Tools"
+    src="https://twelve.tools/badge0-white.svg"
+    width={150}
+    height={40}
+    className="h-auto max-w-[150px] sm:max-w-[200px]"
+    priority
+  />
apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx (2)

192-198: Arrow icons should use weight="fill" per icon guideline.

Guideline: “For arrows use fill.” Apply to this overlay indicator.

-<ArrowRightIcon
+<ArrowRightIcon
+  weight="fill"
   className={`not-dark:text-primary transition-transform duration-1000 ${
     transferringWebsite.fromSide === 'organization' ? 'rotate-180' : ''
   }`}
   size={18}
/>

Also apply the same to the button arrow (Line 244).


103-105: Normalize border-radius classes to rounded (policy).

Guideline: “Always use rounded, not rounded-xl/md/full.” Replace rounded-lg and rounded-full throughout this component.

Example:

- className="flex items-center gap-3 rounded-lg border ..."
+ className="flex items-center gap-3 rounded border ..."
@@
- className="h-10 w-10 rounded-lg border-2 shadow-sm"
+ className="h-10 w-10 rounded border-2 shadow-sm"
@@
- className="flex items-center gap-2 rounded-full bg-primary/20 px-4 py-2 ..."
+ className="flex items-center gap-2 rounded bg-primary/20 px-4 py-2 ..."

Also applies to: 118-119, 166-171, 190-193, 235-236, 241-251, 279-309

apps/docs/components/navbar.tsx (1)

39-53: SVG is accessible; optionally add aria-hidden and aria-label on link

The <title> is good. For cleaner AT behavior, either:

  • Add aria-label to the enclosing NavLink, or
  • Mark the SVG aria-hidden="true" and keep visible text “GitHub”.
-<NavLink external href="https://github.com/databuddy-analytics">
+<NavLink external aria-label="Databuddy GitHub" href="https://github.com/databuddy-analytics">
   <svg
+    aria-hidden="true"
     className="transition-transform duration-200 hover:scale-110"
     height="1.4em"
     viewBox="0 0 496 512"
     width="1.4em"
     xmlns="http://www.w3.org/2000/svg"
   >
apps/dashboard/hooks/use-websites.ts (2)

19-23: Disable the query until an organisation ID is available.

enabled: !isLoadingOrganization still triggers the query with organizationId: undefined, causing an unnecessary round-trip. Gate on the id instead:

-  { enabled: !isLoadingOrganization }
+  { enabled: !!activeOrganization?.id && !isLoadingOrganization }

64-65: Replace console.error with the project’s logger (or remove in production).

The coding standards list “Don’t use console.”. Swap to a structured logger or wrap behind a debug flag to keep production output clean.

Also applies to: 92-94

apps/dashboard/app/(main)/websites/page.tsx (1)

60-70: Rounded-size classes violate the style guide

The design guide mandates using only rounded, never rounded-lg, rounded-xl, or rounded-full, yet these appear in the highlighted lines.
Replace with the plain rounded class (and adjust spacing if bigger radii are desired through custom CSS tokens).

Also applies to: 102-105, 170-177

apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx (1)

208-209: Replace all rounded-lg with rounded

The style guide forbids radius variants; only rounded is allowed.

-className="flex items-center gap-3 rounded-lg border
+className="flex items-center gap-3 rounded border

Apply the same change to the card rows (Lines 223-225) and the “Delete Organization” button (Line 289).

Also applies to: 223-225, 289-289

apps/dashboard/app/(main)/organizations/page.tsx (1)

275-277: Border-radius variant violates style guideline

rounded-none is another radius variant that conflicts with the “always use rounded” rule.
Use plain rounded (or omit the class if square corners are required and get design sign-off).

Also applies to: 285-287

apps/dashboard/app/(main)/websites/_components/mini-chart.tsx (1)

30-93: Chart refinements are solid: accessibility, unique gradient IDs, animations, and rounded corners

  • aria-label + <title> improve accessibility.
  • “rounded” class complies with UI guideline.
  • Unique gradient ID avoids collisions.
  • Tooltip content is concise and uses Dayjs.

Optional: If your data never goes negative, consider clamping Y-axis min to 0 to avoid tiny negative padding when dataMin is close to 0.

apps/docs/components/landing/testimonials.tsx (1)

196-208: Cache duplicated testimonials with useMemo

new Array(15).fill(rowTestimonials).flat() is rebuilt on every render, creating ~15 × N new references each time the parent re-renders. Wrapping this computation in useMemo tied to rowTestimonials.length (or simply the array itself) removes needless allocations and keeps keys stable.

Comment on lines 1 to 5
---
description: Basic guidelines for the project so vibe coders don't fuck it up
globs:
globs:
alwaysApply: true
---
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Tone nit: make the description professional

Consider rephrasing the description to avoid profanity; this is a shared policy doc.

🤖 Prompt for AI Agents
In .cursor/rules/01-MUST-DO.mdc at lines 1 to 5, the description contains
profanity which is unprofessional for a shared policy document. Rephrase the
description to maintain a professional tone by removing any offensive language
and using neutral, respectful wording that clearly conveys the guidelines.

Comment on lines +3 to 5
globs:
alwaysApply: true
---
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm policy consistency: Zod v4 rule

The broader guidelines still reference “Use ONLY Zod V4 from zod/v4”. If this file intentionally removed or no longer includes that rule, confirm project-wide alignment to avoid mixed enforcement.

You can verify current Zod usage across the repo:


🏁 Script executed:

#!/bin/bash
# Find any imports from legacy zod entrypoint (should be avoided)
rg -n "from ['\"]zod['\"]"
# Find desired zod/v4 imports (should be preferred)
rg -n "from ['\"]zod/v4['\"]"

Length of output: 1930


Enforce Zod v4 consistently: restore rule and migrate legacy imports

The rule file .cursor/rules/01-MUST-DO.mdc (lines 3–5) no longer includes the “Use ONLY Zod V4 from zod/v4” policy, yet the repo is mixing legacy import … from 'zod' (v3) and import … from 'zod/v4'. To avoid uneven enforcement:

• Pinpoint the missing rule in .cursor/rules/01-MUST-DO.mdc (it currently only contains globs:/alwaysApply: true).
• Legacy imports found in 17 files, for example:
– packages/validation/src/schemas/analytics.ts
– packages/rpc/src/routers/websites.ts
– apps/dashboard/components/website-dialog.tsx
– …and many more
• Preferred imports already in place in some files (e.g., packages/validation/src/schemas/websites.ts, packages/rpc/src/routers/mini-charts.ts).

Action required:

  1. Re-add the Zod v4 enforcement rule to .cursor/rules/01-MUST-DO.mdc.
  2. Migrate all import … from 'zod' to import … from 'zod/v4'.
🤖 Prompt for AI Agents
In .cursor/rules/01-MUST-DO.mdc around lines 3 to 5, the enforcement rule
requiring use of only Zod v4 imports is missing. Restore this rule by adding a
policy that enforces importing exclusively from 'zod/v4'. Then, update all
source files that currently import from 'zod' to instead import from 'zod/v4' to
ensure consistent usage of Zod v4 across the codebase.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Duplicate guidance with coding guidelines

“Almost NEVER use useEffect unless it's critical” already exists in the shared rules. Consider referencing the canonical rule to prevent drift.

🤖 Prompt for AI Agents
In .cursor/rules/01-MUST-DO.mdc at line 47, the guidance "Almost NEVER use
useEffect unless it's critical" is duplicated from the shared rules. Remove this
duplicate line and instead add a reference or link to the canonical rule in the
shared rules to avoid redundancy and ensure consistency.

Comment on lines +41 to 44
normalizeGeo?: boolean;
deduplicateGeo?: boolean;
mapDeviceTypes?: boolean;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Good tightening of plugin typing; consider extracting a shared PluginFlags type for reuse

Explicit flags improve safety vs index signatures. To keep it DRY across API and utils, extract the shape to a named exported type and reference it here.

Example:

+export type PluginFlags = {
+  parseReferrers?: boolean;
+  normalizeUrls?: boolean;
+  normalizeGeo?: boolean;
+  deduplicateGeo?: boolean;
+  mapDeviceTypes?: boolean;
+};
 
 export interface SimpleQueryConfig {
   …
-  plugins?: {
-    parseReferrers?: boolean;
-    normalizeUrls?: boolean;
-    normalizeGeo?: boolean;
-    deduplicateGeo?: boolean;
-    mapDeviceTypes?: boolean;
-  };
+  plugins?: PluginFlags;
   …
 }
📝 Committable suggestion

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

Suggested change
normalizeGeo?: boolean;
deduplicateGeo?: boolean;
mapDeviceTypes?: boolean;
};
// extract and export the shared plugin‐flags shape
export type PluginFlags = {
parseReferrers?: boolean;
normalizeUrls?: boolean;
normalizeGeo?: boolean;
deduplicateGeo?: boolean;
mapDeviceTypes?: boolean;
};
export interface SimpleQueryConfig {
// … other config properties …
// replace the inline plugin object with the shared PluginFlags type
plugins?: PluginFlags;
// … remaining properties …
}
🤖 Prompt for AI Agents
In apps/api/src/query/types.ts around lines 41 to 44, the explicit boolean flags
for plugin options should be extracted into a shared named exported type called
PluginFlags. Define this type separately with the same flag properties and then
reference it here to keep the code DRY and reusable across API and utils.

Comment on lines +79 to 92
const confirmDeleteAction = async () => {
if (!confirmDelete) {
return;
}

setDeletingId(organizationId);
setDeletingId(confirmDelete.id);
try {
deleteOrganization(organizationId);
} catch (error) {
console.error('Failed to delete organization:', error);
await deleteOrganizationAsync(confirmDelete.id);
} catch (_error) {
toast.error('Failed to delete organization');
} finally {
setDeletingId(null);
setConfirmDelete(null);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Disable dialog’s Delete button while mutation is in-flight.

confirmDeleteAction kicks off deleteOrganizationAsync, but the “Delete” button in the dialog isn’t disabled during the request. Rapid double-clicks can fire multiple deletions. Gate the action with a loading/disabled state tied to isDeletingOrganization or a local isSubmitting.

🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx
around lines 79 to 92, the Delete button in the confirmation dialog is not
disabled while the deleteOrganizationAsync mutation is in progress, allowing
multiple rapid clicks to trigger duplicate deletions. Introduce a local loading
state variable (e.g., isSubmitting) that is set to true before calling
deleteOrganizationAsync and reset to false in the finally block. Use this state
to disable the Delete button during the mutation to prevent multiple
submissions.

Comment on lines +85 to +89
optimizePackageImports: [
'fumadocs-ui',
'lucide-react',
'@phosphor-icons/react',
],
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Remove lucide optimizations; phosphor-only policy.

Guideline: “Don’t use lucide for icons, ONLY use phosphor icons.” Drop lucide-react from optimizePackageImports.

Apply:

 experimental: {
-  optimizePackageImports: [
-    'fumadocs-ui',
-    'lucide-react',
-    '@phosphor-icons/react',
-  ],
+  optimizePackageImports: ['fumadocs-ui', '@phosphor-icons/react'],
 },

To find and remove stray lucide usages:


🏁 Script executed:

#!/bin/bash
rg -n "from 'lucide-react" apps

Length of output: 9331


Enforce phosphor-only icon policy

We’ve confirmed lucide-react is still imported in dozens of files across the monorepo. Please:

  • Remove lucide-react from optimizePackageImports in apps/docs/next.config.mjs.
  • Replace every import … from 'lucide-react' with import … from '@phosphor-icons/react'.
  • Use the following to locate all remaining Lucide imports:
    rg -n "from 'lucide-react'" apps

Apply this diff in apps/docs/next.config.mjs:

 experimental: {
-  optimizePackageImports: [
-    'fumadocs-ui',
-    'lucide-react',
-    '@phosphor-icons/react',
-  ],
+  optimizePackageImports: ['fumadocs-ui', '@phosphor-icons/react'],
 },
🤖 Prompt for AI Agents
In apps/docs/next.config.mjs around lines 85 to 89, remove 'lucide-react' from
the optimizePackageImports array to enforce the phosphor-only icon policy. Then,
search the entire apps directory for all import statements importing from
'lucide-react' and replace them with imports from '@phosphor-icons/react' to
ensure consistency across the monorepo.

"react-day-picker": "^9.7.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Remove react-icons; switch to Phosphor-only per codebase policy.

Guideline: “Don’t use lucide for icons, ONLY use phosphor icons.” Using react-icons violates this. Migrate usages (e.g., footer) to @phosphor-icons/react and drop the dependency.

Apply this diff to remove the dependency:

   "dependencies": {
@@
-    "react-icons": "^5.5.0",
+    // Use only @phosphor-icons/react across the app

Run this to find usages to migrate:


🏁 Script executed:

#!/bin/bash
rg -n "from 'react-icons" apps

Length of output: 603


Remove react-icons and migrate to @phosphor-icons/react

The docs site currently imports icons from react-icons in multiple places. Per our policy (“Don’t use lucide for icons, ONLY use phosphor icons”), please:

• Remove the react-icons dependency from apps/docs/package.json
• Add or verify @phosphor-icons/react in dependencies
• Update all imports to use Phosphor React icons

Locations to update:

  • apps/docs/components/footer.tsx (lines 1–2)
  • apps/docs/app/layout.config.tsx (lines 2–3)
  • apps/docs/components/landing/footer-nav.tsx (lines 3–4)

Example migration in footer.tsx:

-import { FaDiscord, FaGithub, FaXTwitter } from 'react-icons/fa6';
-import { IoMdMail }              from 'react-icons/io';
+import {
+  DiscordLogoIcon,
+  GithubLogoIcon,
+  XLogoIcon,
+  EnvelopeSimpleIcon
+} from '@phosphor-icons/react';

In package.json:

 "dependencies": {
-  "react-icons": "^5.5.0",
+  "@phosphor-icons/react": "^2.0.0",  // use latest Phosphor React package

Verify no remaining react-icons imports:

rg -n "from 'react-icons" apps/docs
🤖 Prompt for AI Agents
In apps/docs/package.json at line 61, remove the "react-icons" dependency and
ensure "@phosphor-icons/react" is added to the dependencies section. Then,
update all imports of icons from "react-icons" to use "@phosphor-icons/react" in
the specified files: apps/docs/components/footer.tsx (lines 1-2),
apps/docs/app/layout.config.tsx (lines 2-3), and
apps/docs/components/landing/footer-nav.tsx (lines 3-4). Finally, verify no
remaining imports from "react-icons" exist by running a search command like `rg
-n "from 'react-icons" apps/docs`.

"recharts": "^2.15.3",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"use-sound": "^5.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify necessity of use-sound.

If it isn’t used in apps/docs, avoid adding it to keep the dependency surface minimal.

Run:


🏁 Script executed:

#!/bin/bash
rg -n "use-sound" apps/docs

Length of output: 80


🏁 Script executed:

#!/bin/bash
set -e
echo "Checking static imports of use-sound..."
rg -n "from ['\"]use-sound['\"]" apps/docs
rg -n "require(['\"]use-sound['\"])" apps/docs

echo "Checking hook invocations..."
rg -n "useSound\(" apps/docs

Length of output: 146


Remove unused use-sound dependency

Our searches did not find any imports or hook calls for use-sound in apps/docs. Please remove it to keep the dependency surface minimal.

Locations to update:

  • apps/docs/package.json: remove the following line
-    "use-sound": "^5.0.0",
📝 Committable suggestion

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

Suggested change
"use-sound": "^5.0.0",
🤖 Prompt for AI Agents
In apps/docs/package.json at line 66, remove the "use-sound": "^5.0.0"
dependency line entirely to eliminate the unused package and reduce the
dependency surface.

"tailwind-merge": "^3.3.0",
"use-sound": "^5.0.0",
"vaul": "^1.1.2",
"zod": "3.25.76"
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Upgrade to Zod v4 and import from zod/v4.

Guideline: “Use ONLY Zod V4 from zod/v4; never zod 3.” You currently pin zod@3.x. Plan a migration to v4 and update all imports.

I can script an impact assessment (files importing zod) if useful.

🤖 Prompt for AI Agents
In apps/docs/package.json at line 68, the project currently uses zod version
3.25.76, but the guideline requires upgrading to zod version 4 and importing
from 'zod/v4'. To fix this, update the zod dependency version to the latest v4
release in package.json, then refactor all code files importing zod to import
from 'zod/v4' instead of 'zod'. Plan and execute a migration to ensure
compatibility with zod v4 APIs.

Comment on lines +144 to +147
organization: one(organization, {
fields: [apikey.organizationId],
references: [organization.id],
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Relation added – consider adding the reverse mapping

The one-to-one link from apikey to organization is correct, but organizationRelations currently lacks a corresponding apikeys: many(apikey) entry. Adding it will simplify querying all keys for an organisation and keeps relations symmetrical.

🤖 Prompt for AI Agents
In packages/db/src/drizzle/relations.ts around lines 144 to 147, you have
defined a one-to-one relation from apikey to organization but missed adding the
reverse relation in organizationRelations. To fix this, add a many-to-one
relation in organizationRelations like apikeys: many(apikey) to enable querying
all API keys for an organization and maintain symmetrical relations.

Copy link
Contributor

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

Review continued from previous batch...

Comment on lines +253 to +278
function applyUrlNormalization(data: DataRow[]): DataRow[] {
return data.map((row) => {
const original = getString(row.name);
if (!original) {
return row;
}
let normalized = original;
try {
if (
normalized.startsWith('http://') ||
normalized.startsWith('https://')
) {
const url = new URL(normalized);
normalized = url.pathname || '/';
}
if (!normalized.startsWith('/')) {
normalized = `/${normalized}`;
}
if (normalized.length > 1 && normalized.endsWith('/')) {
normalized = normalized.slice(0, -1);
}
return { ...row, name: normalized } as DataRow;
} catch {
return row;
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

applyUrlNormalization misses hash/query stripping

The normaliser converts absolute URLs to pathname, ensures leading/trailing slashes, but leaves #hash and ?query fragments when a plain path string is supplied (e.g. "about#team").
Consider removing location.hash and location.search portions for consistency with the behaviour when a full URL is passed.

🤖 Prompt for AI Agents
In apps/api/src/query/utils.ts around lines 253 to 278, the
applyUrlNormalization function does not remove hash (#) and query (?) fragments
from plain path strings, causing inconsistency with full URL normalization.
Update the function to strip any hash and query parts from the normalized string
after handling leading/trailing slashes, ensuring the returned path is clean and
consistent regardless of input format.

Comment on lines +21 to 27
if (num >= 1_000_000) {
return `${(num / 1_000_000).toFixed(1)}M`;
}
if (num >= 1000) {
return `${(num / 1000).toFixed(1)}K`;
}
return num.toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

formatNumber: uses toFixed with digits; consider trimming trailing .0

Current output can be “1.0K/1.0M”. Optional polish: strip trailing “.0” to display “1K/1M”.

Apply this localized refactor:

-  if (num >= 1_000_000) {
-    return `${(num / 1_000_000).toFixed(1)}M`;
-  }
-  if (num >= 1000) {
-    return `${(num / 1000).toFixed(1)}K`;
-  }
+  if (num >= 1_000_000) {
+    const s = (num / 1_000_000).toFixed(1);
+    return `${s.endsWith('.0') ? s.slice(0, -2) : s}M`;
+  }
+  if (num >= 1000) {
+    const s = (num / 1000).toFixed(1);
+    return `${s.endsWith('.0') ? s.slice(0, -2) : s}K`;
+  }
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/_components/mini-chart.tsx around lines 21
to 27, the formatNumber function uses toFixed(1) which results in outputs like
"1.0K" or "1.0M". To improve display, modify the code to trim trailing ".0" from
the formatted string so that numbers like 1,000 or 1,000,000 show as "1K" or
"1M" instead of "1.0K" or "1.0M". Implement this by checking if the formatted
string ends with ".0" and removing it before returning.

Comment on lines 162 to 173
<Suspense
fallback={<Skeleton className="h-12 w-full rounded" />}
fallback={
<Skeleton className="h-12 w-full rounded sm:h-16" />
}
>
<MiniChart data={chartData.data} id={website.id} />
<MiniChart
data={chartData.data}
days={chartData.data.length}
id={website.id}
/>
</Suspense>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add an error boundary around MiniChart render

Guidelines require proper error boundaries. If the chart fails to load/render, users should see a graceful fallback rather than a broken card.

I can provide a lightweight ErrorBoundary component (using react-error-boundary) wrapping the chart area; want me to add it?

🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/_components/website-card.tsx around lines
162 to 173, the MiniChart component is wrapped in Suspense but lacks an error
boundary. To handle potential rendering errors gracefully, wrap the MiniChart
inside an ErrorBoundary component from react-error-boundary, providing a
fallback UI for errors. If you don't have an ErrorBoundary component yet, create
a lightweight one using react-error-boundary and use it to wrap MiniChart
alongside Suspense.

🧹 Nitpick (assertive)

Avoid redundant Suspense: switch next/dynamic to suspense mode or remove wrapper

next/dynamic is configured with a loading component and ssr: false, but MiniChart is also wrapped in <Suspense>. Without suspense: true in dynamic(), the Suspense fallback won’t be used. Use one mechanism consistently.

Two options:

  • Prefer Suspense: set suspense: true and remove loading, keeping the <Suspense> fallback.
  • Or keep loading and remove the <Suspense> wrapper.

Example (prefer Suspense):

-const MiniChart = dynamic(
-  () => import('./mini-chart').then((mod) => mod.default),
-  {
-    loading: () => <Skeleton className="h-12 w-full rounded" />,
-    ssr: false,
-  }
-);
+const MiniChart = dynamic(() => import('./mini-chart').then((m) => m.default), {
+  ssr: false,
+  suspense: true,
+});
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/_components/website-card.tsx around lines
162 to 173, the MiniChart component is wrapped in a Suspense fallback but
next/dynamic is configured with loading and ssr: false without suspense: true,
causing the Suspense fallback to be ineffective. To fix this, either add
suspense: true to the dynamic import of MiniChart and remove the loading option
while keeping the Suspense wrapper, or remove the Suspense wrapper and keep the
loading option in dynamic. Choose one consistent approach to handle loading
states.

Comment on lines +36 to +56
function filterOrganizations<T extends { name: string; slug?: string | null }>(
orgs: T[] | undefined,
query: string
): T[] {
if (!orgs || orgs.length === 0) {
return [];
}
if (!query) {
return orgs;
}
const q = query.toLowerCase();
const filtered: T[] = [];
for (const org of orgs) {
const nameMatch = org.name.toLowerCase().includes(q);
const slugMatch = org.slug ? org.slug.toLowerCase().includes(q) : false;
if (nameMatch || slugMatch) {
filtered.push(org);
}
}
return filtered;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Leverage native Array.filter for clarity

filterOrganizations manually builds a new array in a for loop. The same result can be expressed more readably and with less code by using orgs.filter(...).
No behavioural change, only readability & maintainability.

🤖 Prompt for AI Agents
In apps/dashboard/components/layout/organization-selector.tsx around lines 36 to
56, the filterOrganizations function manually constructs a filtered array using
a for loop. Refactor this function to use the native Array.filter method
instead, applying the filtering condition inside the filter callback. This will
simplify the code, improve readability, and maintain the same behavior without
manually pushing elements.

Comment on lines +5 to +6
export type OrganizationRole = 'owner' | 'admin' | 'member';

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Derive OrganizationRole from the auth client to avoid drift

type OrganizationRole = 'owner' | 'admin' | 'member' is hard-coded in three places. If the server adds/renames a role, this union goes stale and compilation won’t warn you. Prefer:

export type OrganizationRole = keyof typeof authClient.config.roles;

(or export the enum/union from a shared package) so the source-of-truth is single and compile-time-safe.

Also applies to: 23-24, 29-31

🤖 Prompt for AI Agents
In apps/dashboard/hooks/use-organizations.ts around lines 5 to 6, the
OrganizationRole type is hard-coded as a union of string literals, which can
become outdated if roles change on the server. To fix this, replace the
hard-coded union with a type derived from the auth client's roles configuration,
such as using keyof typeof authClient.config.roles, ensuring the roles stay in
sync and are compile-time safe. Apply the same change to lines 23-24 and 29-31
where the roles are also hard-coded.

Comment on lines +35 to +59
const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);

useEffect(() => {
const trigger = triggerRef.current;
if (!trigger) {
return;
}

const observer = new MutationObserver(() => {
const dataState = trigger.getAttribute('data-state');
setIsOpen(dataState === 'open');
});

observer.observe(trigger, {
attributes: true,
attributeFilter: ['data-state'],
});

// Initial check
const dataState = trigger.getAttribute('data-state');
setIsOpen(dataState === 'open');

return () => observer.disconnect();
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

MutationObserver & extra state are over-engineered

Using a MutationObserver plus useEffect to track data-state adds unnecessary complexity and violates the project rule “Almost NEVER use useEffect unless critical”.

Radix already exposes the open state:

const open = props['data-state'] === 'open'

or, at a higher level, via the Accordion’s onValueChange.

Drop the observer and drive animations directly from data-state or component props – you’ll remove ~25 lines, one effect, and an extra state variable.

🤖 Prompt for AI Agents
In apps/docs/components/ui/accordion.tsx from lines 35 to 59, the current
implementation uses a MutationObserver and useEffect to track the 'data-state'
attribute and manage an extra isOpen state, which is unnecessarily complex.
Remove the MutationObserver, useEffect, and isOpen state entirely. Instead,
derive the open state directly from the 'data-state' prop or from the
Accordion's onValueChange callback, and use that to drive animations and UI
changes. This simplifies the code by eliminating about 25 lines and avoids using
useEffect unnecessarily.

Comment on lines +85 to +88
<PlusIcon
className="size-6 shrink-0 translate-y-0.5 cursor-pointer text-muted-foreground transition-transform duration-200 hover:text-accent-foreground"
strokeWidth={1.5}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Non-interactive icon styled as interactive

cursor-pointer suggests the PlusIcon is clickable, but all clicks are handled by the surrounding <button>.
Remove the cursor style to avoid confused hit-targets and redundant hover focus states.

🤖 Prompt for AI Agents
In apps/docs/components/ui/accordion.tsx around lines 85 to 88, the PlusIcon has
a cursor-pointer style that incorrectly suggests it is clickable, but the click
events are handled by the surrounding button. Remove the cursor-pointer class
from the PlusIcon's className to prevent misleading the user about the clickable
area and avoid redundant hover and focus states on the icon.

Comment on lines +22 to +23
const MIN_DAYS = 3;
const MAX_DAYS = 30;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Align minimum days with trend requirements

calculateTrend requires at least 4 points (data.length < 4 returns null). The router clamps days at MIN_DAYS = 3. Consider bumping MIN_DAYS to 4 so the smallest query can still produce a trend.

-const MIN_DAYS = 3;
+const MIN_DAYS = 4;

If you intentionally want trend=null for short ranges, keep as-is.

Also applies to: 80-84

🤖 Prompt for AI Agents
In packages/rpc/src/routers/mini-charts.ts around lines 22-23 and 80-84, the
MIN_DAYS constant is set to 3, but the calculateTrend function requires at least
4 data points to compute a trend. To fix this, update MIN_DAYS to 4 so that the
minimum days queried aligns with the trend calculation requirements, ensuring
the smallest query can produce a valid trend result.

Comment on lines +44 to 45
const authCacheKey = `auth:${userId}:${[...requestedIds].sort().join(',')}`;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use a hashed IDs segment for the auth cache key to avoid oversized Redis keys

Concatenating potentially thousands of IDs creates long keys. Hash for stability and size, similar to the main cache key.

- const authCacheKey = `auth:${userId}:${[...requestedIds].sort().join(',')}`;
+ const idsHash = createHash('sha1')
+   .update([...requestedIds].sort().join(','))
+   .digest('base64url')
+   .slice(0, 16);
+ const authCacheKey = `auth:${userId}:${idsHash}`;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/rpc/src/routers/mini-charts.ts around lines 44 to 45, the
authCacheKey is constructed by concatenating all requested IDs, which can create
excessively long Redis keys. To fix this, replace the concatenated IDs segment
with a hash of the sorted requested IDs array. Use a suitable hashing function
to generate a fixed-length string representing the IDs, ensuring the
authCacheKey remains stable and compact.

Comment on lines +161 to 174
queryFn: async () => {
if (input.organizationId) {
const { success } = await websitesApi.hasPermission({
headers: ctx.headers,
body: { permissions: { website: ['read'] } },
});
if (!success) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Missing organization permissions.',
});
}
}
const whereClause = buildWebsiteFilter(
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Extract repeated permission check into a helper

The hasPermission block is repeated in both list and listWithCharts.
Pulling it into a small utility (e.g. assertOrgPermission(ctx, headers, ['read'])) will reduce duplication and future-maintenance risk.

🤖 Prompt for AI Agents
In packages/rpc/src/routers/websites.ts around lines 161 to 174, the permission
check using websitesApi.hasPermission is duplicated in both list and
listWithCharts functions. To fix this, create a helper function named
assertOrgPermission that accepts context, headers, and required permissions,
performs the hasPermission check, and throws a TRPCError if permissions are
missing. Replace the duplicated permission check blocks in both functions with
calls to this new helper to reduce code duplication and improve maintainability.

@coderabbitai coderabbitai bot mentioned this pull request Dec 1, 2025
7 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 25, 2025
7 tasks
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.

2 participants