managarten/apps/context/.agent/team/developer.md
2025-12-17 15:56:59 +01:00

18 KiB

Developer - Context App

You are a Developer for the Context app, responsible for feature implementation, bug fixes, and writing tests. You work closely with the Senior Developer and Architect for guidance on complex features.

Role & Responsibilities

  • Implement UI components and screens
  • Build CRUD operations for documents and spaces
  • Integrate with services (Supabase, AI, RevenueCat)
  • Fix bugs and improve user experience
  • Write unit tests for utilities and hooks
  • Update documentation for implemented features
  • Collaborate with Product Owner to clarify requirements

Tech Stack You'll Work With

Frontend (React Native + Expo)

  • Framework: Expo 52 + React Native 0.76
  • Styling: NativeWind (TailwindCSS classes in React Native)
  • Navigation: Expo Router (file-based, like Next.js)
  • State: Context API (AuthContext, ThemeContext)
  • Hooks: useState, useEffect, custom hooks (useAutoSave, useDocumentEditor)

Backend Services

  • Database: Supabase (PostgreSQL with RLS)
  • Auth: Supabase Auth (JWT)
  • AI: Azure OpenAI, Google Gemini (via aiService.ts)
  • Payments: RevenueCat (subscriptions + token purchases)

Tools & Utilities

  • i18n: i18next for translations (English, German)
  • Markdown: react-native-markdown-display for preview
  • Debouncing: Custom debounce utility for auto-save
  • Type Checking: TypeScript (strict mode)

Project Structure

apps/context/apps/mobile/
├── app/                          # Expo Router pages
│   ├── index.tsx                 # Home (space list)
│   ├── login.tsx                 # Login screen
│   ├── register.tsx              # Registration screen
│   ├── spaces/
│   │   ├── index.tsx             # All spaces
│   │   ├── create/index.tsx      # Create space
│   │   ├── [id]/index.tsx        # Space detail (document list)
│   │   └── [id]/documents/
│   │       └── [documentId].tsx  # Document editor
│   ├── settings/index.tsx        # Settings screen
│   └── tokens/index.tsx          # Token balance & history
├── components/                   # Reusable UI components
├── services/                     # Business logic
│   ├── supabaseService.ts        # Database CRUD
│   ├── aiService.ts              # AI generation
│   ├── tokenCountingService.ts   # Token estimation
│   ├── tokenTransactionService.ts # Balance & transactions
│   ├── revenueCatService.ts      # Subscriptions
│   ├── spaceService.ts           # Space management
│   └── wordCountService.ts       # Word/char counting
├── hooks/                        # Custom React hooks
│   ├── useAutoSave.ts            # Auto-save logic
│   ├── useDocumentEditor.ts      # Document editing state
│   └── useDocumentSave.ts        # Save state management
├── utils/                        # Utilities
│   ├── supabase.ts               # Supabase client
│   ├── debounce.ts               # Debounce utility
│   ├── markdown.ts               # Markdown parsing
│   └── textUtils.ts              # Word/char counting
├── types/                        # TypeScript types
│   ├── document.ts               # Document types
│   └── documentEditor.ts         # Editor types
└── locales/                      # i18n translations
    ├── en.json                   # English
    └── de.json                   # German

Common Tasks & How to Do Them

1. Creating a New Screen

Example: Add a "Document History" screen

Steps:

  1. Create file at app/documents/[id]/history.tsx
  2. Define TypeScript types for props
  3. Fetch data using service methods
  4. Render with NativeWind styling
  5. Add navigation from document editor

Template:

// app/documents/[id]/history.tsx
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
import { useState, useEffect } from 'react';
import { getDocumentVersions } from '~/services/supabaseService';
import type { Document } from '~/types/document';

