# Feature Development Guide

This guide documents the end-to-end workflow for building features in your composable monorepo.

## Quick Start

```bash
# 1. Create feature branch
git checkout -b feature/my-feature

# 2. Implement API endpoint (use Wrap + Bind patterns)
# 3. Implement frontend (use design system + auth)
# 4. Write tests
# 5. Run quality checks
./scripts/quality.sh

# 6. Commit and push
git add -A && git commit -m "feat: my feature"
git push -u origin feature/my-feature
```

## Architecture Overview

```
{{PROJECT_NAME}}/
├── pkg/                         # Shared Go packages (chassis)
│   ├── app/                     # Wrap, Bind, Health patterns
│   ├── httperror/               # Typed HTTP errors
│   ├── httpresponse/            # JSON response envelope
│   └── auth/                    # JWT + API key validation
├── packages/                    # Shared TypeScript packages
│   ├── ui/                      # Design system components
│   ├── layout/                  # DashboardShell, Sidebar
│   ├── auth/                    # AuthProvider, useAuth
│   └── api-client/              # Generated TypeScript client
├── services/                    # Go backend services
└── apps/                        # Frontend applications
```

## Backend Patterns

### Wrap Pattern

Convert error-returning handlers to `http.HandlerFunc`:

```go
import "{{PROJECT_NAME}}/pkg/app"

func (h *Handler) GetItem() http.HandlerFunc {
    return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
        // Return errors - they become proper HTTP responses
        item, err := h.svc.Get(r.Context(), chi.URLParam(r, "id"))
        if err != nil {
            return httperror.NotFound("item not found")
        }
        httpresponse.JSON(w, http.StatusOK, item)
        return nil
    })
}
```

### Bind Pattern

Parse and validate request bodies in one call:

```go
import "{{PROJECT_NAME}}/pkg/app"

func (h *Handler) CreateItem() http.HandlerFunc {
    type request struct {
        Name string `json:"name" validate:"required,min=1,max=100"`
        Type string `json:"type" validate:"required,oneof=a b c"`
    }

    return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
        var req request
        if err := app.BindAndValidate(r, &req); err != nil {
            return err // Validation errors become 400 with details
        }
        // Use req.Name, req.Type...
    })
}
```

### HTTPError Sentinels

Use typed errors that map to HTTP status codes:

```go
import "{{PROJECT_NAME}}/pkg/httperror"

httperror.BadRequest("invalid input")      // 400
httperror.Unauthorized("not authenticated") // 401
httperror.Forbidden("access denied")        // 403
httperror.NotFound("resource not found")    // 404
httperror.Conflict("already exists")        // 409
httperror.Internal("something went wrong")  // 500

// With formatted messages
httperror.NotFoundf("user %s not found", userID)

// With details
httperror.WithDetails(
    httperror.BadRequest("validation failed"),
    map[string]string{"name": "required"},
)
```

### Auth Context

Access the authenticated user from request context:

```go
import "{{PROJECT_NAME}}/pkg/auth"

user, ok := auth.GetUser(r.Context())
if !ok {
    return httperror.Unauthorized("not authenticated")
}

// user.ID, user.Email, user.Roles available
```

## Frontend Patterns

### Design System Components

Import from the shared UI package:

```tsx
import { Button, Card, Input, Label, Badge } from '@{{PROJECT_NAME}}/ui';
import { DashboardShell, Sidebar, Header } from '@{{PROJECT_NAME}}/layout';
```

### Auth Hook

Access auth state and actions:

```tsx
import { useAuth } from '@{{PROJECT_NAME}}/auth';

function MyComponent() {
    const { user, isLoading, login, logout, refreshUser } = useAuth();

    if (isLoading) return <div>Loading...</div>;
    if (!user) return <LoginPrompt />;

    return <div>Hello, {user.name}!</div>;
}
```

### Protected Routes

Wrap routes that require authentication:

```tsx
import { ProtectedRoute } from '@{{PROJECT_NAME}}/auth';

// In your router or layout
<ProtectedRoute>
    <DashboardPage />
</ProtectedRoute>
```

### Server Actions (Next.js)

Create server actions for form submissions:

```typescript
// src/actions/my-action.ts
'use server';

import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';

export async function createItem(formData: FormData) {
    const token = cookies().get('auth_token')?.value;

    const response = await fetch(`${process.env.API_URL}/api/items`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
        },
        body: JSON.stringify({
            name: formData.get('name'),
        }),
    });

    if (!response.ok) {
        const error = await response.json();
        return { success: false, error: error.message };
    }

    revalidatePath('/items');
    return { success: true };
}
```

## API Client Generation

After adding OpenAPI annotations, regenerate the TypeScript client:

```bash
./scripts/generate-client.sh
```

This updates `packages/api-client/src/schema.d.ts` with typed endpoints.

## Testing

### Handler Tests

```go
func TestHandler_GetItem(t *testing.T) {
    handler := NewHandler(mockService, slog.Default())

    req := httptest.NewRequest("GET", "/api/items/123", nil)
    req = req.WithContext(auth.SetUser(req.Context(), &auth.User{ID: "user-1"}))

    rr := httptest.NewRecorder()
    handler.GetItem().ServeHTTP(rr, req)

    if rr.Code != http.StatusOK {
        t.Errorf("expected 200, got %d: %s", rr.Code, rr.Body.String())
    }
}
```

### Running Tests

```bash
# All tests
./scripts/quality.sh

# Specific service
go test ./services/api/...

# With coverage
go test -cover ./services/api/...
```

## Code Review

Use the built-in review command:

```bash
/review-code
```

This checks for:
- Completeness and accuracy
- Tech debt indicators
- Maintainability issues
- DRY/CLEAN violations

## Deployment

### Commit Message Format

```
feat: add user profile feature

- Add GET/PUT /api/users/me endpoints
- Add profile page with edit form
- Add handler tests
```

### CI Pipeline

On push to any branch:
1. Build all components
2. Run tests
3. Build Docker images

On merge to main:
1. Build + test
2. Push to registry
3. Deploy to K8s

### Verifying Deployment

```bash
# Check pod status
kubectl get pods -n projects -l app={{PROJECT_NAME}}

# View logs
kubectl logs -n projects -l app={{PROJECT_NAME}} -f

# Test endpoint
curl https://{{DOMAIN}}/api/health
```

## Common Issues

### Handler returns 500 instead of proper error
Use `httperror.*` functions, not raw `errors.New()`.

### Validation errors missing details
Use `app.BindAndValidate()` instead of separate bind + validate.

### Auth middleware not working
Check route is inside the `r.Group()` with `auth.Middleware()`.

### Generated client out of sync
Run `./scripts/generate-client.sh` after OpenAPI changes.

### Frontend auth not working
Ensure `AuthProvider` wraps your app in `providers.tsx`.

## Related

- [Local Setup](./local/setup.md)
- [Deploying](./ops/deploying.md)
