Development
This guide covers development workflows, project structure, and how to contribute to LegalEase.
Repository Layout
legalease-ai/
├── frontend/ # Nuxt 4 dashboard (pnpm)
│ ├── app/
│ │ ├── components/
│ │ ├── composables/
│ │ ├── pages/
│ │ └── plugins/
│ └── nuxt.config.ts
├── functions/ # Firebase Cloud Functions (npm)
│ ├── src/
│ │ ├── flows/ # Genkit AI flows
│ │ ├── transcription/
│ │ └── index.ts
│ └── package.json
├── landing/ # Marketing + docs site (pnpm)
├── docker-compose.yml # Qdrant, Docling services
└── .mise.toml # Task definitions
Task Runner (mise)
LegalEase uses mise for task management. View available tasks:
mise tasks
Common Tasks
# Full local development stack
mise run dev:local
# Frontend only (requires emulators running)
mise run dev
# Install all dependencies
mise run install
# Build everything
mise run build
# Build functions only
mise run build:functions
# Start Docker services
mise run services:up
# Deploy to Firebase
mise run deploy
Frontend Development
The frontend is a Nuxt 4 application with Vue 3 Composition API.
Setup
cd frontend
pnpm install
Development Server
# With emulators (recommended)
NUXT_PUBLIC_USE_EMULATORS=true pnpm dev
# With production Firebase
pnpm dev
Key Patterns
Composables (app/composables/)
Firebase-integrated composables with SSR safety:
// All composables check for server-side rendering
if (import.meta.server) return null
// Use Nuxt app for Firebase instances
const { $firestore, $storage, $functions } = useNuxtApp()
| Composable | Purpose |
|---|---|
useAuth | Firebase Authentication |
useCases | Case CRUD operations |
useDocuments | Document upload/management |
useFirestore | Direct Firestore operations |
useAI | Firebase Functions calls |
Firebase Plugin (app/plugins/firebase.client.ts)
Initializes Firebase and connects to emulators when NUXT_PUBLIC_USE_EMULATORS=true.
Commands
pnpm dev # Development server
pnpm build # Production build
pnpm lint # ESLint check
pnpm typecheck # TypeScript check
Functions Development
Firebase Cloud Functions use Genkit for AI flows.
Setup
cd functions
npm install
Build and Deploy
npm run build # Compile TypeScript
firebase deploy --only functions
Key Patterns
Genkit Flows (src/flows/)
AI operations are defined as Genkit flows:
export const transcribeMediaFlow = ai.defineFlow(
{
name: 'transcribeMedia',
inputSchema: TranscriptionInput,
outputSchema: TranscriptionOutput
},
async (input) => {
// Flow implementation
}
)
Firestore Triggers (src/index.ts)
Background processing via triggers:
export const startTranscriptionJob = onDocumentWritten(
{ document: 'transcriptions/{transcriptionId}' },
async (event) => {
// Process when status changes to 'processing'
}
)
Provider Pattern (src/transcription/providers/)
Pluggable AI providers:
export interface TranscriptionProvider {
name: string
capabilities: ProviderCapabilities
canHandle(request: TranscriptionRequest): boolean
transcribe(request: TranscriptionRequest): Promise<TranscriptionResult>
}
Testing Functions Locally
# Start emulators with functions
firebase emulators:start --only functions,firestore,auth,storage
# Watch function logs
firebase emulators:start | grep "function\["
Landing Site Development
The landing/docs site uses Nuxt Content.
cd landing
pnpm install
pnpm dev
Content lives in content/ as Markdown files. Changes trigger hot reload.
Adding a New Feature
1. Plan the Data Model
Define Firestore collections and types:
// frontend/app/types/myfeature.ts
export interface MyFeature {
id?: string
caseId: string
// ... fields
createdAt?: Timestamp
}
2. Create Composable
// frontend/app/composables/useMyFeature.ts
export function useMyFeature() {
const { $firestore } = useNuxtApp()
async function create(data: MyFeatureInput) {
if (import.meta.server) throw new Error('Client only')
// Firestore operations
}
return { create, /* ... */ }
}
3. Add Cloud Function (if needed)
// functions/src/flows/myfeature.ts
export const myFeatureFlow = ai.defineFlow(
{ name: 'myFeature', inputSchema, outputSchema },
async (input) => {
// AI processing
}
)
// functions/src/index.ts
export const myFeature = onCallGenkit(
{ secrets: [googleAIApiKey] },
myFeatureFlow
)
4. Create UI Components
<!-- frontend/app/components/MyFeature.vue -->
<script setup lang="ts">
const { create } = useMyFeature()
</script>
Code Style
TypeScript
- Strict mode enabled
- Prefer
typeoverinterfacefor simple types - Use
import typefor type-only imports
Vue
- Composition API with
<script setup> - SSR safety checks in composables
- Nuxt UI components
ESLint
cd frontend
pnpm lint # Check
pnpm lint --fix # Auto-fix
Key rules:
max-lines: 500max-lines-per-function: 100no-console(except warn/error)
Contributing
- Fork the repository
- Create a feature branch
- Make changes with tests
- Run lint and build
- Submit PR with description
PR Checklist
- Code follows existing patterns
- SSR safety for new composables
- Documentation updated if needed
- Lint passes (
pnpm lint) - Build passes (
mise run build)
Debugging Tips
Frontend
- Vue DevTools for component state
- Network tab for Firebase requests
console.login composables (remove before commit)
Functions
- Firebase Emulator UI shows function logs
- Add
console.logwith prefixes:[FunctionName] message - Check for timeout issues in long operations
Common Issues
"X is not defined" runtime errors
- Check
import typevsimport- functions need runtime imports
Emulator connection fails
- Verify
NUXT_PUBLIC_USE_EMULATORS=trueis set - Check emulator ports are not in use
Functions timeout
- Increase
timeoutSecondsin function config - Check for infinite loops or hanging promises