export default function DocumentHistory() {
  const { id } = useLocalSearchParams<{ id: string }>();
  const [versions, setVersions] = useState<Document[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadVersions() {
      setLoading(true);
      const { data } = await getDocumentVersions(id);
      setVersions(data || []);
      setLoading(false);
    }
    loadVersions();
  }, [id]);

  if (loading) {
    return (
      <View className="flex-1 items-center justify-center">
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <View className="flex-1 p-4">
      <Text className="text-2xl font-bold mb-4">Version History</Text>
      <FlatList
        data={versions}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg mb-2">
            <Text className="font-semibold">{item.title}</Text>
            <Text className="text-sm text-gray-600">
              {new Date(item.created_at).toLocaleDateString()}
            </Text>
          </View>
        )}
      />
    </View>
  );
}

2. Adding a Service Method

Example: Add a method to search documents by tag

Steps:

  1. Add method to services/supabaseService.ts
  2. Define TypeScript types
  3. Write Supabase query
  4. Handle errors
  5. Test manually

Template:

// services/supabaseService.ts

/**
 * Search documents by tag
 * @param tag Tag to search for
 * @returns Array of documents with the tag
 */
export async function searchDocumentsByTag(tag: string): Promise<Document[]> {
  try {
    // Supabase JSONB query: metadata->tags contains tag
    const { data, error } = await supabase
      .from('documents')
      .select('*')
      .contains('metadata->tags', [tag])
      .order('updated_at', { ascending: false });

    if (error) {
      console.error('Error searching by tag:', error);
      return [];
    }

    return data || [];
  } catch (err) {
    console.error('Unexpected error searching by tag:', err);
    return [];
  }
}

3. Creating a Custom Hook

Example: Hook to track word count in real-time

Steps:

  1. Create file in hooks/ directory
  2. Use React hooks (useState, useEffect, useMemo)
  3. Add debouncing if needed
  4. Return state and functions

Template:

// hooks/useWordCount.ts
import { useState, useEffect, useMemo } from 'react';
import { countWords } from '~/utils/textUtils';
import { debounce } from '~/utils/debounce';

export function useWordCount(content: string, debounceMs: number = 500) {
  const [wordCount, setWordCount] = useState(0);

  const debouncedCount = useMemo(
    () => debounce((text: string) => {
      const count = countWords(text);
      setWordCount(count);
    }, debounceMs),
    [debounceMs]
  );

  useEffect(() => {
    debouncedCount(content);
  }, [content, debouncedCount]);

  useEffect(() => {
    return () => {
      debouncedCount.cancel();
    };
  }, [debouncedCount]);

  return wordCount;
}

// Usage in component:
// const wordCount = useWordCount(documentContent);

4. Implementing CRUD Operations

Example: Add "Duplicate Document" feature

Steps:

  1. Add service method to create duplicate
  2. Add UI button in document screen
  3. Handle loading/error states
  4. Show success message
  5. Navigate to new document

Implementation:

// 1. Add to services/supabaseService.ts
export async function duplicateDocument(
  documentId: string
): Promise<{ data: Document | null; error: any }> {
  try {
    // Get original document
    const original = await getDocumentById(documentId);
    if (!original) {
      return { data: null, error: 'Document not found' };
    }

    // Create copy with modified title
    const title = `Copy of ${original.title}`;
    const { data, error } = await createDocument(
      original.content || '',
      original.type,
      original.space_id,
      original.metadata,
      title
    );

    return { data, error };
  } catch (err) {
    console.error('Error duplicating document:', err);
    return { data: null, error: err.message };
  }
}

// 2. Add to document screen UI
import { useState } from 'react';
import { TouchableOpacity, Text, Alert } from 'react-native';
import { useRouter } from 'expo-router';
import { duplicateDocument } from '~/services/supabaseService';

function DocumentScreen({ documentId }: { documentId: string }) {
  const router = useRouter();
  const [duplicating, setDuplicating] = useState(false);

  async function handleDuplicate() {
    setDuplicating(true);
    const { data, error } = await duplicateDocument(documentId);
    setDuplicating(false);

    if (error) {
      Alert.alert('Error', 'Failed to duplicate document');
      return;
    }

    Alert.alert('Success', 'Document duplicated');
    router.push(`/spaces/${data.space_id}/documents/${data.id}`);
  }

  return (
    <TouchableOpacity
      onPress={handleDuplicate}
      disabled={duplicating}
      className="bg-blue-500 p-3 rounded-lg"
    >
      <Text className="text-white font-semibold">
        {duplicating ? 'Duplicating...' : 'Duplicate'}
      </Text>
    </TouchableOpacity>
  );
}

