Skip to content

Ultracite#27

Merged
izadoesdev merged 17 commits into
databuddy-analytics:mainfrom
haydenbleasel:ultracite
Jul 25, 2025
Merged

Ultracite#27
izadoesdev merged 17 commits into
databuddy-analytics:mainfrom
haydenbleasel:ultracite

Conversation

@haydenbleasel

@haydenbleasel haydenbleasel commented Jul 25, 2025

Copy link
Copy Markdown
Contributor

This pull request introduces several improvements to the codebase, focusing on consistent formatting, enhanced configuration, and minor functional adjustments. The most significant changes include enforcing consistent double-quote usage across the project, updating editor settings for code formatting, and refining schema definitions for better clarity.

Configuration and Formatting Enhancements:

  • .vscode/settings.json: Added settings for default formatters, enabled formatOnSave and formatOnPaste, and configured biome for code actions on save.
  • apps/api/biome.json: Simplified the includes array in the files section for better readability.

Code Style Consistency:

  • Enforced double-quote usage across all imports and strings in various files for consistent formatting:
    • apps/api/src/agent/handlers/chart-handler.ts [1] [2] [3]
    • apps/api/src/agent/handlers/metric-handler.ts [1] [2] [3]
    • apps/api/src/agent/index.ts
    • apps/api/src/agent/processor.ts [1] [2] [3] [4]
    • apps/api/src/agent/prompts/agent.ts [1] [2] [3]
    • apps/api/src/agent/utils/ai-client.ts

Schema and Prompt Refinements:

  • apps/api/src/agent/prompts/agent.ts: Improved AIResponseJsonSchema and AIPlanSchema definitions for better clarity and alignment with coding standards.
  • Enhanced the comprehensiveUnifiedPrompt function to handle conversation history and agent tool results more cleanly.

These changes collectively improve the maintainability, readability, and consistency of the codebase.

Summary by CodeRabbit

  • Style

    • Standardized code formatting across the application, including consistent use of single quotes, improved indentation, and multiline formatting for readability in both frontend and backend files.
    • Reorganized and reordered import statements for clarity and consistency.
    • Reformatted JSX and TypeScript elements for improved code style without affecting functionality.
    • Updated TypeScript and VSCode configuration files for consistent formatting and tooling behavior.
  • Documentation

    • Updated code comments and removed inline comments in configuration files for a cleaner setup.
  • Chores

    • Removed unused configuration files and redundant ignore rules.
    • Added missing trailing newlines to package files for consistency.

No functional changes or new features were introduced. All updates are non-disruptive and focused on code quality and maintainability.

@vercel

vercel Bot commented Jul 25, 2025

Copy link
Copy Markdown

@haydenbleasel is attempting to deploy a commit to the Databuddy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 25, 2025

Copy link
Copy Markdown
Contributor
## Walkthrough

This update is a comprehensive, project-wide code style and formatting refactor. It standardizes string quotes to single quotes, normalizes indentation and spacing, and reorganizes import statements for consistency across all application folders. No functional, logic, or control flow changes are introduced; all modifications are purely stylistic, affecting TypeScript, JavaScript, and JSX/TSX files, as well as configuration and test files.

## Changes

