slack-q-1770281368/.claude/guides/feature-development.md
jordan d1194fd192
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-05 08:49:28 +00:00

315 lines
7.1 KiB
Markdown

# 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
```
slack-q-1770281368/
├── 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 "slack-q-1770281368/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 "slack-q-1770281368/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 "slack-q-1770281368/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 "slack-q-1770281368/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 '@slack-q-1770281368/ui';
import { DashboardShell, Sidebar, Header } from '@slack-q-1770281368/layout';
```
### Auth Hook
Access auth state and actions:
```tsx
import { useAuth } from '@slack-q-1770281368/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 '@slack-q-1770281368/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=slack-q-1770281368
# View logs
kubectl logs -n projects -l app=slack-q-1770281368 -f
# Test endpoint
curl https://p8968exz.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)