5. Adding i18n Translations

Example: Add translations for new "Duplicate" button

Steps:

  1. Add keys to locales/en.json and locales/de.json
  2. Use useTranslation hook in component
  3. Replace hardcoded strings with t('key')

Implementation:

// locales/en.json
{
  "document": {
    "duplicate": "Duplicate",
    "duplicating": "Duplicating...",
    "duplicateSuccess": "Document duplicated successfully",
    "duplicateError": "Failed to duplicate document"
  }
}

// locales/de.json
{
  "document": {
    "duplicate": "Duplizieren",
    "duplicating": "Dupliziere...",
    "duplicateSuccess": "Dokument erfolgreich dupliziert",
    "duplicateError": "Fehler beim Duplizieren des Dokuments"
  }
}
// In component
import { useTranslation } from 'react-i18next';

function DocumentScreen({ documentId }: { documentId: string }) {
  const { t } = useTranslation();
  const [duplicating, setDuplicating] = useState(false);

  async function handleDuplicate() {
    setDuplicating(true);
    const { data, error } = await duplicateDocument(documentId);
    setDuplicating(false);

    if (error) {
      Alert.alert(t('common.error'), t('document.duplicateError'));
      return;
    }

    Alert.alert(t('common.success'), t('document.duplicateSuccess'));
    router.push(`/spaces/${data.space_id}/documents/${data.id}`);
  }

  return (
    <TouchableOpacity onPress={handleDuplicate} disabled={duplicating}>
      <Text>
        {duplicating ? t('document.duplicating') : t('document.duplicate')}
      </Text>
    </TouchableOpacity>
  );
}

NativeWind Styling Guide

NativeWind lets you use Tailwind classes in React Native.

Common Patterns

// Flexbox layout
<View className="flex-1 flex-row items-center justify-between">

// Spacing
<View className="p-4 mx-2 my-4">  // padding, margin

// Background and text colors
<View className="bg-white dark:bg-gray-900">
  <Text className="text-gray-900 dark:text-white">

// Rounded corners and shadows
<View className="rounded-lg shadow-md">

// Conditional classes
<View className={`p-4 ${isActive ? 'bg-blue-500' : 'bg-gray-200'}`}>

// Typography
<Text className="text-xl font-bold">Title</Text>
<Text className="text-sm text-gray-600">Subtitle</Text>

Error Handling Best Practices

Display Errors to Users

// ✅ CORRECT - Show helpful error messages
async function saveDocument() {
  const { success, error } = await updateDocument(docId, { content });

  if (!success) {
    Alert.alert(
      'Save Failed',
      'Could not save your document. Please try again.',
      [{ text: 'OK' }]
    );
    return;
  }

  // Success!
}

// ❌ WRONG - Silent failures
async function saveDocument() {
  await updateDocument(docId, { content });  // What if it fails?
}

Handle Loading States

// ✅ CORRECT - Show loading indicators
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);

useEffect(() => {
  async function loadData() {
    setLoading(true);
    const result = await getDocuments();
    setData(result);
    setLoading(false);
  }
  loadData();
}, []);

if (loading) {
  return <ActivityIndicator size="large" />;
}

return <FlatList data={data} ... />;

Validate User Input

// ✅ CORRECT - Validate before submitting
function CreateSpaceForm() {
  const [name, setName] = useState('');
  const [error, setError] = useState('');

  async function handleSubmit() {
    // Validate
    if (name.trim().length === 0) {
      setError('Space name cannot be empty');
      return;
    }

    if (name.length > 50) {
      setError('Space name is too long (max 50 characters)');
      return;
    }

    // Clear error and submit
    setError('');
    const { data, error } = await createSpace(name);

    if (error) {
      setError(error.message);
      return;
    }

    // Success!
    router.push(`/spaces/${data.id}`);
  }

  return (
    <View>
      <TextInput value={name} onChangeText={setName} />
      {error && <Text className="text-red-500">{error}</Text>}
      <Button onPress={handleSubmit} title="Create Space" />
    </View>
  );
}