| File(s) / Group                                                                                 | Change Summary                                                                                                       |
|-------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| .vscode/settings.json, apps/api/tsconfig.json, apps/basket/tsconfig.json                        | Editor and TypeScript config formatting and settings updates                                                         |
| apps/api/.gitignore, apps/api/package.json, apps/basket/package.json                            | Minor formatting: removed .eslintcache from .gitignore, added trailing newlines to package.json files                |
| apps/api/biome.json                                                                             | Deleted Biome configuration file                                                                                     |
| apps/api/src/agent/**/*, apps/api/src/query/**/*, apps/api/src/routes/**/*, apps/api/src/index.ts| Consistent formatting: single quotes, indentation, import reordering, trailing commas, code style normalization      |
| apps/api/src/types/tables.ts, apps/api/src/query/types.ts, apps/api/src/query/constants.ts       | Type and constant file formatting: single quotes, indentation, minor type annotation tweaks                          |
| apps/basket/src/**/*                                                                             | Single quotes, indentation, import reordering, object and array formatting, code style consistency                   |
| apps/dashboard/app/**/*                                                                          | Single quotes, import reordering, JSX/TSX multiline formatting, prop ordering, whitespace and style normalization    |
| apps/api/src/agent/index.ts, apps/dashboard/app/(main)/billing/components/overview-tab.tsx, ...  | Minor type annotation and export ordering updates                                                                    |
| apps/api/src/query/builders/*.ts, apps/api/src/query/utils.ts                                   | SQL builder and utility file formatting: string literal normalization, multiline arrays/objects, trailing commas     |

## Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

## Poem

> Hoppity hop, a code-style sweep,  
> Single quotes and tidy lines we keep.  
> Indents aligned, imports in a row,  
> The garden of code now neatly will grow.  
> No logic disturbed, just a cleaner view—  
> A rabbit’s delight: fresh code, brand new!  
> 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d26763c and 90c1423.

📒 Files selected for processing (1)
  • .vscode/settings.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .vscode/settings.json
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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.

@haydenbleasel
haydenbleasel marked this pull request as ready for review July 25, 2025 16:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 30

🔭 Outside diff range comments (11)
apps/dashboard/app/(main)/organizations/components/error-display.tsx (2)

27-31: Avoid leaking raw error messages to end-users

Rendering error.message verbatim may disclose stack traces, internal paths or PII coming from upstream APIs. Gate this behind a development check (or a dedicated prop) and fall back to a generic message in production.

-{error && (
-  <pre className="max-h-[150px] overflow-auto rounded bg-muted p-3 font-mono text-xs">
-    {error.message}
-  </pre>
-)}
+{error &&
+  process.env.NODE_ENV === 'development' && (
+    <pre className="max-h-[150px] overflow-auto rounded bg-muted p-3 font-mono text-xs">
+      {error.message}
+    </pre>
+  )}

32-36: Add accessibility attributes to the retry button & icon

The button is purely icon-driven; screen-reader users need an aria-label. Likewise, mark the SVG as decorative.

-<Button onClick={onRetry} size="sm" variant="outline">
-  <ArrowClockwise className="mr-2 h-4 w-4" />
+<Button
+  onClick={onRetry}
+  size="sm"
+  variant="outline"
+  aria-label="Retry loading organizations"
+>
+  <ArrowClockwise className="mr-2 h-4 w-4" aria-hidden="true" />
   Retry
 </Button>
apps/dashboard/app/(main)/revenue/_components/onboarding/configuration-summary.tsx (1)

47-53: Mask the token to avoid accidental exposure

Displaying the full webhookToken in the UI risks leaking secrets (e.g., during screen-sharing or screenshots). Show only a short prefix/suffix instead.

-              <p className="font-mono">{webhookToken || 'Not configured'}</p>
+              <p className="font-mono">
+                {webhookToken
+                  ? `${webhookToken.slice(0, 6)}…${webhookToken.slice(-4)}`
+                  : 'Not configured'}
+              </p>
apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx (1)

81-88: Avoid premature success toast – wait for onSave to resolve

onSave is invoked and the success toast is fired immediately afterwards.
If onSave is async and rejects (e.g. network/API error), the UI will still announce success.

-  onSave({
-    webhookSecret: localWebhookSecret,
-    isLiveMode: localIsLiveMode,
-  });
-  setHasUnsavedChanges(false);
-  toast.success('Settings saved successfully');
+  const result = await onSave({
+    webhookSecret: localWebhookSecret,
+    isLiveMode: localIsLiveMode,
+  });
+  if (result?.success) {
+    setHasUnsavedChanges(false);
+    toast.success('Settings saved successfully');
+  }

Alternatively, have onSave return a promise and handle the toast in the calling component.

apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx (1)

169-182: Keystroke-level API calls – debounce or defer save

onChange directly calls setWebhookSecret, which ultimately triggers createOrUpdateMutation.mutate.
This fires a network request on every keystroke of the secret field:

  1. Unnecessary server load.
  2. Risk of rate-limiting / throttling.
  3. Possible partial secrets stored if user abandons typing.

Consider buffering the input locally and persisting only on explicit save / blur:

-<Input
-
-  onChange={(e) => setWebhookSecret(e.target.value)}
-
-/>

+const [draftSecret, setDraftSecret] = useState('');
+
+<Input
+
+  onChange={(e) => setDraftSecret(e.target.value)}
+
+/>

+// within handleSave or a dedicated onBlur handler
+if (draftSecret.trim()) {
+  setWebhookSecret(draftSecret.trim());
+}

This keeps the UX snappy while dramatically reducing backend chatter.

apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts (1)

199-204: State setter name collides with updater – leads to implicit writes

setWebhookSecret immediately calls updateConfig, pushing every keystroke to the server.
Besides the performance issue flagged in the settings tab, the naming (setWebhookSecret) implies a local setter but has side-effects.

Rename to saveWebhookSecret (or similar) and expose a truly local setter for controlled inputs, or decouple persistence with debounce.

apps/dashboard/app/(auth)/register/page.tsx (1)

454-468: Add rel="noopener noreferrer" to external links opened with target="_blank"

Opening a new tab without these attributes enables the newly opened page to gain window.opener access and potentially manipulate the parent (tab-nabbing).

-          <Link
-            className="font-medium text-primary hover:text-primary/80"
-            href="https://www.databuddy.cc/terms"
-            target="_blank"
-          >
+          <Link
+            className="font-medium text-primary hover:text-primary/80"
+            href="https://www.databuddy.cc/terms"
+            target="_blank"
+            rel="noopener noreferrer"
+          >
@@
-          <Link
-            className="font-medium text-primary hover:text-primary/80"
-            href="https://www.databuddy.cc/privacy"
-            target="_blank"
-          >
+          <Link
+            className="font-medium text-primary hover:text-primary/80"
+            href="https://www.databuddy.cc/privacy"
+            target="_blank"
+            rel="noopener noreferrer"
+          >
apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx (1)

72-99: updateOrganization should be awaited to avoid race conditions
handleSave is not declared async, so updateOrganization (likely async) isn’t awaited. This means:

  1. toast.success is fired before the mutation actually resolves.
  2. catch will never trigger on request failures, as Promise rejections escape the try/catch.
  3. setIsSaving(false) executes immediately, removing the loading state too early.
-const handleSave = () => {
+const handleSave = async () => {-  setIsSaving(true);
-  try {
-    updateOrganization({
+  setIsSaving(true);
+  try {
+    await updateOrganization({
       organizationId: organization.id,
       data: {
         name: name.trim(),
         slug: slug.trim(),
       },
     });
     toast.success('Organization updated successfully');

This single change lets the loader, success toast and error handling reflect real network state.

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

60-69: Select should be controlled, not initialised with defaultValue
After a role change the component receives a new member.role, but the selector still shows the old value because defaultValue is only read once. Use the controlled value prop instead.

-    <Select
-      defaultValue={member.role}
+    <Select
+      value={member.role}

This keeps the UI in sync with server state and avoids user confusion.

apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)

60-77: Await the asynchronous deletion call

deleteOrganization is likely async. Without await, rejection errors bypass the surrounding try/catch, and finally clears the loading state too early.

-      deleteOrganization(organizationId);
+      await deleteOrganization(organizationId);
apps/basket/src/utils/ip-geo.ts (1)

90-97: IPv6 validation rejects perfectly valid addresses

isValidIp only accepts fully-expanded IPv6 strings. Short-hand forms such as 2001:db8:85a3::8a2e:370:7334 (which you already test for) or ::ffff:192.0.2.1 will be rejected, causing unnecessary “geo-location disabled” fall-backs.

A zero-maintenance fix is to delegate to Node’s built-in net.isIP, which handles both IPv4 & IPv6 (compressed or not) and avoids maintaining brittle regexes:

-import { createHash } from 'node:crypto';
+import { createHash } from 'node:crypto';
+import { isIP } from 'node:net';
 ...
-function isValidIp(ip: string): boolean {
-  if (!ip) return false;
-
-  // Check for IPv4 format
-  const ipv4Regex =
-    /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
-  if (ipv4Regex.test(ip)) return true;
-
-  // Check for IPv6 format (basic check)
-  const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
-  if (ipv6Regex.test(ip)) return true;
-
-  return false;
+function isValidIp(ip: string): boolean {
+  return isIP(ip) !== 0;
 }

This both simplifies the code and fixes the false-negative.

♻️ Duplicate comments (1)
apps/dashboard/app/(auth)/login/verification-needed/page.tsx (1)

71-78: Consistent cleanup pattern mirrors earlier comment
Same loading-state duplication noted in the magic-link page; applying the try/finally pattern here will keep state handling consistent.

🧹 Nitpick comments (68)
apps/dashboard/app/(main)/organizations/components/onboarding-card.tsx (3)

24-41: Consider memoising or hoisting the features array

features is recreated on every render.
While the cost is negligible here, extracting the constant outside the component or wrapping it in useMemo prevents unnecessary allocations and helps the component remain referentially stable if it ever becomes more complex.

-import {
+import {
   BuildingsIcon,
   ChartLineIcon,
   ShieldIcon,
   UsersIcon,
 } from '@phosphor-icons/react';
+import { useMemo } from 'react';
 ...
-  const features = [
+  const features = useMemo(
+    () => [
       { icon: UsersIcon, title: 'Team Collaboration', description: 'Invite team members and manage permissions' },
       { icon: ChartLineIcon, title: 'Shared Analytics', description: 'Share insights and reports across your team' },
       { icon: ShieldIcon, title: 'Role-Based Access', description: 'Control who can view and manage your data' },
-  ];
+    ],
+    [],
+  );

60-67: Add aria-hidden to decorative icons for better a11y

The SVGs are purely decorative; convey no additional information beyond the adjacent text.
Mark them as hidden from assistive technologies to reduce noise in the accessibility tree.

-<feature.icon
-  className="h-5 w-5 text-primary"
-  size={20}
-  weight="duotone"
-/>
+<feature.icon
+  aria-hidden="true"
+  className="h-5 w-5 text-primary"
+  size={20}
+  weight="duotone"
+/>

46-50: Same a11y tweak for the other icon instances

Apply aria-hidden="true" to the banner icon and the button icon as well for consistency.

Also applies to: 78-80

apps/dashboard/app/(main)/layout.tsx (2)

11-12: await headers() is unnecessary noise
next/headers returns a Headers object synchronously. Awaiting a non-Promise works but is misleading and may trigger ESLint warnings.

-  const session = await auth.api.getSession({ headers: await headers() });
+  const session = await auth.api.getSession({ headers: headers() });

6-10: Consider typing the props instead of an inline object
Extracting an explicit MainLayoutProps interface improves readability and reuse.

-export default async function MainLayout({
-  children,
-}: {
-  children: React.ReactNode;
-}) {
+interface MainLayoutProps {
+  children: React.ReactNode;
+}
+
+export default async function MainLayout({ children }: MainLayoutProps) {
apps/dashboard/app/(auth)/login/magic/page.tsx (1)

24-44: Guarantee setIsLoading(false) in all paths
setIsLoading(false) is currently duplicated inside each success/error callback and the catch block. If signIn.magicLink throws before invoking fetchOptions, isLoading may remain true. Wrap the call in try/finally to ensure consistent cleanup.

-    try {
-      await signIn.magicLink({
+    try {
+      await signIn.magicLink({
         ...
-      });
-    } catch (error) {
-      setIsLoading(false);
-      toast.error('Failed to send magic link. Please try again.');
+      });
+    } catch {
+      toast.error('Failed to send magic link. Please try again.');
+    } finally {
+      setIsLoading(false);
     }
apps/dashboard/app/(auth)/layout.tsx (1)

28-36: Use <a> + target="_blank" for external links instead of next/link

next/link is optimised for internal routing; when the href is an absolute URL (https://www.databuddy.cc) it falls back to a normal anchor, but it still omits the usual security attributes. Prefer:

<a
  href="https://www.databuddy.cc"
  target="_blank"
  rel="noopener noreferrer"
  className="relative z-10"
>

This avoids pre-fetch overhead and prevents reverse-tab-nabbing.

apps/dashboard/app/(auth)/login/forgot/page.tsx (1)

24-40: Duplicate setIsLoading(false) invocations

setIsLoading(false) is executed:

  1. Inside fetchOptions.onSuccess
  2. Inside fetchOptions.onError
  3. In the catch block

The outer try…catch will also run after the promise resolves, so state is toggled multiple times. Not harmful, but unnecessary noise in React DevTools. Consider removing the inner calls and rely on finally:

 try {
   await authClient.forgotPassword({ … });
-  // onSuccess: setIsLoading(false) <-- remove
 } catch (error) {
   toast.error('An error occurred. Please try again later.');
 } finally {
   setIsLoading(false);
 }
apps/dashboard/app/(auth)/login/page.tsx (1)

195-199: Minor UX: “Last used” badge overlaps input border on narrow screens

The absolutely-positioned badge might overlap the input’s right padding on very small widths. Consider adding a right-margin to the input (e.g. pr-16) or moving the badge below the field.

apps/dashboard/app/(main)/invitations/[id]/page.tsx (2)

65-74: String-matching on error messages is brittle

invitationError?.message?.includes('expired') etc. tightly couples logic to server wording and is case-sensitive.
Prefer error codes/enums returned by the API, or at least normalise the message:

-            invitationError?.message?.includes('expired') ||
-            invitationError?.message?.includes('not found')
+            /expired|not found/i.test(invitationError?.message ?? '')

This avoids false negatives if the backend message changes case.


134-142: formatExpiryDate can accept Date to remove caller conversions

formatExpiryDate currently expects a string, forcing every caller to convert. Receive Date | string and normalise inside:

-const formatExpiryDate = (expiresAt: string) => {
-  const date = new Date(expiresAt);
+const formatExpiryDate = (expiresAt: string | Date) => {
+  const date = new Date(expiresAt);

Removes repetitive .toString()/string literals and reduces error surface.

apps/dashboard/app/(main)/sandbox/api-testing/page.tsx (1)

21-23: Minor UX improvement idea
The default headers string now contains explicit new-lines (\n). In the rendered <Textarea> this produces the desired formatting, but when the string is round-tripped through setHeaders the \n are preserved literally.
Consider initialising with a multi-line template literal to avoid the escape sequences inside the UI:

-const [headers, setHeaders] = useState(
-  '{\n  "Content-Type": "application/json"\n}'
-);
+const [headers, setHeaders] = useState(`{
+  "Content-Type": "application/json"
+}`);
apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx (1)

33-38: Avoid recreating the steps array on every render

The constant is stable; wrap it in useMemo (or move it outside the component) to prevent needless allocations and to keep referential equality for child props.

-  const steps: OnboardingStep[] = [
-    'overview',
-    'webhook',
-    'testing',
-    'complete',
-  ];
+  const steps: OnboardingStep[] = useMemo(
+    () => ['overview', 'webhook', 'testing', 'complete'],
+    []
+  );

Remember to import { useMemo } from 'react';.

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

114-117: Minor wording nit

“one-time setup that works for all your sites” – consider dropping “one-time” if future edits can occur, or clarify. Purely copy related; ignore if intentional.

apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx (2)

75-81: Input could use semantic type

If the component supports the type prop, pass type="url" to enable native validation / copy helpers in some browsers.


82-95: Copy feedback race condition

copied is supplied by the parent, but rapid successive clicks may flicker icon states. Consider debouncing or locally setting a brief success state to avoid UX jitter.

apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx (1)

37-39: Label mismatch

The label 'Test command' is generic; consider something more specific (e.g., 'Stripe CLI command') to aid analytics or toast messages.

apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx (1)

141-149: Reuse existing clipboard helper to unify error handling

handleCopy re-implements clipboard logic already available as copyToClipboard in other components / utils.
Import and reuse it to avoid divergence in behaviour & error reporting.

apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts (1)

175-180: Clipboard helper duplicates logic across components

Same copy-to-clipboard implementation exists in multiple places. Consolidate into a shared util (e.g. lib/utils/clipboard.ts) so error handling & toast text remain consistent.

apps/dashboard/app/(auth)/register/page.tsx (2)

81-88: Avoid storing auth tokens in localStorage – switch to Http-Only cookies

Persisting sensitive session data in localStorage exposes it to JavaScript (and thus XSS). Consider returning the token in a Set-Cookie header with the HttpOnly, Secure, and SameSite flags and let the browser manage it automatically.
This keeps the credential out of the JS context and eliminates manual reads/writes in both success callbacks.

Also applies to: 135-141


432-435: Cast in onCheckedChange hides potential API-shape issues

onCheckedChange from Radix UI returns boolean | "indeterminate".
Blind-casting to boolean may silently coerce "indeterminate" to true, defeating the honeypot / T&C logic. Safeguard the value instead:

-onCheckedChange={(checked) => setIsHoneypot(checked as boolean)}
+onCheckedChange={(checked) =>
+  setIsHoneypot(checked === true)
+}

Repeat the same pattern for setAcceptTerms.

Also applies to: 445-447

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

82-91: Add aria-hidden to decorative icons inside tab triggers.

<tab.icon /> is purely visual; screen-readers will read the SVG title (often the filename), producing redundant output after the visible <span> label. Mark the icon as hidden to assistive tech:

-<tab.icon className="mr-2 h-4 w-4" />
+<tab.icon aria-hidden="true" className="mr-2 h-4 w-4" />
apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx (1)

20-24: Silence duplicate screen-reader output from inline SVGs.

Like the billing tabs, these inline UsersIcon/BuildingsIcon instances are decorative and immediately followed by descriptive text. Add aria-hidden="true" (or role="presentation") to prevent redundant announcements.

-<UsersIcon className="h-8 w-8 text-primary" size={32} weight="duotone" />
+<UsersIcon
+  aria-hidden="true"
+  className="h-8 w-8 text-primary"
+  size={32}
+  weight="duotone"
+/>

-<UsersIcon className="h-3 w-3 text-success" size={12} />
+<UsersIcon aria-hidden="true" className="h-3 w-3 text-success" size={12} />
apps/dashboard/app/(main)/organizations/[slug]/components/team-view.tsx (2)

21-29: Prefer precise typing for the icon prop

icon: any loses all compile-time guarantees. Narrow the type to ComponentType<IconProps> (or whatever @phosphor-icons/react exports) so that only valid icon components are accepted and the JSX attributes are type-checked.

-}: {
-  icon: any;
-  label: string;
-  value: number;
-}) => (
+}: {
+  icon: React.ComponentType<React.ComponentProps<typeof UsersIcon>>;
+  label: string;
+  value: number;
+}) => (

94-115: Memoise derived collections to avoid needless re-renders

activeInvitations and totalMembers are recomputed on every render.
Wrap them in useMemo to prevent unnecessary work and to avoid triggering downstream memoised components when the source data hasn’t changed.

-const activeInvitations =
-  invitations?.filter((inv) => inv.status === 'pending') || [];
-const totalMembers = (members?.length || 0) + activeInvitations.length;
+const activeInvitations = useMemo(
+  () => invitations?.filter((inv) => inv.status === 'pending') || [],
+  [invitations],
+);
+const totalMembers = useMemo(
+  () => (members?.length || 0) + activeInvitations.length,
+  [members, activeInvitations],
+);
apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx (1)

65-68: Remove the any cast on role selection

The state is already constrained to 'owner' | 'admin' | 'member'; cast is unnecessary and hides typos.

-<Select
-  onValueChange={(value) => setInviteRole(value as any)}
-  value={inviteRole}
->
+<Select
+  onValueChange={(value) =>
+    setInviteRole(value as 'owner' | 'admin' | 'member')
+  }
+  value={inviteRole}
+>
apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (1)

138-151: Repeated filter calls in render can be expensive

members?.filter(...) is executed three times. Cache the counts with a single pass or useMemo for O(n) → O(1) during render.

-<Badge ...>
-  {members?.filter((m) => m.role === 'admin').length || 0}
-</Badge>
+<Badge ...>
+  {adminCount}
+</Badge>
const { ownerCount, adminCount, memberCount } = useMemo(() => {
  const counts = { owner: 0, admin: 0, member: 0 };
  members?.forEach((m) => counts[m.role]++);
  return {
    ownerCount: counts.owner,
    adminCount: counts.admin,
    memberCount: counts.member,
  };
}, [members]);
apps/dashboard/app/(main)/organizations/[slug]/components/invitation-list.tsx (1)

106-108: Handle potential long email strings gracefully

Long email addresses might overflow the dialog title width. Consider truncating or moving the address into the description to preserve layout.

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

40-43: Replace any with a typed Organization interface
organization: any forfeits type-safety in a core admin screen. Expose a minimal Organization type (id, name, slug, logo, …) in @/types and import it here.

apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx (1)

87-97: Add MIME-type / size guards before FileReader

Only the <input accept="…"> attribute guards the picker UI. Users (or tests) can still drop any file onto the input.
Consider rejecting non-image files and oversized images up-front to avoid unnecessary FileReader work and potential DoS via huge files.

apps/dashboard/app/(main)/organizations/[slug]/page.tsx (2)

26-31: Provide explicit prop types

SetActiveButton receives four props yet is typed as any. This leaks type-safety and makes refactors harder.

-function SetActiveButton({
-  onSetActive,
-  isSettingActive,
-  isCurrentlyActive,
-}: any) {
+interface SetActiveButtonProps {
+  onSetActive: () => void;
+  isSettingActive: boolean;
+  isCurrentlyActive: boolean;
+}
+
+function SetActiveButton({
+  onSetActive,
+  isSettingActive,
+  isCurrentlyActive,
+}: SetActiveButtonProps) {

118-122: Avoid forcing full page reload on retry

window.location.reload() drops React state and incurs a cold start.
Prefer re-invoking the useOrganizations query, or exposing its refetch() method, so only the needed data is reloaded and the UX remains smooth.

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

44-47: find scans both arrays every time

[...personalWebsites, ...organizationWebsites] allocates a new array on every click.
For larger lists consider a memoised Map keyed by id to achieve O(1) look-ups.

apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)

88-97: Skeleton key should precede the className prop

Minor: The key={i} prop is placed after several JSX props, which can hinder quick key scanning when debugging list rendering issues. Placing key first is a common convention.

No functional change—just a readability nit.

apps/dashboard/app/(main)/billing/hooks/use-billing.ts (1)

22-36: Unused variable result

handleUpgrade stores const result = await attach(…) but never uses result.
Either remove the assignment or use the value (e.g., for analytics) to avoid the eslint @typescript-eslint/no-unused-vars warning.

apps/dashboard/app/(main)/billing/components/cancel-subscription-dialog.tsx (1)

31-34: Locale-agnostic date formatting may surprise users

new Date(currentPeriodEnd).toLocaleDateString() renders according to the server/browser locale, which can be inconsistent across users and environments. If the product requires a consistent format (e.g. MMM d, yyyy) consider specifying a locale or using a dedicated date-formatting util (date-fns, dayjs, Intl.DateTimeFormat).

apps/dashboard/app/(main)/billing/components/pricing-tiers-tooltip.tsx (1)

81-85: key={tier.to} risks duplicate keys

to can repeat across different plans ('inf' is identical for every last tier), which breaks React’s key uniqueness guarantee. Use the index or a composite key.

-            {tiers.map((tier) => (
+            {tiers.map((tier, idx) => (
 ...
-                key={tier.to}
+                key={`${tier.to}-${idx}`}
apps/dashboard/app/(main)/billing/components/overview-tab.tsx (1)

144-151: Duplicate conditional renders identical text

isOverLimit ? 'Upgrade' : 'Upgrade' can be simplified.

-                  {isOverLimit ? 'Upgrade' : 'Upgrade'}
+                  Upgrade
apps/dashboard/app/(main)/billing/components/history-tab.tsx (1)

245-249: Sorting mutates the invoices prop – clone before sorting

Array.prototype.sort sorts in place. If invoices is derived from props or a global store, this mutates shared state and can cause subtle bugs or unexpected re-renders. Clone before sorting.

-  {invoices
-    .sort((a: Invoice, b: Invoice) => b.created_at - a.created_at)
+  {[...invoices]
+    .sort((a: Invoice, b: Invoice) => b.created_at - a.created_at)
apps/dashboard/app/(main)/organizations/components/organization-switcher.tsx (1)

29-30: Remove unused isSettingActiveOrganization variable

isSettingActiveOrganization is destructured but never used, which will trigger the no-unused-vars ESLint / TypeScript rule and bloats the bundle.

-const { setActiveOrganization, isSettingActiveOrganization } = useOrganizations();
+const { setActiveOrganization } = useOrganizations();
apps/basket/src/lib/logger.ts (1)

5-9: Guard against missing ENV before instantiating Logtail

token and endpoint are cast to string; if either env var is undefined you’ll end up sending the literal string "undefined" to Logtail, silently mis-configuring it.

-const token = process.env.LOGTAIL_SOURCE_TOKEN as string;
-const endpoint = process.env.LOGTAIL_ENDPOINT as string;
+const token = process.env.LOGTAIL_SOURCE_TOKEN ?? '';
+const endpoint = process.env.LOGTAIL_ENDPOINT ?? '';
+
+if (!token) {
+  console.warn('[logger] LOGTAIL_SOURCE_TOKEN is not set – logs will be dropped');
+}

Optionally throw early in non-production builds.

apps/basket/src/index.ts (2)

40-41: port type mismatch

process.env.PORT is a string; Elysia expects a number. Coerce explicitly to avoid accidental "3000" string ports on some runtimes.

-  port: process.env.PORT || 4000,
+  port: Number(process.env.PORT) || 4000,

11-17: Avoid losing original stack trace

Wrapping the original error in a new Error discards its stack. Pass the original object directly:

-logger.error(
-  new Error(`${error instanceof Error ? error.name : 'Unknown'}: ${…}`)
-);
+logger.error(error);

If you need custom formatting, attach metadata instead of re-wrapping.

apps/basket/src/hooks/auth.ts (2)

313-350: Duplicate caching utilities—consider consolidating

getWebsiteById/getOwnerId and the later Cached variants implement identical logic with different cache keys. Keeping both increases maintenance overhead and risk of divergence.

If two TTL/keys are required, expose the raw fetchers once and wrap with cacheable in a single place:

const fetchWebsite   = (id: string) => db.query.websites.findFirst({});
export const getWebsiteById = cacheable(fetchWebsite, {});

const fetchOwnerId   = (w: Website) => _resolveOwnerId(w);
export const getOwnerId     = cacheable(fetchOwnerId, {});

114-142: isValidOrigin early-exit may hide configuration errors

Returning true when originHeader is empty effectively disables origin checking for requests lacking the header (e.g., cURL or some older browsers). If the intent is to require the header, flip the logic or make it configurable.

apps/api/src/types/tables.ts (1)

21-31: Reduce duplication by deriving TableFieldsMap from Analytics

TableFieldsMap must be updated manually whenever a new analytics table is added.
A mapped-type keyed off Analytics eliminates this maintenance risk:

-export type TableFieldsMap = {
-  'analytics.events': keyof AnalyticsEvent;
-  'analytics.errors': keyof ErrorEvent;
-  'analytics.web_vitals': keyof WebVitalsEvent;
-  'analytics.stripe_payment_intents': keyof StripePaymentIntent;
-  'analytics.stripe_charges': keyof StripeCharge;
-  'analytics.stripe_refunds': keyof StripeRefund;
-  'analytics.blocked_traffic': keyof BlockedTraffic;
-};
+export type TableFieldsMap = {
+  [K in AnalyticsTable]: K extends 'analytics.events'
+    ? keyof AnalyticsEvent
+    : K extends 'analytics.errors'
+      ? keyof ErrorEvent
+      : K extends 'analytics.web_vitals'
+        ? keyof WebVitalsEvent
+        : K extends 'analytics.stripe_payment_intents'
+          ? keyof StripePaymentIntent
+          : K extends 'analytics.stripe_charges'
+            ? keyof StripeCharge
+            : K extends 'analytics.stripe_refunds'
+              ? keyof StripeRefund
+              : keyof BlockedTraffic;
+};
apps/basket/src/polyfills/compression.js (1)

10-24: Stream handles are never closed

zlib transform streams hold native resources.
Consider exposing flush / close helpers or forwarding handle.close() on the returned WritableStream’s close() to avoid leaks in long-lived workers.

apps/api/src/routes/health.ts (1)

37-61: Hard-coded version string

version: '1.0.0' will drift quickly. Pull the value from package.json or an environment variable to avoid manual updates.

apps/api/src/middleware/rate-limit.ts (1)

12-15: Path check may miss /trpc root

request.url.includes('/trpc/') skips /trpc but not /trpc without a trailing slash.
Consider checking startsWith('/trpc') after stripping the origin for full coverage.

apps/basket/src/utils/user-agent.ts (1)

84-92: Compile bot regexes once to cut per-request CPU

detectBot rebuilds every RegExp on each call (bots.length ≫ 1 000). Pre-compiling once at module load reduces GC pressure and latency.

-import { bots } from '@databuddy/shared';
+import { bots as rawBots } from '@databuddy/shared';
+
+const bots = rawBots.map((b) => ({ ...b, regex: new RegExp(b.regex, 'i') }));
-const detectedBot = bots.find((bot) => new RegExp(bot.regex, 'i').test(ua));
+const detectedBot = bots.find((bot) => bot.regex.test(ua));
apps/basket/src/utils/user-agent.test.ts (1)

57-70: Add a negative-parsing test for stronger coverage

parseUserAgent('garbage{') currently exercises the JSON-parser’s error branch in UAParser, but the suite never asserts on the "success: false" path.
A one-liner test would lock that behaviour and prevent silent regressions.

apps/api/src/agent/utils/response-parser.ts (1)

13-17: Minor regexp hardening

replace(/```json\n?/g, '') and replace(/```\n?/g, '') miss \r\n (Windows) line endings.
Consider \r?\n? to be platform-agnostic.

-      .replace(/```json\n?/g, '')
-      .replace(/```\n?/g, '');
+      .replace(/```json\r?\n?/g, '')
+      .replace(/```\r?\n?/g, '');
apps/api/src/query/screen-resolution-to-device-type.ts (1)

38-44: Landscape mobiles mis-classified as unknown

A phone in landscape (e.g. 812x375, aspect ≈ 2.16) fails the mobile check (aspect < 1.1) and the tablet check (width <= 800), ending up as unknown.
Unless intentional, broaden the mobile guard to include wider aspects when width ≤ 800.

-  if (width <= 800 && aspect < 1.1) return 'mobile';
+  if (width <= 800) return 'mobile';
apps/basket/src/hooks/auth.test.ts (1)

13-18: Reset mocked logger between tests

vi.mock replaces logger.warn / logger.error, but the mock state is never cleared. Residual call-counts can cause false positives in later assertions. Add a global afterEach(vi.resetAllMocks) (or scoped reset) to avoid bleed-through.

apps/basket/src/routes/stripe.ts (1)

68-73: Trim forwarded-for header to prevent leading/trailing spaces

split(',')[0] may still include a preceding space. Add .trim() to avoid bogus IPs.

-    request.headers.get('x-forwarded-for')?.split(',')[0] ||
+    request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
apps/api/src/query/index.ts (2)

1-1: Use a single import path for Zod across the repo

Other modules (e.g., apps/basket/src/utils/event-schema.ts) import Zod via 'zod/v4', whereas this file uses the root package name. Mixing entry-points risks bundlers treating them as two distinct copies, breaking instanceof ZodError checks and increasing bundle size.

-import { z } from 'zod';
+import { z } from 'zod/v4';

(or switch every other file to 'zod', but pick one and standardise)


32-57: Eliminate duplication between executeQuery and compileQuery

Both functions parse the request, look up the config, construct a SimpleQueryBuilder, and only differ in the final method invoked (execute() vs compile()). Consolidating that common path into a small helper keeps the public API intact while shrinking maintenance surface.

-const build = (
-  request: QueryRequest,
-  websiteDomain?: string | null,
-  timezone?: string
-) => {
-  const validated = QuerySchema.parse(request);
-  const config = QueryBuilders[validated.type];
-  if (!config) throw new Error(`Unknown query type: ${validated.type}`);
-  return new SimpleQueryBuilder(
-    config,
-    { ...validated, timezone: timezone ?? validated.timezone },
-    websiteDomain
-  );
-};
-
-export const executeQuery = async (
+const build = (
   request: QueryRequest,
   websiteDomain?: string | null,
   timezone?: string
-) => {
-  const builder = build(request, websiteDomain, timezone);
-  return await builder.execute();
-};
+) => new SimpleQueryBuilder(
+    QueryBuilders[QuerySchema.parse(request).type],
+    { ...request, timezone },
+    websiteDomain
+  );
+
+export const executeQuery = async (...args: Parameters<typeof build>) =>
+  (await build(...args)).execute();
apps/api/src/agent/utils/stream-utils.ts (1)

8-18: Reuse a single TextEncoder instance

Creating a new TextEncoder for every chunk is minor but avoidable allocation. Hoisting one encoder outside the loop (or even outside start) is simpler and slightly faster.

-          const data = `data: ${JSON.stringify(update)}\n\n`;
-          controller.enqueue(new TextEncoder().encode(data));
+          const data = `data: ${JSON.stringify(update)}\n\n`;
+          controller.enqueue(encoder.encode(data));
const encoder = new TextEncoder();
apps/api/src/agent/processor.ts (1)

35-40: Avoid logging raw user prompts in production

console.info currently emits the full user message and website identifiers. Even though this PR is mainly cosmetic, consider redacting or using structured logging with a log level that can be suppressed in prod to minimise PII exposure.

-console.info('✅ [Assistant Processor] Input validated', {
-  message: request.message,
+console.info('✅ [Assistant Processor] Input validated', {
+  message: process.env.NODE_ENV === 'development' ? request.message : '<redacted>',
apps/basket/src/routes/basket.ts (2)

562-585: Prefer using the project logger over console.error for consistency

console.error bypasses the structured logger and makes it harder to correlate messages in production logs.

- console.error(
-   'Blocked event schema errors:',
-   parseResult.error.issues,
-   'Payload:',
-   body
- );
+ logger.warn('Blocked event schema errors', {
+   issues: parseResult.error.issues,
+   payload: body,
+ });

689-713: Duplicate diagnostic code – consider extracting helper

The “invalid_schema” handling block is repeated three times (track, error, web_vitals). Extracting a small helper would shrink the route body and lower the chance of divergence.

apps/api/src/agent/prompts/agent.ts (1)

128-142: Minor: potential HTML-escaping issue in history injection

User/assistant messages are injected directly into an XML-like tag. If a message contains </message> (unlikely but possible) it will break the prompt structure. Consider escape-ing angle brackets.

apps/api/src/query/simple-builder.ts (1)

70-74: Tiny optimisation

dateStr.split('.') is called but only index 0 is used. dateStr.split('T')[0] would be clearer and faster.

apps/api/src/query/utils.ts (2)

64-81: Performance: avoid repeated split in domain matching

Inside getReferrerByDomain, every sub-domain path runs parts.slice(i).join('.') on each loop. Pre-compute parts.length and reuse or iterate with substring maths to cut allocations, especially on large datasets.


212-243: Percentage recomputation can exceed 100%

After lumping resolutions into device types you simply sum previous percentages then recalc based on pageviews, which may diverge from the original denominator used by upstream SQL. Consider dropping the carry-over sum and always recompute from pageviews/totalPageviews to guarantee totals ≈ 100%.

apps/api/src/routes/query.ts (3)

82-100: Factor out duplicated timezone-resolution logic

The logic that derives the final timezone value appears twice verbatim. Consider extracting it into a small helper (e.g., resolveTimezone(request, url, pref?)) to:

  1. Remove ~40 duplicate lines
  2. Guarantee the two branches never diverge subtly in the future
  3. Make the derive block far easier to scan

This is a low-risk, readability-first refactor.

Also applies to: 124-142


255-260: Avoid redundant DB look-ups for website domains

executeDynamicQuery calls getWebsiteDomain even though the upstream .derive() has already fetched the website object (for non-public sites) and placed it on the context.

Passing website?.domain (when available) into executeDynamicQuery would:

  • Save one round-trip per query
  • Reduce pressure on the cacheable layer
  • Keep public-website behaviour unchanged

Consider amending the call site and function signature to accept an optional websiteDomain parameter.


261-264: Granularity→time-unit mapping is incomplete

getTimeUnit() collapses everything that isn’t 'hourly' | 'hour' to 'day'. If front-end code ever sends 'daily' you get 'day', which is fine, but 'minute', 'week', etc. silently become 'day', potentially generating bloated result sets.

Either:

  • Extend the mapping to cover every timeUnit your builders support, or
  • Throw early when an unsupported granularity is provided.

Fail-fast beats silent downgrades.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e737a75 and d26763c.

📒 Files selected for processing (106)
  • .vscode/settings.json (1 hunks)
  • apps/api/.gitignore (0 hunks)
  • apps/api/biome.json (0 hunks)
  • apps/api/package.json (1 hunks)
  • apps/api/src/agent/handlers/chart-handler.ts (1 hunks)
  • apps/api/src/agent/handlers/metric-handler.ts (1 hunks)
  • apps/api/src/agent/index.ts (1 hunks)
  • apps/api/src/agent/processor.ts (1 hunks)
  • apps/api/src/agent/prompts/agent.ts (3 hunks)
  • apps/api/src/agent/utils/ai-client.ts (1 hunks)
  • apps/api/src/agent/utils/query-executor.ts (1 hunks)
  • apps/api/src/agent/utils/response-parser.ts (1 hunks)
  • apps/api/src/agent/utils/sql-validator.ts (1 hunks)
  • apps/api/src/agent/utils/stream-utils.ts (1 hunks)
  • apps/api/src/index.ts (3 hunks)
  • apps/api/src/middleware/rate-limit.ts (1 hunks)
  • apps/api/src/query/builders/custom-events.ts (1 hunks)
  • apps/api/src/query/builders/devices.ts (1 hunks)
  • apps/api/src/query/builders/errors.ts (1 hunks)
  • apps/api/src/query/builders/geo.ts (1 hunks)
  • apps/api/src/query/builders/index.ts (1 hunks)
  • apps/api/src/query/builders/pages.ts (3 hunks)
  • apps/api/src/query/builders/performance.ts (1 hunks)
  • apps/api/src/query/builders/profiles.ts (7 hunks)
  • apps/api/src/query/builders/sessions.ts (6 hunks)
  • apps/api/src/query/builders/summary.ts (6 hunks)
  • apps/api/src/query/builders/traffic.ts (1 hunks)
  • apps/api/src/query/constants.ts (1 hunks)
  • apps/api/src/query/index.ts (1 hunks)
  • apps/api/src/query/screen-resolution-to-device-type.ts (1 hunks)
  • apps/api/src/query/simple-builder.ts (1 hunks)
  • apps/api/src/query/types.ts (1 hunks)
  • apps/api/src/query/utils.ts (1 hunks)
  • apps/api/src/routes/assistant.ts (1 hunks)
  • apps/api/src/routes/health.ts (1 hunks)
  • apps/api/src/routes/query.ts (1 hunks)
  • apps/api/src/types/tables.ts (1 hunks)
  • apps/api/tsconfig.json (0 hunks)
  • apps/basket/package.json (1 hunks)
  • apps/basket/src/hooks/auth.test.ts (1 hunks)
  • apps/basket/src/hooks/auth.ts (7 hunks)
  • apps/basket/src/index.ts (1 hunks)
  • apps/basket/src/lib/logger.ts (2 hunks)
  • apps/basket/src/polyfills/compression.js (1 hunks)
  • apps/basket/src/routes/basket.ts (1 hunks)
  • apps/basket/src/routes/stripe.ts (2 hunks)
  • apps/basket/src/types/payments.ts (1 hunks)
  • apps/basket/src/types/track.ts (1 hunks)
  • apps/basket/src/utils/event-schema.ts (1 hunks)
  • apps/basket/src/utils/ip-geo.test.ts (1 hunks)
  • apps/basket/src/utils/ip-geo.ts (8 hunks)
  • apps/basket/src/utils/user-agent.test.ts (1 hunks)
  • apps/basket/src/utils/user-agent.ts (1 hunks)
  • apps/basket/src/utils/validation.ts (13 hunks)
  • apps/basket/tsconfig.json (1 hunks)
  • apps/dashboard/app/(auth)/layout.tsx (2 hunks)
  • apps/dashboard/app/(auth)/login/forgot/page.tsx (3 hunks)
  • apps/dashboard/app/(auth)/login/magic-sent/page.tsx (3 hunks)
  • apps/dashboard/app/(auth)/login/magic/page.tsx (3 hunks)
  • apps/dashboard/app/(auth)/login/page.tsx (12 hunks)
  • apps/dashboard/app/(auth)/login/verification-needed/page.tsx (3 hunks)
  • apps/dashboard/app/(auth)/register/page.tsx (21 hunks)
  • apps/dashboard/app/(main)/billing/components/cancel-subscription-dialog.tsx (1 hunks)
  • apps/dashboard/app/(main)/billing/components/history-tab.tsx (1 hunks)
  • apps/dashboard/app/(main)/billing/components/no-payment-method-dialog.tsx (1 hunks)
  • apps/dashboard/app/(main)/billing/components/overview-tab.tsx (10 hunks)
  • apps/dashboard/app/(main)/billing/components/plans-tab.tsx (1 hunks)
  • apps/dashboard/app/(main)/billing/components/pricing-tiers-tooltip.tsx (1 hunks)
  • apps/dashboard/app/(main)/billing/data/billing-data.ts (5 hunks)
  • apps/dashboard/app/(main)/billing/hooks/use-billing.ts (1 hunks)
  • apps/dashboard/app/(main)/billing/page.tsx (5 hunks)
  • apps/dashboard/app/(main)/home/page.tsx (1 hunks)
  • apps/dashboard/app/(main)/invitations/[id]/page.tsx (21 hunks)
  • apps/dashboard/app/(main)/layout.tsx (1 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/invitation-list.tsx (6 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx (4 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/member-list.tsx (7 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx (9 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/organization-page-skeleton.tsx (1 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (9 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx (12 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/team-view.tsx (2 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx (3 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx (7 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx (3 hunks)
  • apps/dashboard/app/(main)/organizations/[slug]/page.tsx (12 hunks)
  • apps/dashboard/app/(main)/organizations/components/error-display.tsx (2 hunks)
  • apps/dashboard/app/(main)/organizations/components/onboarding-card.tsx (1 hunks)
  • apps/dashboard/app/(main)/organizations/components/organization-switcher.tsx (1 hunks)
  • apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (10 hunks)
  • apps/dashboard/app/(main)/organizations/components/stats-skeleton.tsx (1 hunks)
  • apps/dashboard/app/(main)/organizations/page.tsx (9 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/configuration-summary.tsx (3 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx (2 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx (2 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx (2 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx (2 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx (2 hunks)
  • apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx (7 hunks)
  • apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx (10 hunks)
  • apps/dashboard/app/(main)/revenue/_components/tabs/overview-tab.tsx (3 hunks)
  • apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx (13 hunks)
  • apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts (6 hunks)
  • apps/dashboard/app/(main)/revenue/page.tsx (9 hunks)
  • apps/dashboard/app/(main)/revenue/utils/types.ts (1 hunks)
  • apps/dashboard/app/(main)/sandbox/api-testing/page.tsx (10 hunks)
💤 Files with no reviewable changes (3)
  • apps/api/.gitignore
  • apps/api/tsconfig.json
  • apps/api/biome.json
🧰 Additional context used
🧬 Code Graph Analysis (32)
apps/dashboard/app/(main)/billing/components/plans-tab.tsx (1)
apps/dashboard/components/autumn/pricing-table.tsx (1)
  • PricingTable (13-183)
apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx (5)
apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx (1)
  • StepIndicator (11-36)
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx (1)
  • OverviewStep (10-57)
apps/database/lib/utils.ts (1)
  • copyToClipboard (57-65)
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx (1)
  • TestingStep (12-77)
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx (1)
  • CompleteStep (10-43)
apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx (1)
apps/database/lib/utils.ts (1)
  • copyToClipboard (57-65)
apps/api/src/query/builders/custom-events.ts (1)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/basket/src/utils/user-agent.test.ts (1)
apps/basket/src/utils/user-agent.ts (2)
  • detectBot (73-119)
  • parseUserAgent (25-71)
apps/api/src/query/builders/profiles.ts (3)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
packages/db/src/clickhouse/query_builder.ts (2)
  • limit (322-327)
  • offset (332-337)
apps/api/src/agent/utils/stream-utils.ts (2)
apps/api/src/agent/index.ts (3)
  • createStreamingResponse (18-18)
  • StreamingUpdate (16-16)
  • generateThinkingSteps (19-19)
apps/dashboard/error.tsx (1)
  • Error (8-44)
apps/basket/src/utils/user-agent.ts (1)
packages/shared/src/lists/bots.ts (1)
  • bots (3-4760)
apps/basket/src/types/track.ts (1)
packages/rpc/src/trpc.ts (1)
  • t (27-32)
apps/api/src/query/builders/traffic.ts (2)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
apps/api/src/agent/utils/ai-client.ts (1)
apps/api/src/agent/index.ts (1)
  • getAICompletion (12-12)
apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (2)
apps/dashboard/hooks/use-organizations.ts (1)
  • useOrganizationMembers (259-364)
packages/db/src/drizzle/schema.ts (1)
  • organization (750-761)
apps/api/src/query/constants.ts (1)
apps/api/src/query/builders/index.ts (1)
  • QueryBuilders (12-23)
apps/basket/src/index.ts (1)
apps/basket/src/lib/logger.ts (1)
  • logger (5-9)
apps/basket/src/utils/ip-geo.test.ts (1)
apps/basket/src/utils/ip-geo.ts (6)
  • getGeoLocation (101-150)
  • getClientIp (152-166)
  • parseIp (168-171)
  • anonymizeIp (173-180)
  • getGeo (182-190)
  • extractIpFromRequest (192-204)
apps/api/src/query/builders/pages.ts (3)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
packages/db/src/clickhouse/query_builder.ts (2)
  • limit (322-327)
  • offset (332-337)
apps/api/src/routes/health.ts (1)
packages/db/src/clickhouse/client.ts (1)
  • chQuery (121-126)
apps/basket/src/hooks/auth.test.ts (1)
apps/basket/src/hooks/auth.ts (6)
  • normalizeDomain (151-174)
  • isSubdomain (182-190)
  • isValidDomainFormat (197-223)
  • isValidOrigin (113-142)
  • isValidOriginSecure (226-298)
  • isLocalhost (305-311)
apps/api/src/types/tables.ts (1)
packages/db/src/clickhouse/schema.ts (4)
  • StripePaymentIntent (355-375)
  • StripeCharge (377-397)
  • StripeRefund (399-412)
  • BlockedTraffic (414-440)
apps/dashboard/app/(main)/organizations/page.tsx (2)
apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx (1)
  • TeamsTab (13-73)
apps/dashboard/lib/utils.ts (1)
  • cn (4-6)
apps/api/src/query/builders/summary.ts (3)
apps/api/src/query/types.ts (3)
  • SimpleQueryConfig (30-56)
  • Filter (24-28)
  • TimeUnit (22-22)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
packages/db/src/clickhouse/query_builder.ts (2)
  • limit (322-327)
  • offset (332-337)
apps/dashboard/app/(auth)/login/page.tsx (1)
packages/auth/src/client/auth-client.ts (1)
  • signIn (49-49)
apps/api/src/query/builders/errors.ts (2)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx (1)
apps/database/lib/utils.ts (1)
  • copyToClipboard (57-65)
apps/api/src/query/builders/geo.ts (2)
apps/api/src/query/types.ts (1)
  • SimpleQueryConfig (30-56)
apps/api/src/types/tables.ts (1)
  • Analytics (11-19)
apps/dashboard/app/(main)/layout.tsx (2)
packages/auth/src/auth.ts (1)
  • auth (28-273)
apps/dashboard/next.config.ts (1)
  • headers (15-47)
apps/api/src/agent/prompts/agent.ts (1)
apps/api/src/agent/index.ts (2)
  • AIPlanSchema (8-8)
  • comprehensiveUnifiedPrompt (10-10)
apps/api/src/agent/utils/query-executor.ts (3)
apps/api/src/agent/index.ts (1)
  • executeQuery (13-13)
apps/api/src/query/index.ts (1)
  • executeQuery (32-50)
packages/db/src/clickhouse/client.ts (1)
  • chQuery (121-126)
apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx (1)
apps/dashboard/hooks/use-organizations.ts (1)
  • useOrganizationMembers (259-364)
apps/api/src/query/utils.ts (4)
packages/rpc/src/utils/referrer.ts (1)
  • parseReferrer (19-92)
packages/shared/src/lists/referrers.ts (1)
  • referrers (4-2682)
packages/shared/src/country-codes.ts (2)
  • getCountryCode (270-286)
  • getCountryName (293-305)
apps/api/src/query/screen-resolution-to-device-type.ts (1)
  • mapScreenResolutionToDeviceType (18-57)
apps/api/src/query/index.ts (3)
apps/api/src/query/types.ts (1)
  • QueryRequest (58-70)
apps/api/src/query/builders/index.ts (1)
  • QueryBuilders (12-23)
apps/api/src/query/simple-builder.ts (1)
  • SimpleQueryBuilder (11-175)
apps/basket/src/types/payments.ts (1)
packages/rpc/src/trpc.ts (1)
  • t (27-32)
🔇 Additional comments (48)
apps/basket/tsconfig.json (1)

6-6: Formatting-only change is fine

Arrays were flattened to single-line form without altering content—no impact on the build pipeline or type discovery.
Good consistency with the repo-wide style refactor.

Also applies to: 12-13

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

1-5: LGTM – purely stylistic update
String-quote normalization has no functional impact.

apps/dashboard/app/(auth)/login/magic-sent/page.tsx (1)

73-80: String literal inside JSX is fine – no action needed
The new 'Resend magic link' literal renders correctly; no issues spotted.

apps/dashboard/app/(auth)/login/forgot/page.tsx (1)

24-37: Verify client API method name

The call uses authClient.forgetPassword. In most auth SDKs the verb is forgotPassword. A typo here will lead to a runtime undefined is not a function.

-      await authClient.forgetPassword({
+      await authClient.forgotPassword({

Please confirm the exported method name in @databuddy/auth/client.

apps/dashboard/app/(main)/sandbox/api-testing/page.tsx (3)

3-16: Consistent quote style & import formatting look good
The switch to single quotes and multi-line imports matches the project-wide style enforcement. No functional impact.


139-151: Good guard on conditional body field
Rendering the request-body textarea only for POST/PUT keeps the UI clean and prevents irrelevant input.


224-249: Data array reformatting is purely cosmetic
The quick-test endpoints array now reads much clearer; no behavioural change.

apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx (2)

3-5: Quote normalisation confirmed
Imports switched to single quotes; no other changes.


19-23: Conditional className string remains correct
Nested ternary still produces the intended colour states; just reformatted.

apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx (1)

14-16: Heading reflow only; semantics intact
Splitting the <h3> text across lines doesn’t affect rendering or a11y.

apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx (1)

28-30: Heading reflow matches style guide
Purely stylistic; no functional concerns.

apps/dashboard/app/(main)/revenue/utils/types.ts (1)

1-1: Type alias quote update is safe
Changing the union literals to single quotes does not affect type-checking or downstream usage.

apps/dashboard/app/(main)/revenue/_components/tabs/overview-tab.tsx (1)

24-32: Looks good – purely presentational edits
No behavioural change introduced here.

apps/dashboard/app/(main)/organizations/components/stats-skeleton.tsx (1)

1-1: Import-quote switch is in line with project conventions – no action required.

apps/dashboard/app/(main)/organizations/[slug]/components/organization-page-skeleton.tsx (1)

1-1: Consistent import style acknowledged.

apps/dashboard/app/(main)/billing/components/plans-tab.tsx (1)

1-7: Compact function body is clean and idiomatic – looks good.

apps/dashboard/app/(main)/billing/components/no-payment-method-dialog.tsx (1)

16-52: Looks good – purely stylistic changes

No functional issues spotted in the dialog logic.

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

210-225: Guard against empty names when deriving avatar fallback

member.user.name.charAt(0) throws if name is an empty string.
Use a safe fallback:

-<AvatarFallback ...>
-  {member.user.name.charAt(0).toUpperCase()}
-</AvatarFallback>
+<AvatarFallback ...>
+  {(member.user.name?.[0] || '?').toUpperCase()}
+</AvatarFallback>
apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx (1)

57-61: Looks good – concise toggle logic
The inline callback cleanly flips between selecting and clearing the active website ID; no functional issues spotted.

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

154-159: Tailwind arbitrary animation classes rely on the keyframes being present at build time

Using animate-[slide-right_1s_ease-in-out] / slide-left inside runtime <style jsx> works in dev, but Tailwind strips unknown arbitrary values unless the keyframes are whitelisted in content or declared in tailwind.config.js.

Verify the animation still works after production builds. If it doesn’t, move the @keyframes declarations to a CSS file loaded at build time and add:

theme: {
  extend: {
    animation: {
      'slide-right': 'slide-right 1s ease-in-out',
      'slide-left': 'slide-left 1s ease-in-out',
    },
  },
}
apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)

170-176: Icon-only buttons need accessible labels

The trash button (and similar) has no aria-label. Screen-reader users won’t know its purpose.

<Button
   className="rounded …"
+  aria-label="Delete organization"
   …>

Likely an incorrect or invalid review comment.

apps/dashboard/app/(main)/billing/hooks/use-billing.ts (1)

75-80: Guard against SSR usage

openBillingPortal references window.location.origin. If this hook is ever executed in a server context it will throw. A simple guard prevents accidental SSR regression:

if (typeof window === 'undefined') return;
await openBillingPortal({ returnUrl: `${window.location.origin}/billing` });
apps/dashboard/app/(main)/organizations/page.tsx (1)

241-259: UI state is self-contained and works as intended

MainView correctly controls active tab state via useState and keeps the Tabs component in sync through value / onValueChange. No logic issues detected here.

apps/api/package.json (1)

27-27: No-op change is fine

Only a trailing newline was added—no functional impact.

apps/basket/package.json (1)

34-34: File-ending newline added

Nothing to flag.

apps/basket/src/utils/validation.ts (1)

155-167: Good defensive numeric parsing

The refactored validateNumeric reads well and correctly guards against NaN / Infinity while supporting string inputs.

apps/api/src/query/types.ts (1)

1-56: No concerns – purely stylistic edits
The re-indent and single-quote switch do not alter any exported types.

apps/basket/src/utils/user-agent.ts (1)

102-108: Accept header absence ≠ bot

Some legitimate clients (certain fetch libraries, curl, edge-cases with service-workers) omit the Accept header. Treating that alone as bot traffic risks false positives. Consider combining signals (e.g. UA length + missing Accept) or downgrading to a confidence score.

apps/api/src/query/builders/custom-events.ts (1)

16-20: Looks good – purely stylistic

The where clause retains its original semantics; only quoting style changed.

apps/api/src/query/builders/errors.ts (1)

4-7: Type parameter on SimpleQueryConfig may be invalid

SimpleQueryConfig is imported as if it were generic, yet the definition in query/types.ts shows no type parameter. This will fail type-checking unless there’s an overloaded declaration elsewhere.

Please confirm and either:

-SimpleQueryConfig<typeof Analytics.errors>
+SimpleQueryConfig

or update the interface to be generic across builder files for consistency.

apps/basket/src/utils/user-agent.test.ts (1)

1-2: Mind the Request polyfill dependency

vitest in a Node environment doesn’t ship a global Request object.
If a polyfill (e.g. undici or node-fetch) isn’t injected in the test runner’s setup file, these tests will crash at runtime.

Run the suite locally; if it explodes with ReferenceError: Request is not defined, add:

// test/setup.ts
import 'undici/register'   // or  globalThis.Request = (await import('node-fetch')).Request

and reference this file in vitest.config.ts.

apps/api/src/query/builders/index.ts (1)

12-22: Verify no duplicate keys across QueryBuilders spreads
With ten builder objects spread into QueryBuilders, any overlapping property name in a later spread will silently override an earlier one. Our automation scripts ran into syntax issues, so please manually confirm there are no duplicate builder keys. For example, you can run:

for file in apps/api/src/query/builders/*Builders.ts; do
  awk '/export const .*Builders *= *{/,/};/' "$file"
done \
  | grep -oE "['\"]?[A-Za-z0-9_]+['\"]?(?=:)" \
  | tr -d \"\' \
  | sort | uniq -d

If this prints any names, rename or dedupe the conflicting builder functions to avoid silent overrides.

apps/api/src/query/builders/devices.ts (1)

95-112: Ensure mapDeviceTypes plugin exists

device_types declares plugins: { mapDeviceTypes: true }, yet no guarantee the plugin is registered in the query pipeline.
A missing plugin will throw at runtime.

Search for its implementation; if absent, either add it or drop the flag.

apps/basket/src/types/payments.ts (1)

1-1: Typings only – OK

Importing t from elysia is correct for TypeBox-backed schemas; no functional changes introduced.

apps/basket/src/utils/event-schema.ts (1)

1-1: Standardise the Zod import path

This file imports from 'zod/v4' while other API-side files import 'zod'. Pick one convention to avoid duplicate bundles and subtle type-identity issues.

apps/api/src/query/builders/traffic.ts (1)

4-7: Verify that SimpleQueryConfig is declared as a generic

SimpleQueryConfig<typeof Analytics.events> implies SimpleQueryConfig is generic, but the snippet in query/types.ts shows a non-generic interface. If the interface really takes zero type parameters, this will be a compile-time error.

apps/api/src/query/builders/geo.ts (1)

5-90: LGTM – purely stylistic changes

All modifications are quote/indentation normalisation; no functional impact detected.

apps/api/src/routes/assistant.ts (1)

1-96: Excellent stylistic consistency improvements!

The reformatting enhances code readability through consistent single quote usage, proper indentation, and organized import statements. All functional logic remains intact.

Note: The PR objectives mention enforcing "double quotes" but the implementation uses single quotes throughout. This appears to be a documentation discrepancy rather than an implementation issue.

apps/api/src/query/constants.ts (1)

1-237: Consistent stylistic improvements with a minor logic enhancement.

The formatting changes improve code consistency with single quote usage, proper indentation, and trailing commas. The simplification of the isQueryCustomizable function (lines 230-233) to directly return the property value is a nice improvement over the previous || false pattern.

apps/api/src/query/builders/pages.ts (1)

1-171: Well-executed formatting improvements throughout the query builders.

The consistent use of single quotes, improved indentation, and reformatted SQL queries significantly enhance readability. All query logic, configurations, and exported functionality remain unchanged.

apps/api/src/agent/handlers/metric-handler.ts (1)

1-89: Clean formatting improvements that enhance code organization.

The reformatted imports, consistent indentation, and improved parameter alignment make the code more readable. All metric handling logic, SQL validation, and error handling functionality remain intact.

apps/api/src/query/builders/performance.ts (1)

1-188: Consistent formatting improvements across all performance query builders.

The standardization to single quotes, improved indentation, and consistent trailing commas enhance readability. All performance metrics calculations, filtering logic, and query configurations remain functionally unchanged.

apps/api/src/query/builders/summary.ts (1)

4-8: ✅ Purely stylistic – looks good

The reshaped generic type declaration and multiline object literal improve readability without affecting behaviour. No concerns.

apps/api/src/query/builders/sessions.ts (1)

18-26: ✅ No-op formatting change

Quote‐style normalisation and trailing commas are syntactically correct and keep the builder consistent with the rest of the codebase.

apps/api/src/index.ts (1)

10-20: CORS origin list correctly gated by NODE_ENV

The regex plus dev override is clear and avoids inadvertent prod exposure. Good defensive choice.

apps/api/src/agent/handlers/chart-handler.ts (1)

49-63: LGTM – logic untouched

Formatting only; the early-return guard, SQL validation, and admin debug info remain intact.

apps/api/src/agent/index.ts (1)

1-20: Export regrouping looks good

Pure re-ordering / grouping – no functional impact. Tree-shaking and type exports remain intact.

apps/api/src/agent/prompts/agent.ts (1)

4-20: Keep enum in sync with downstream consumers

chart_type now includes radar, funnel, grouped_bar. Please verify that:

  1. Chart-handler front-end components render these types.
  2. Any runtime validation (e.g. ClickHouse result-shape mappers) recognises them.

If not, add fallbacks before this ships.

Comment thread .vscode/settings.json Outdated
Comment on lines +37 to 44
userQuery: string,
websiteId: string,
websiteHostname: string,
mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step',
previousMessages?: any[],
agentToolResult?: any,
model?: 'chat' | 'agent' | 'agent-max'
) => `

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

model parameter is unused – will fail with noUnusedParameters

The new optional model argument is never referenced. If the repo has noUnusedParameters turned on, compilation will fail.

Either remove it or wire it into the template.

-   agentToolResult?: any,
-   model?: 'chat' | 'agent' | 'agent-max'
+   agentToolResult?: any

or make use of it inside the template.

📝 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
userQuery: string,
websiteId: string,
websiteHostname: string,
mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step',
previousMessages?: any[],
agentToolResult?: any,
model?: 'chat' | 'agent' | 'agent-max'
) => `
userQuery: string,
websiteId: string,
websiteHostname: string,
mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step',
previousMessages?: any[],
agentToolResult?: any
) => `
🤖 Prompt for AI Agents
In apps/api/src/agent/prompts/agent.ts around lines 37 to 44, the optional
'model' parameter is declared but not used, which will cause a compilation error
with 'noUnusedParameters' enabled. To fix this, either remove the 'model'
parameter if it is unnecessary, or incorporate it into the template string where
the function returns, ensuring it is referenced and used properly.

Comment on lines 3 to 10
const OPENAI_CONFIG = {
apiKey: process.env.AI_API_KEY,
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.AI_API_KEY,
baseURL: 'https://openrouter.ai/api/v1',
} as const;

const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17';

const openai = new OpenAI(OPENAI_CONFIG);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fail fast when AI_API_KEY is missing

Instantiation happens at module load; if the env var is absent the SDK will later throw. Surfacing this earlier helps ops triage:

-const OPENAI_CONFIG = {
-  apiKey: process.env.AI_API_KEY,
-  baseURL: 'https://openrouter.ai/api/v1',
-} as const;
+if (!process.env.AI_API_KEY) {
+  throw new Error('Environment variable AI_API_KEY is not set');
+}
+
+const OPENAI_CONFIG = {
+  apiKey: process.env.AI_API_KEY,
+  baseURL: 'https://openrouter.ai/api/v1',
+} as const;
📝 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
const OPENAI_CONFIG = {
apiKey: process.env.AI_API_KEY,
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.AI_API_KEY,
baseURL: 'https://openrouter.ai/api/v1',
} as const;
const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17';
const openai = new OpenAI(OPENAI_CONFIG);
if (!process.env.AI_API_KEY) {
throw new Error('Environment variable AI_API_KEY is not set');
}
const OPENAI_CONFIG = {
apiKey: process.env.AI_API_KEY,
baseURL: 'https://openrouter.ai/api/v1',
} as const;
const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17';
const openai = new OpenAI(OPENAI_CONFIG);
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/ai-client.ts around lines 3 to 10, the code
initializes the OpenAI client using an API key from the environment without
checking if the key is present. To fail fast when AI_API_KEY is missing, add a
check right after reading the environment variable to verify it is defined. If
it is missing, throw an explicit error immediately before creating the OpenAI
instance to help with early detection and easier troubleshooting.

Comment on lines +4 to 8
data: unknown[];
executionTime: number;
rowCount: number;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Introduce a generic to preserve result typing

QueryResult.data is typed as unknown[], which throws away everything that chQuery’s generic gives you. Expose that information by parameterising both the interface and executeQuery().

-export interface QueryResult {
-  data: unknown[];
+export interface QueryResult<T = unknown> {
+  data: T[];
   executionTime: number;
   rowCount: number;
 }
 
-export async function executeQuery(sql: string): Promise<QueryResult> {
+export async function executeQuery<T = unknown>(sql: string): Promise<QueryResult<T>> {
   const queryStart = Date.now();
-  const result = await chQuery(sql);
+  const result = await chQuery<T>(sql);

This is a zero-risk change for current callers (type argument infers to unknown) while giving stronger typing to new code.

📝 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
data: unknown[];
executionTime: number;
rowCount: number;
}
export interface QueryResult<T = unknown> {
data: T[];
executionTime: number;
rowCount: number;
}
export async function executeQuery<T = unknown>(sql: string): Promise<QueryResult<T>> {
const queryStart = Date.now();
const result = await chQuery<T>(sql);
// …rest of implementation remains unchanged
}
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/query-executor.ts around lines 4 to 8, the
QueryResult interface currently types the data property as unknown[], losing the
specific type information from chQuery's generic. To fix this, make the
QueryResult interface generic by adding a type parameter that defaults to
unknown, and use this parameter to type the data array. Also, update the
executeQuery() function to be generic and return a QueryResult with the
corresponding type parameter. This preserves existing behavior while enabling
stronger typing for new code.

Comment on lines +15 to +18
timeTaken: `${queryTime}ms`,
resultCount: result.length,
sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid leaking full SQL in production logs

Even truncated to 100 chars, raw SQL can expose PII or internal schema. Wrap the console.info behind a debug flag or redact parameters before logging.

-console.info('🔍 [Query Executor] Query completed', {
-  timeTaken: `${queryTime}ms`,
-  resultCount: result.length,
-  sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
-});
+if (process.env.NODE_ENV === 'development') {
+  console.info('🔍 [Query Executor] Query completed', {
+    timeTaken: `${queryTime}ms`,
+    resultCount: result.length,
+    sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
+  });
+}
📝 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
timeTaken: `${queryTime}ms`,
resultCount: result.length,
sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
});
if (process.env.NODE_ENV === 'development') {
console.info('🔍 [Query Executor] Query completed', {
timeTaken: `${queryTime}ms`,
resultCount: result.length,
sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
});
}
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/query-executor.ts around lines 15 to 18, the current
code logs raw SQL queries truncated to 100 characters, which can still expose
sensitive information. Modify the logging to either wrap the console.info call
behind a debug flag so it only logs in development or debug mode, or redact
sensitive parameters from the SQL string before logging to avoid leaking PII or
internal schema details.

Comment on lines +29 to 31
const { inviteMember, isInvitingMember } =
useOrganizationMembers(organizationId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

awaiting mutate is a bug – switch to mutateAsync or drop await

useOrganizationMembers returns inviteMemberMutation.mutate, whose return type is void.
await inviteMember(...) therefore resolves immediately and does not wait for the network request, which defeats the loading state and may surface unhandled errors.

-const { inviteMember, isInvitingMember } =
-  useOrganizationMembers(organizationId);
+const {
+  inviteMember: inviteMemberRaw,
+  isInvitingMember,
+} = useOrganizationMembers(organizationId);
+
+// Prefer the async variant if you need to await
+const inviteMember = inviteMemberRaw as unknown as (
+  Parameters<typeof inviteMemberRaw>[0]
+) => Promise<unknown>;

or simpler:

-await inviteMember({ ... });
+inviteMember({ ... }); // fire-and-forget

Consider exporting mutateAsync from the hook for cleaner semantics.

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

🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx
around lines 29 to 31, the code is incorrectly awaiting the mutate function
returned by useOrganizationMembers, which returns void and does not wait for the
network request. To fix this, update the hook to export mutateAsync instead of
mutate, then replace await inviteMember(...) with await inviteMemberAsync(...)
to properly await the network request and handle loading and errors correctly.

Comment on lines +100 to 103
if (!(imageSrc && completedCrop && imageRef.current)) {
toast.error('Please crop the image before uploading.');
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate crop dimensions before upload

completedCrop can be truthy while still having width === 0 or height === 0 (e.g., user closes the modal without moving the crop handles). A zero-sized crop will cause getCroppedImage to throw.

-    if (!(imageSrc && completedCrop && imageRef.current)) {
+    if (
+      !(
+        imageSrc &&
+        completedCrop &&
+        completedCrop.width > 0 &&
+        completedCrop.height > 0 &&
+        imageRef.current
+      )
+    ) {
📝 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
if (!(imageSrc && completedCrop && imageRef.current)) {
toast.error('Please crop the image before uploading.');
return;
}
if (
!(
imageSrc &&
completedCrop &&
completedCrop.width > 0 &&
completedCrop.height > 0 &&
imageRef.current
)
) {
toast.error('Please crop the image before uploading.');
return;
}
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx
around lines 100 to 103, the current check only verifies if completedCrop is
truthy but does not validate its dimensions. Update the condition to also check
that completedCrop.width and completedCrop.height are greater than zero before
proceeding. If either dimension is zero, show the error toast and return early
to prevent getCroppedImage from throwing due to invalid crop size.

Comment on lines +140 to 146
<FloppyDiskBackIcon
className="h-4 w-4"
size={16}
weight="fill"
/>
{isSaving ? 'Saving...' : 'Save Changes'}
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Disable save while secret empty

Prevent empty secrets from being persisted.

-                disabled={isSaving}
+                disabled={isSaving || !localWebhookSecret.trim()}
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 140 to 146, the save button should be disabled when the secret is
empty to prevent saving invalid data. Add a condition to the Button component's
disabled property that checks if the secret value is empty or invalid, ensuring
the button is disabled in that case.

Comment on lines +172 to 174
window.open('https://dashboard.stripe.com/webhooks', '_blank')
}
size="sm"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden window.open call

Add noopener,noreferrer to block reverse-tab-nabbing.

-                window.open('https://dashboard.stripe.com/webhooks', '_blank')
+                window.open(
+                  'https://dashboard.stripe.com/webhooks',
+                  '_blank',
+                  'noopener,noreferrer'
+                )
📝 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
window.open('https://dashboard.stripe.com/webhooks', '_blank')
}
size="sm"
window.open(
'https://dashboard.stripe.com/webhooks',
'_blank',
'noopener,noreferrer'
)
}
size="sm"
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 172 to 174, the window.open call lacks security attributes. Update
the window.open call to include 'noopener,noreferrer' in the third argument to
prevent reverse-tab-nabbing attacks by adding "noopener,noreferrer" as the
window features string.

Comment on lines +192 to +196
<Button
className="flex-1"
disabled={!localWebhookSecret || hasChanges}
onClick={onNext}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Block navigation during save

While the mutation is in flight, disable the “Continue” button to avoid state desync.

-          disabled={!localWebhookSecret || hasChanges}
+          disabled={!localWebhookSecret || hasChanges || isSaving}
📝 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
<Button
className="flex-1"
disabled={!localWebhookSecret || hasChanges}
onClick={onNext}
>
<Button
className="flex-1"
disabled={!localWebhookSecret || hasChanges || isSaving}
onClick={onNext}
>
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 192 to 196, the "Continue" button should be disabled while the
mutation is in progress to prevent state desynchronization. Update the disabled
prop to also check if the mutation is loading or in flight, and disable the
button accordingly until the save operation completes.

@vercel

vercel Bot commented Jul 25, 2025

Copy link
Copy Markdown

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

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 25, 2025 5:01pm

Comment thread apps/dashboard/public/databuddy.js Dismissed
Comment thread apps/docs/app/llms.txt/route.ts Fixed
@izadoesdev
izadoesdev merged commit d01ae59 into databuddy-analytics:main Jul 25, 2025
7 of 8 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Dec 4, 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.

3 participants