# 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 ``` css-fix-test-1770192445/ ├── 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 "css-fix-test-1770192445/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 "css-fix-test-1770192445/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 "css-fix-test-1770192445/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 "css-fix-test-1770192445/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 '@css-fix-test-1770192445/ui'; import { DashboardShell, Sidebar, Header } from '@css-fix-test-1770192445/layout'; ``` ### Auth Hook Access auth state and actions: ```tsx import { useAuth } from '@css-fix-test-1770192445/auth'; function MyComponent() { const { user, isLoading, login, logout, refreshUser } = useAuth(); if (isLoading) return
Loading...
; if (!user) return ; return
Hello, {user.name}!
; } ``` ### Protected Routes Wrap routes that require authentication: ```tsx import { ProtectedRoute } from '@css-fix-test-1770192445/auth'; // In your router or layout ``` ### 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=css-fix-test-1770192445 # View logs kubectl logs -n projects -l app=css-fix-test-1770192445 -f # Test endpoint curl https://x622xe69.threesix.ai/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)