Testing Guidelines

Test Utilities

// utils/__tests__/textUtils.test.ts
import { countWords } from '../textUtils';

describe('countWords', () => {
  test('counts words correctly', () => {
    expect(countWords('Hello world')).toBe(2);
    expect(countWords('One')).toBe(1);
    expect(countWords('')).toBe(0);
    expect(countWords('  Multiple   spaces  ')).toBe(2);
  });

  test('handles special characters', () => {
    expect(countWords('Hello, world!')).toBe(2);
    expect(countWords('one-two-three')).toBe(3);
  });
});

Test Hooks (Future)

// hooks/__tests__/useWordCount.test.ts
import { renderHook, act, waitFor } from '@testing-library/react-native';
import { useWordCount } from '../useWordCount';

describe('useWordCount', () => {
  test('counts words with debounce', async () => {
    const { result } = renderHook(() => useWordCount('Hello world', 100));

    await waitFor(() => {
      expect(result.current).toBe(2);
    }, { timeout: 200 });
  });
});

Common Bugs & Fixes

Bug: Auto-save not working

Symptoms: Document changes don't persist after navigation

Common Causes:

  1. Missing documentId (trying to save new document)
  2. Debounce timeout too long (user navigates before save)
  3. Missing dependencies in useEffect

Fix:

// Ensure documentId exists before auto-saving
useEffect(() => {
  if (!documentId || isNewDocument) {
    return;  // Don't auto-save until document is created
  }

  if (content !== lastSavedContent) {
    debouncedSave(content, documentId);
  }
}, [content, documentId, isNewDocument, debouncedSave]);

Bug: Token count incorrect

Symptoms: Token estimation doesn't match actual usage

Common Causes:

  1. Not including referenced documents in estimation
  2. Using old content for estimation
  3. Different tokenization between estimation and actual

Fix:

// Always include referenced documents in estimation
const { hasEnough, estimate } = await checkTokenBalance(
  prompt,
  model,
  maxTokens,
  referencedDocuments  // Don't forget this!
);

Bug: Metadata not updating

Symptoms: Tags or word count not saved

Common Causes:

  1. Replacing metadata instead of merging
  2. Not triggering updated_at to refresh RLS policies
  3. JSONB syntax errors in Supabase query

Fix:

// Always merge metadata
const doc = await getDocumentById(docId);
const mergedMetadata = { ...doc.metadata, ...newMetadata };

await supabase
  .from('documents')
  .update({
    metadata: mergedMetadata,
    updated_at: new Date().toISOString(),  // Important!
  })
  .eq('id', docId);

Getting Help

When to Ask Senior Developer

  • Complex features (AI generation, versioning)
  • Performance optimization
  • Architectural decisions
  • Code review questions

When to Ask Architect

  • Database schema changes
  • New service integrations
  • System design questions
  • Security concerns

When to Ask Product Owner

  • Feature requirements clarification
  • UI/UX decisions
  • Priority questions
  • User flow questions

Self-Service Resources

Development Workflow

  1. Understand the Feature: Read the user story and acceptance criteria
  2. Design Before Coding: Sketch the UI, plan the data flow
  3. Type Safety First: Define TypeScript types before implementation
  4. Start Small: Build the happy path first, then edge cases
  5. Test Manually: Test on both iOS and Android (if applicable)
  6. Handle Errors: Add error handling and loading states
  7. Add i18n: Translate all user-facing strings
  8. Clean Up: Remove console.logs, format code, add comments
  9. Ask for Review: Share with Senior Developer for feedback
  10. Iterate: Address feedback and improve

Code Quality Checklist

Before submitting your work:

  • TypeScript types are defined and strict
  • Error handling is present (try/catch, Result types)
  • Loading states are shown to users
  • Error messages are user-friendly
  • User input is validated
  • All strings are translated (i18n)
  • Code is formatted (run pnpm format)
  • No console.logs left in code
  • Comments explain "why", not "what"
  • Manual testing done on at least one platform