6.7 KiB
Agent Guidelines for Helium
Project Overview
Helium is a Nuxt 3 application for effortless WebRTC screensharing with:
- Framework: Nuxt 4 (Vue 3 + TypeScript)
- Package Manager: pnpm
- Database: PostgreSQL with Drizzle ORM
- Auth: Clerk
- UI: shadcn-vue + Tailwind CSS v4
- State: Pinia stores
- i18n: @nuxtjs/i18n (English & Spanish)
Build/Dev/Test Commands
Development
pnpm dev # Start dev server
pnpm build # Build for production
pnpm preview # Preview production build
pnpm generate # Generate static site
Database
pnpm db:migrate # Generate and run migrations
UI Components
pnpm ui:add [component] # Add shadcn-vue component
Running Single Tests
Currently no test framework is configured. If adding tests, recommend Vitest for unit tests and Playwright for e2e.
Project Structure
app/
├── components/ # Vue components
│ ├── app/ # Application-specific components
│ └── ui/ # shadcn-vue UI components
├── composables/ # Vue composables (e.g., useWebSocketUrl)
├── layouts/ # Nuxt layouts
├── lib/ # Utilities, DB schema, types
│ ├── db/ # Database schema and utils
│ ├── schema/ # Zod validation schemas
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Helper functions
├── middleware/ # Route middleware (e.g., auth)
├── pages/ # File-based routing
├── plugins/ # Nuxt plugins
└── state/ # Pinia stores
server/
├── api/ # API endpoints
├── cron/ # Scheduled tasks
└── routes/ # Server routes (e.g., WebSocket)
drizzle/ # Database migrations
i18n/ # Translation files
public/ # Static assets
Code Style Guidelines
TypeScript
-
Always use TypeScript: No
.jsfiles. All code must be typed. -
Type imports: Use
import typefor type-only importsimport type { PrimitiveProps } from "reka-ui"; import type { HTMLAttributes } from "vue"; -
Explicit return types: Prefer explicit return types for functions
-
No
any: Avoidanytype. Useunknownor proper typing -
Interface vs Type: Use
interfacefor object shapes,typefor unions/intersections
Imports
-
Path aliases: Use
@/for app directory imports and~/for root importsimport { cn } from "@/lib/utils"; import { schema } from "~/lib/schema/new-preset"; -
Import order:
- External packages
- Vue/Nuxt imports
- Type imports
- Internal utilities
- Components
- Relative imports
Vue Components
-
Script Setup: Always use
<script setup lang="ts"> -
Props: Define with TypeScript interfaces
interface Props extends PrimitiveProps { variant?: ButtonVariants["variant"]; size?: ButtonVariants["size"]; class?: HTMLAttributes["class"]; } const props = withDefaults(defineProps<Props>(), { as: "button", }); -
Template: Keep templates clean, extract complex logic to composables
-
Component naming: PascalCase for components, kebab-case for files in multi-word components
Naming Conventions
- Files:
- Components: PascalCase (e.g.,
Button.vue,EditPresetDialog.vue) - Composables: camelCase starting with
use(e.g.,useWebSocketUrl.ts) - API routes: kebab-case with HTTP method (e.g.,
[id].get.ts,[id].delete.ts) - Database: camelCase for tables (e.g.,
schema.ts,migrate.ts)
- Components: PascalCase (e.g.,
- Variables: camelCase
- Constants: UPPER_SNAKE_CASE for true constants, camelCase for config objects
- Functions: camelCase, descriptive verbs (e.g.,
createPreset,getPresetById) - Components: PascalCase
Database (Drizzle)
-
Schema location:
app/lib/db/schema.ts -
Relations: Define relations separately after table definitions
-
Column naming: camelCase in TypeScript, snake_case in SQL
export const presets = pgTable("presets", { createdBy: text("created_by").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); -
Migrations: Always generate migrations with
pnpm db:migrate
API Routes (Server)
-
Event handlers: Use
defineEventHandler -
Auth: Check
event.context.auth()for authenticationconst { isAuthenticated, userId } = event.context.auth(); if (!isAuthenticated || !userId) { throw createError({ statusCode: 401, statusMessage: "Unauthorized" }); } -
Error handling: Use
createErrorfor HTTP errors -
Validation: Use Zod schemas from
app/lib/schema/ -
Response format: Consistent structure with
successanddata/messagefieldsreturn { success: true, data: preset, };
Error Handling
-
Server: Use
createError()with proper status codes -
Client: Use
toastfromvue-sonnerfor user feedbacktry { await $fetch("/api/presets", { method: "POST" }); toast.success(t("presetCreatedSuccessfully")); } catch (error) { toast.error(t("failedToCreatePreset")); } -
Validation: Use Zod with
.safeParse()and handle errors -
Always catch promises: Never leave promises unhandled
Formatting
- Indentation: 2 spaces
- Quotes: Double quotes for strings
- Semicolons: Optional but consistent within files
- Line length: Aim for 80-100 characters, break at 120
- Trailing commas: Use in multiline objects/arrays
- Comments: Prevent comments where possible, if the code is not understandable by itself.
State Management
- Pinia stores: Located in
app/state/ - Store naming: Descriptive (e.g.,
streamer.ts,viewer.ts) - Composables: Prefer composables over stores for simple state
i18n
-
Always use: Use
useI18n()composable for all user-facing strings. That is, you must localize any user-facing text.const { t } = useI18n(); <h1>{{ t('presets') }}</h1> -
Keys: camelCase (e.g.,
createNewPreset,deletePresetConfirm) -
Files:
i18n/locales/en.jsonandi18n/locales/es.json
Important Notes
- Authentication: All protected routes must use auth middleware
- WebSocket: Available at
/ws/signalingfor real-time communication - Environment: Use
.envfile (not committed) for DATABASE_URL and Clerk keys - Clerk: Auth provider - use
useAuth()anduseUser()composables - Styling: Use Tailwind utility classes,
cn()helper for conditional classes