Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 0 additions & 48 deletions .cursorrules

This file was deleted.

167 changes: 167 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
Concise rules for building accessible, fast, delightful UIs Use MUST/SHOULD/NEVER to guide decisions

## Interactions

- Keyboard
- MUST: Full keyboard support per [WAI-ARIA APG](https://wwww3org/WAI/ARIA/apg/patterns/)
- MUST: Visible focus rings (`:focus-visible`; group with `:focus-within`)
- MUST: Manage focus (trap, move, and return) per APG patterns
- Targets & input
- MUST: Hit target ≥24px (mobile ≥44px) If visual <24px, expand hit area
- MUST: Mobile `<input>` font-size ≥16px or set:
```html
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover"
/>
```
- NEVER: Disable browser zoom
- MUST: `touch-action: manipulation` to prevent double-tap zoom; set `-webkit-tap-highlight-color` to match design
- Inputs & forms (behavior)
- MUST: Hydration-safe inputs (no lost focus/value)
- NEVER: Block paste in `<input>/<textarea>`
- MUST: Loading buttons show spinner and keep original label
- MUST: Enter submits focused text input In `<textarea>`, ⌘/Ctrl+Enter submits; Enter adds newline
- MUST: Keep submit enabled until request starts; then disable, show spinner, use idempotency key
- MUST: Don’t block typing; accept free text and validate after
- MUST: Allow submitting incomplete forms to surface validation
- MUST: Errors inline next to fields; on submit, focus first error
- MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode`
- SHOULD: Disable spellcheck for emails/codes/usernames
- SHOULD: Placeholders end with ellipsis and show example pattern (eg, `+1 (123) 456-7890`, `sk-012345…`)
- MUST: Warn on unsaved changes before navigation
- MUST: Compatible with password managers & 2FA; allow pasting one-time codes
- MUST: Trim values to handle text expansion trailing spaces
- MUST: No dead zones on checkboxes/radios; label+control share one generous hit target
- State & navigation
- MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels) Prefer libs like [nuqs](https://nuqs.dev)
- MUST: Back/Forward restores scroll
- MUST: Links are links—use `<a>/<Link>` for navigation (support Cmd/Ctrl/middle-click)
- Feedback
- SHOULD: Optimistic UI; reconcile on response; on failure show error and rollback or offer Undo
- MUST: Confirm destructive actions or provide Undo window
- MUST: Use polite `aria-live` for toasts/inline validation
- SHOULD: Ellipsis (`…`) for options that open follow-ups (eg, “Rename…”)
- Touch/drag/scroll
- MUST: Design forgiving interactions (generous targets, clear affordances; avoid finickiness)
- MUST: Delay first tooltip in a group; subsequent peers no delay
- MUST: Intentional `overscroll-behavior: contain` in modals/drawers
- MUST: During drag, disable text selection and set `inert` on dragged element/containers
- MUST: No “dead-looking” interactive zones—if it looks clickable, it is
- Autofocus
- SHOULD: Autofocus on desktop when there’s a single primary input; rarely on mobile (to avoid layout shift)

## Animation

- MUST: Honor `prefers-reduced-motion` (provide reduced variant)
- SHOULD: Prefer CSS > Web Animations API > JS libraries
- MUST: Animate compositor-friendly props (`transform`, `opacity`); avoid layout/repaint props (`top/left/width/height`)
- SHOULD: Animate only to clarify cause/effect or add deliberate delight
- SHOULD: Choose easing to match the change (size/distance/trigger)
- MUST: Animations are interruptible and input-driven (avoid autoplay)
- MUST: Correct `transform-origin` (motion starts where it “physically” should)

## Layout

- SHOULD: Optical alignment; adjust by ±1px when perception beats geometry
- MUST: Deliberate alignment to grid/baseline/edges/optical centers—no accidental placement
- SHOULD: Balance icon/text lockups (stroke/weight/size/spacing/color)
- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
- MUST: Respect safe areas (use env(safe-area-inset-\*))
- MUST: Avoid unwanted scrollbars; fix overflows

## Content & Accessibility

- SHOULD: Inline help first; tooltips last resort
- MUST: Skeletons mirror final content to avoid layout shift
- MUST: `<title>` matches current context
- MUST: No dead ends; always offer next step/recovery
- MUST: Design empty/sparse/dense/error states
- SHOULD: Curly quotes (“ ”); avoid widows/orphans
- MUST: Tabular numbers for comparisons (`font-variant-numeric: tabular-nums` or a mono like Geist Mono)
- MUST: Redundant status cues (not color-only); icons have text labels
- MUST: Don’t ship the schema—visuals may omit labels but accessible names still exist
- MUST: Use the ellipsis character `…` (not ``)
- MUST: `scroll-margin-top` on headings for anchored links; include a “Skip to content” link; hierarchical `<h1–h6>`
- MUST: Resilient to user-generated content (short/avg/very long)
- MUST: Locale-aware dates/times/numbers/currency
- MUST: Accurate names (`aria-label`), decorative elements `aria-hidden`, verify in the Accessibility Tree
- MUST: Icon-only buttons have descriptive `aria-label`
- MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA
- SHOULD: Right-clicking the nav logo surfaces brand assets
- MUST: Use non-breaking spaces to glue terms: `10&nbsp;MB`, `⌘&nbsp;+&nbsp;K`, `Vercel&nbsp;SDK`

## Performance

- SHOULD: Test iOS Low Power Mode and macOS Safari
- MUST: Measure reliably (disable extensions that skew runtime)
- MUST: Track and minimize re-renders (React DevTools/React Scan)
- MUST: Profile with CPU/network throttling
- MUST: Batch layout reads/writes; avoid unnecessary reflows/repaints
- MUST: Mutations (`POST/PATCH/DELETE`) target <500 ms
- SHOULD: Prefer uncontrolled inputs; make controlled loops cheap (keystroke cost)
- MUST: Virtualize large lists (eg, `virtua`)
- MUST: Preload only above-the-fold images; lazy-load the rest
- MUST: Prevent CLS from images (explicit dimensions or reserved space)

## Design

- SHOULD: Layered shadows (ambient + direct)
- SHOULD: Crisp edges via semi-transparent borders + shadows
- SHOULD: Nested radii: child ≤ parent; concentric
- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
- MUST: Accessible charts (color-blind-friendly palettes)
- MUST: Meet contrast—prefer [APCA](https://apcacontrastcom/) over WCAG 2
- MUST: Increase contrast on `:hover/:active/:focus`
- SHOULD: Match browser UI to bg
- SHOULD: Avoid gradient banding (use masks when needed)

## Code Style and Structure:

- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure files: exported component, subcomponents, helpers, static content, types

## Naming Conventions:

- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components

## TypeScript Usage:

- Use TypeScript for all code; prefer types over interfaces UNLESS specificially for intersections (Typescript Performance)
- Avoid enums; use maps instead
- Use functional components with types

## Syntax and Formatting:

- Use the "function" keyword for pure functions
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
- Use declarative JSX

## Error Handling and Validation:

- Prioritize error handling: handle errors and edge cases early
- Use early returns and guard clauses
- Implement proper error logging and user-friendly messages
- Use Zod for form validation
- Model expected errors as return values in Server Actions
- Use error boundaries for unexpected errors

## Performance Optimization:

- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
- Wrap client components in Suspense with fallback
- Use dynamic loading for non-critical components
- Optimize images: use WebP format, include size data, implement lazy loading

## Key Conventions:

- Use 'nuqs' for URL search parameter state management
- Optimize Web Vitals (LCP, CLS, FID)
- Limit 'use client':
- Favor server components and Next.js SSR
- Use only for Web API access in small components
- Avoid for data fetching or state management
8 changes: 4 additions & 4 deletions apps/app/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@v1/app",
"name": "@d0/app",
"version": "0.1.0",
"private": true,
"scripts": {
Expand All @@ -18,9 +18,9 @@
"@polar-sh/sdk": "^0.26.1",
"@tanstack/react-form": "^0.33.0",
"@tanstack/zod-form-adapter": "^0.33.0",
"@v1/analytics": "workspace:*",
"@v1/backend": "workspace:*",
"@v1/ui": "workspace:*",
"@d0/analytics": "workspace:*",
"@d0/backend": "workspace:*",
"@d0/ui": "workspace:*",
"@xixixao/uploadstuff": "^0.0.5",
"convex": "^1.19.2",
"dub": "^0.36.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
} from "@v1/ui/select";
} from "@d0/ui/select";
import { Languages } from "lucide-react";

export function LanguageSwitcher() {
Expand Down
10 changes: 5 additions & 5 deletions apps/app/src/app/[locale]/(dashboard)/_components/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

import { useAuthActions } from "@convex-dev/auth/react";
import { CheckoutLink } from "@convex-dev/polar/react";
import { api } from "@v1/backend/convex/_generated/api";
import { Button, buttonVariants } from "@v1/ui/button";
import { api } from "@d0/backend/convex/_generated/api";
import { Button, buttonVariants } from "@d0/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@v1/ui/dropdown-menu";
import { Logo } from "@v1/ui/logo";
import { cn } from "@v1/ui/utils";
} from "@d0/ui/dropdown-menu";
import { Logo } from "@d0/ui/logo";
import { cn } from "@d0/ui/utils";
import { type Preloaded, usePreloadedQuery } from "convex/react";
import {
Check,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
} from "@v1/ui/select";
import { cn } from "@v1/ui/utils";
} from "@d0/ui/select";
import { cn } from "@d0/ui/utils";
import { Monitor, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/app/[locale]/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
import { api } from "@v1/backend/convex/_generated/api";
import { api } from "@d0/backend/convex/_generated/api";
import { fetchQuery, preloadQuery } from "convex/nextjs";
import { redirect } from "next/navigation";
import { Navigation } from "./_components/navigation";
Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/app/[locale]/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export const metadata = {
title: "Home",
};

import { buttonVariants } from "@v1/ui/button";
import { cn } from "@v1/ui/utils";
import { buttonVariants } from "@d0/ui/button";
import { cn } from "@d0/ui/utils";
import { ExternalLink, Plus } from "lucide-react";

export default async function Page() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
import { api } from "@v1/backend/convex/_generated/api";
import { Button } from "@v1/ui/button";
import { Switch } from "@v1/ui/switch";
import { api } from "@d0/backend/convex/_generated/api";
import { Button } from "@d0/ui/button";
import { Switch } from "@d0/ui/switch";
import { useQuery } from "convex/react";
import { useState } from "react";

Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/app/[locale]/(dashboard)/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { I18nProviderClient, useScopedI18n } from "@/locales/client";
import { buttonVariants } from "@v1/ui/button";
import { cn } from "@v1/ui/utils";
import { buttonVariants } from "@d0/ui/button";
import { cn } from "@d0/ui/utils";
import Link from "next/link";
import { usePathname } from "next/navigation";

Expand Down
14 changes: 7 additions & 7 deletions apps/app/src/app/[locale]/(dashboard)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useScopedI18n } from "@/locales/client";
import { useAuthActions } from "@convex-dev/auth/react";
import { useForm } from "@tanstack/react-form";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { api } from "@v1/backend/convex/_generated/api";
import type { Id } from "@v1/backend/convex/_generated/dataModel";
import * as validators from "@v1/backend/convex/utils/validators";
import { Button } from "@v1/ui/button";
import { Input } from "@v1/ui/input";
import { UploadInput } from "@v1/ui/upload-input";
import { useDoubleCheck } from "@v1/ui/utils";
import { api } from "@d0/backend/convex/_generated/api";
import type { Id } from "@d0/backend/convex/_generated/dataModel";
import * as validators from "@d0/backend/convex/utils/validators";
import { Button } from "@d0/ui/button";
import { Input } from "@d0/ui/input";
import { UploadInput } from "@d0/ui/upload-input";
import { useDoubleCheck } from "@d0/ui/utils";
import type { UploadFileResponse } from "@xixixao/uploadstuff/react";
import { useAction, useMutation, useQuery } from "convex/react";
import { Upload } from "lucide-react";
Expand Down
6 changes: 3 additions & 3 deletions apps/app/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "@v1/ui/globals.css";
import "@d0/ui/globals.css";
import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server";
import { TooltipProvider } from "@v1/ui/tooltip";
import { cn } from "@v1/ui/utils";
import { TooltipProvider } from "@d0/ui/tooltip";
import { cn } from "@d0/ui/utils";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from "geist/font/sans";
import type { Metadata } from "next";
Expand Down
8 changes: 4 additions & 4 deletions apps/app/src/app/[locale]/onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { useForm } from "@tanstack/react-form";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { api } from "@v1/backend/convex/_generated/api";
import * as validators from "@v1/backend/convex/utils/validators";
import { Button } from "@v1/ui/button";
import { Input } from "@v1/ui/input";
import { api } from "@d0/backend/convex/_generated/api";
import * as validators from "@d0/backend/convex/utils/validators";
import { Button } from "@d0/ui/button";
import { Input } from "@d0/ui/input";
import { useMutation, useQuery } from "convex/react";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/google-signin.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useAuthActions } from "@convex-dev/auth/react";
import { Button } from "@v1/ui/button";
import { Button } from "@d0/ui/button";

export function GoogleSignin() {
const { signIn } = useAuthActions();
Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/components/sign-out.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { useAuthActions } from "@convex-dev/auth/react";
import { Button } from "@v1/ui/button";
import { Icons } from "@v1/ui/icons";
import { Button } from "@d0/ui/button";
import { Icons } from "@d0/ui/icons";

export function SignOut() {
const { signOut } = useAuthActions();
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/types.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Doc } from "@v1/backend/convex/_generated/dataModel";
import type { Doc } from "@d0/backend/convex/_generated/dataModel";

export type User = Doc<"users"> & {
avatarUrl?: string;
Expand Down
Loading
Loading