docs/agents/portal-code-style.md

Portal Conventions

Nuxt 3 SSR app. Vue 3 Composition API, TanStack Query, Pinia, shadcn-nuxt, Tailwind, i18n, Supabase.

Portal Conventions

Nuxt 3 SSR app. Vue 3 Composition API, TanStack Query, Pinia, shadcn-nuxt, Tailwind, i18n, Supabase.

SSR & Hydration

  • Write SSR-friendly code: no window/document/localStorage without onMounted or <ClientOnly>
  • Never cause hydration errors

Auto-Imports

  • Never import from #components - Nuxt auto-imports all (check .nuxt/components.d.ts)
  • Exception: multiple <script> tags require explicit imports

Translations

  • NO dynamic keys - use static strings: t('key.subkey')
  • Component-specific: local <i18n> blocks
  • Composables: pass t function as param, add to global i18n/*.json
  • Respect translation rules in ../../.cursor/rules/translations.mdc

Type Safety

  • No as unknown, as any, @ts-ignore - fix at source
  • Use Zod for validation of JSON objects

Code Quality

  • Before complex tasks: analyze context, ask questions
  • After changes: verify existing flows work, consolidate logic (merge computed/props/emits), rename if purpose changes
  • <script setup lang="ts"> only, Composition API, prefix unused vars with _
  • Respect Nuxt naming: composables useX, stores useXStore, files in correct dirs (middleware/, plugins/, utils/)

Control Flow

  • Avoid Vue watchers by default. Prefer render precedence, computed state, events, or lifecycle hooks. If a watcher is needed, prefer VueUse helpers where applicable.

API Calls

  • Use callEdgeFunction wrapper: callEdgeFunction(() => honoClient.api.endpoint.$post())
  • Access via useHonoClient() composable

Async Buttons

  • Wrap in useMutation, use isPending to disable button

Styling

  • Use cn() for class composition
  • NEVER hard code colors, use Tailwind tokens only