2025-12-22 21:00:50 +00:00
{
"meta" : {
"title" : "Veza Integration MVP Stability Todolist" ,
"description" : "Complete actionable todolist to reach a stable MVP state for backend/frontend integration" ,
"created_at" : "2025-01-27T00:00:00Z" ,
"target_health_score" : "8/10" ,
"current_health_score" : "4/10" ,
"estimated_total_effort" : "8-12 days" ,
"source_documents" : [
"INTEGRATION_AUDIT_BACKEND_FRONTEND.md" ,
"INTEGRATION_ISSUES_INDEX.json"
]
} ,
"mvp_definition" : {
"description" : "A stable MVP means: core authentication works reliably, API contracts are consistent, no security vulnerabilities, and the app can be deployed to production without critical failures" ,
"must_have" : [
"Authentication flow works 100% (login, logout, token refresh)" ,
"CORS properly configured for production" ,
"No token desync issues" ,
"Type safety across frontend/backend boundary" ,
"All active API calls have corresponding backend endpoints" ,
"Basic security (CSRF protection)"
] ,
"nice_to_have" : [
"Full error correlation (request IDs)" ,
"Retry logic with exponential backoff" ,
"Complete 2FA implementation" ,
"All collaboration features"
] ,
"out_of_scope_for_mvp" : [
"HLS streaming endpoints" ,
"Advanced playlist features (recommendations, sharing)" ,
"Role management endpoints" ,
"Notification system"
]
} ,
"phases" : [
{
"id" : "PHASE-1" ,
"name" : "Critical Blockers" ,
"description" : "Without these fixes, the app will not work in production" ,
"priority" : "CRITICAL" ,
"estimated_effort" : "3-4 days" ,
"tasks" : [
{
"id" : "MVP-001" ,
"source_issue" : "INT-000001" ,
"title" : "Fix CORS Production Configuration" ,
"description" : "CORS rejects ALL requests in production if CORS_ALLOWED_ORIGINS is not set. Add fail-fast validation." ,
"owner" : "backend" ,
"estimated_hours" : 2 ,
2025-12-22 21:56:37 +00:00
"status" : "done" ,
2025-12-22 21:00:50 +00:00
"priority" : 1 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "veza-backend-api/internal/config/config.go" ,
"action" : "Add validation function" ,
"lines" : "L638-L664"
} ,
{
"path" : "veza-backend-api/cmd/api/main.go" ,
"action" : "Call validation on startup"
} ,
{
"path" : "docker-compose.production.yml" ,
"action" : "Add CORS_ALLOWED_ORIGINS example"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Create validation function in config.go" ,
"code_snippet" : "func (c *Config) ValidateForProduction() error {\n if c.Environment == EnvProduction && len(c.CORSOrigins) == 0 {\n return fmt.Errorf(\"FATAL: CORS_ALLOWED_ORIGINS must be set in production\")\n }\n return nil\n}"
} ,
{
"step" : 2 ,
"action" : "Call validation in main.go before server starts" ,
"code_snippet" : "if err := cfg.ValidateForProduction(); err != nil {\n log.Fatal(err)\n}"
} ,
{
"step" : 3 ,
"action" : "Update docker-compose with documented example"
} ,
{
"step" : 4 ,
"action" : "Add unit test for validation"
}
] ,
"validation" : {
"commands" : [
"APP_ENV=production CORS_ALLOWED_ORIGINS='' go run ./cmd/api # Should fail with clear error" ,
"APP_ENV=production CORS_ALLOWED_ORIGINS='https://app.veza.com' go run ./cmd/api # Should start"
] ,
"expected_outcome" : "Server refuses to start with empty CORS in production mode"
} ,
"acceptance_criteria" : [
"Server fails fast with clear error message if CORS empty in production" ,
"Server starts normally with CORS configured" ,
"Documentation updated with required env vars"
]
} ,
{
"id" : "MVP-002" ,
"source_issue" : "INT-000002" ,
"title" : "Unify Token Storage to Single Source of Truth" ,
"description" : "Three competing token storage mechanisms cause auth failures. Consolidate to TokenStorage only." ,
"owner" : "frontend" ,
"estimated_hours" : 4 ,
2025-12-22 21:56:37 +00:00
"status" : "done" ,
2025-12-22 21:00:50 +00:00
"priority" : 2 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/stores/auth.ts" ,
"action" : "Remove token storage, keep only user and isAuthenticated"
} ,
{
"path" : "apps/web/src/utils/token-manager.ts" ,
"action" : "DELETE or redirect to TokenStorage"
} ,
{
"path" : "apps/web/src/services/api/client.ts" ,
"action" : "Remove Zustand fallback (L48-L64), use only TokenStorage"
} ,
{
"path" : "apps/web/src/services/tokenStorage.ts" ,
"action" : "Verify this is the canonical source"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Audit all token access points" ,
"command" : "grep -rn 'localStorage.*token\\|getAccessToken\\|setAccessToken\\|auth-storage' apps/web/src/"
} ,
{
"step" : 2 ,
"action" : "Update Zustand store to remove token storage" ,
"details" : "Keep only: user, isAuthenticated, isLoading, error. Remove: accessToken, refreshToken"
} ,
{
"step" : 3 ,
"action" : "Delete apps/web/src/utils/token-manager.ts"
} ,
{
"step" : 4 ,
"action" : "Update apiClient to remove Zustand fallback" ,
"details" : "Remove lines 48-64 that parse auth-storage"
} ,
{
"step" : 5 ,
"action" : "Update all login/logout flows to use TokenStorage exclusively"
} ,
{
"step" : 6 ,
"action" : "Test token persistence across page reloads"
}
] ,
"validation" : {
"commands" : [
"grep -r 'auth-storage' apps/web/src/services/api/ # Should return 0 results" ,
"grep -r 'token-manager' apps/web/src/ # Should return 0 results"
] ,
"manual_tests" : [
"Login → Refresh page → Still logged in" ,
"Login → Open new tab → Still logged in" ,
"Logout → Token cleared from localStorage"
]
} ,
"acceptance_criteria" : [
"Only TokenStorage class manages tokens" ,
"No token references in Zustand state" ,
"token-manager.ts deleted or re-exports TokenStorage" ,
"Auth persists across page reloads"
]
} ,
{
"id" : "MVP-003" ,
"source_issue" : "INT-000003" ,
"title" : "Fix User.id Type Mismatch (string everywhere)" ,
"description" : "Backend sends UUID (string) but some frontend types expect number. Causes runtime comparison bugs." ,
"owner" : "frontend" ,
"estimated_hours" : 3 ,
2025-12-22 21:56:37 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 3 ,
"dependencies" : [ ] ,
2025-12-22 21:56:37 +00:00
"completion" : {
"completed_at" : "2025-01-27T16:00:00Z" ,
"completed_by" : "cursor-agent" ,
"actual_effort_hours" : 2.5 ,
"commits" : [ ] ,
"notes" : "Updated all userId/user_id parameters from number to string. Updated Zod schemas to validate UUID format with z.string().uuid(). Fixed TypeScript compilation errors." ,
"issues_encountered" : [ ]
} ,
2025-12-22 21:00:50 +00:00
"files_to_modify" : [
{
"path" : "apps/web/src/features/auth/types/index.ts" ,
"action" : "Change id: number to id: string" ,
"lines" : "L8"
} ,
{
"path" : "apps/web/src/types/api.ts" ,
"action" : "Verify id: string (already correct)"
} ,
{
"path" : "apps/web/src/schemas/validation.ts" ,
"action" : "Update Zod schemas to use z.string().uuid()"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Find all User type definitions" ,
"command" : "grep -rn 'id:\\s*number' apps/web/src/ --include='*.ts' --include='*.tsx'"
} ,
{
"step" : 2 ,
"action" : "Update each occurrence to id: string"
} ,
{
"step" : 3 ,
"action" : "Update Zod schemas for user ID validation" ,
"code_snippet" : "id: z.string().uuid()"
} ,
{
"step" : 4 ,
"action" : "Run TypeScript compiler to find remaining errors" ,
"command" : "cd apps/web && npx tsc --noEmit"
} ,
{
"step" : 5 ,
"action" : "Fix all type errors"
}
] ,
"validation" : {
"commands" : [
"grep -rn 'id:\\s*number' apps/web/src/ # Should return 0 User-related results" ,
"cd apps/web && npx tsc --noEmit # Should pass"
]
} ,
"acceptance_criteria" : [
"All User type definitions use id: string" ,
"Zod schemas validate UUID format" ,
"TypeScript compiles without User.id errors"
]
} ,
{
"id" : "MVP-004" ,
"source_issue" : "INT-000004" ,
"title" : "Remove Deprecated ApiService, Migrate to apiClient" ,
"description" : "Deprecated ApiService expects wrong response format. Remove entirely and use apiClient." ,
"owner" : "frontend" ,
"estimated_hours" : 4 ,
2025-12-22 21:56:37 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 4 ,
2025-12-22 21:56:37 +00:00
"dependencies" : [
"MVP-002"
] ,
"completion" : {
"completed_at" : "2025-01-27T17:30:00Z" ,
"completed_by" : "cursor-agent" ,
"actual_effort_hours" : 3.5 ,
"commits" : [ ] ,
"notes" : "Migrated all ApiService usages to apiClient. Updated library.ts, chat.ts, ProfileForm.tsx, LibraryManager.tsx, UploadModal.tsx, VirtualizedChatMessages.tsx, ChatInterface.tsx. Deleted api.ts and api.test.ts. Updated test mocks." ,
"issues_encountered" : [ ]
} ,
2025-12-22 21:00:50 +00:00
"files_to_modify" : [
{
"path" : "apps/web/src/services/api.ts" ,
"action" : "DELETE this file after migration"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Find all ApiService usages" ,
"command" : "grep -rn 'ApiService\\|apiService\\|from.*api[\"'\\']' apps/web/src/ | grep -v 'api/client\\|api/auth\\|apiClient'"
} ,
{
"step" : 2 ,
"action" : "For each usage, migrate to apiClient or specific API module" ,
"migration_map" : {
"apiService.login()" : "authApi.login()" ,
"apiService.register()" : "authApi.register()" ,
"apiService.getUser()" : "apiClient.get('/users/:id')" ,
"apiService.refreshToken()" : "authApi.refresh()"
}
} ,
{
"step" : 3 ,
"action" : "Update imports in all affected files"
} ,
{
"step" : 4 ,
"action" : "Delete apps/web/src/services/api.ts"
} ,
{
"step" : 5 ,
"action" : "Verify no references remain" ,
"command" : "grep -rn 'ApiService\\|apiService' apps/web/src/"
}
] ,
"validation" : {
"commands" : [
"grep -rn 'ApiService' apps/web/src/ # Should return 0 results" ,
"ls apps/web/src/services/api.ts # Should fail (file deleted)" ,
"cd apps/web && npx tsc --noEmit # Should pass"
] ,
"manual_tests" : [
"Login flow works" ,
"Registration flow works" ,
"User profile loads"
]
} ,
"acceptance_criteria" : [
"ApiService class completely removed" ,
"All API calls use apiClient or typed API modules" ,
"No regressions in auth or user flows"
]
} ,
{
"id" : "MVP-005" ,
"source_issue" : "INT-000005" ,
"title" : "Implement CSRF Protection" ,
"description" : "No CSRF protection exists. Implement token generation and validation for state-changing operations." ,
"owner" : "backend + frontend" ,
"estimated_hours" : 6 ,
2025-12-22 21:56:37 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 5 ,
2025-12-22 21:56:37 +00:00
"dependencies" : [
"MVP-001"
] ,
"completion" : {
"completed_at" : "2025-01-27T18:00:00Z" ,
"completed_by" : "cursor-agent" ,
"actual_effort_hours" : 5.5 ,
"commits" : [ ] ,
"notes" : "Implemented CSRF protection using Redis for token storage. Created middleware and handler in backend. Added CSRF service in frontend with automatic token refresh. Integrated with apiClient interceptor. Login/register correctly excluded from CSRF check." ,
"issues_encountered" : [ ]
} ,
2025-12-22 21:00:50 +00:00
"files_to_modify" : [
{
"path" : "veza-backend-api/internal/middleware/csrf.go" ,
"action" : "CREATE - CSRF middleware"
} ,
{
"path" : "veza-backend-api/internal/handlers/csrf.go" ,
"action" : "CREATE - CSRF token endpoint"
} ,
{
"path" : "veza-backend-api/internal/api/router.go" ,
"action" : "Add CSRF routes and middleware"
} ,
{
"path" : "apps/web/src/services/csrf.ts" ,
"action" : "Implement refreshCsrfToken()"
} ,
{
"path" : "apps/web/src/services/api/client.ts" ,
"action" : "Add CSRF header interceptor"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Create CSRF middleware in backend" ,
"code_snippet" : "func CSRFMiddleware() gin.HandlerFunc {\n return func(c *gin.Context) {\n if c.Request.Method == \"GET\" || c.Request.Method == \"OPTIONS\" {\n c.Next()\n return\n }\n token := c.GetHeader(\"X-CSRF-Token\")\n sessionToken := getSessionCSRFToken(c)\n if token == \"\" || token != sessionToken {\n c.AbortWithStatusJSON(403, gin.H{\"error\": \"Invalid CSRF token\"})\n return\n }\n c.Next()\n }\n}"
} ,
{
"step" : 2 ,
"action" : "Create CSRF token endpoint" ,
"route" : "GET /api/v1/csrf-token"
} ,
{
"step" : 3 ,
"action" : "Apply middleware to router (after auth routes)" ,
"note" : "Exclude login/register from CSRF check"
} ,
{
"step" : 4 ,
"action" : "Implement frontend csrf.ts" ,
"code_snippet" : "async refreshToken(): Promise<void> {\n const response = await fetch('/api/v1/csrf-token', { credentials: 'include' });\n const data = await response.json();\n this.token = data.csrf_token;\n}"
} ,
{
"step" : 5 ,
"action" : "Add interceptor to apiClient" ,
"code_snippet" : "apiClient.interceptors.request.use((config) => {\n if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method?.toUpperCase() || '')) {\n config.headers['X-CSRF-Token'] = csrfService.getToken();\n }\n return config;\n});"
} ,
{
"step" : 6 ,
"action" : "Fetch CSRF token on app initialization"
}
] ,
"validation" : {
"manual_tests" : [
"POST request without CSRF token → 403 error" ,
"POST request with valid CSRF token → Success" ,
"GET requests work without CSRF token"
]
} ,
"acceptance_criteria" : [
"CSRF endpoint returns token" ,
"All POST/PUT/DELETE requests include X-CSRF-Token header" ,
"Requests without valid token are rejected with 403" ,
"Login/register still work (excluded from CSRF)"
]
}
]
} ,
{
"id" : "PHASE-2" ,
"name" : "API Contract Alignment" ,
"description" : "Fix mismatches between frontend calls and backend routes" ,
"priority" : "HIGH" ,
"estimated_effort" : "2-3 days" ,
"tasks" : [
{
"id" : "MVP-006" ,
"source_issue" : "INT-000007" ,
"title" : "Standardize Environment Variable Names" ,
"description" : "VITE_API_BASE_URL vs VITE_API_URL inconsistency causes build failures" ,
"owner" : "frontend" ,
"estimated_hours" : 1 ,
2025-12-22 21:56:37 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 6 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/scripts/check_backend.sh" ,
"action" : "Replace VITE_API_BASE_URL with VITE_API_URL"
} ,
{
"path" : "apps/web/Dockerfile" ,
"action" : "Replace ARG VITE_API_BASE_URL with VITE_API_URL"
} ,
{
"path" : "apps/web/.env.example" ,
"action" : "Ensure only VITE_API_URL is documented"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Find all VITE_API_BASE_URL references" ,
"command" : "grep -rn 'VITE_API_BASE_URL' apps/web/"
} ,
{
"step" : 2 ,
"action" : "Replace all with VITE_API_URL"
} ,
{
"step" : 3 ,
"action" : "Update .env.example with correct variable"
} ,
{
"step" : 4 ,
"action" : "Update deployment documentation"
}
] ,
"validation" : {
"commands" : [
"grep -rn 'VITE_API_BASE_URL' apps/web/ # Should return 0 results"
]
} ,
"acceptance_criteria" : [
"Only VITE_API_URL used everywhere" ,
"Scripts and Dockerfile updated" ,
"Documentation reflects correct variable name"
]
} ,
{
"id" : "MVP-007" ,
"source_issue" : "INT-000008" ,
"title" : "Fix Profile Endpoint Path Mismatch" ,
"description" : "Frontend calls /users/:userId/profile but backend uses /users/:id" ,
"owner" : "frontend" ,
"estimated_hours" : 2 ,
2025-12-22 21:58:18 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 7 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/features/profile/services/profileService.ts" ,
"action" : "Update paths to match backend routes"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Update getProfile path" ,
"from" : "GET /api/v1/users/${userId}/profile" ,
"to" : "GET /api/v1/users/${userId}"
} ,
{
"step" : 2 ,
"action" : "Update updateProfile path" ,
"from" : "PUT /api/v1/users/${userId}/profile" ,
"to" : "PUT /api/v1/users/${userId}"
} ,
{
"step" : 3 ,
"action" : "Verify response handling matches backend format"
}
] ,
"validation" : {
"manual_tests" : [
"Profile page loads correctly" ,
"Profile update saves successfully"
]
} ,
"acceptance_criteria" : [
"Profile endpoints match backend routes" ,
"Profile CRUD operations work"
]
} ,
{
"id" : "MVP-008" ,
"source_issue" : "INT-000006" ,
"title" : "Handle Missing Endpoints - Decide and Clean" ,
"description" : "18 frontend calls target non-existent endpoints. For MVP: remove calls for non-essential features, stub essential ones." ,
"owner" : "frontend + backend" ,
"estimated_hours" : 4 ,
2025-12-22 22:01:36 +00:00
"status" : "completed" ,
2025-12-22 21:00:50 +00:00
"priority" : 8 ,
"dependencies" : [ ] ,
"sub_tasks" : [
{
"id" : "MVP-008a" ,
"title" : "Remove 2FA service calls (not MVP)" ,
"action" : "Comment out or remove 2fa-service.ts usage until backend implemented" ,
2025-12-22 21:56:37 +00:00
"files" : [
"apps/web/src/services/2fa-service.ts"
]
2025-12-22 21:00:50 +00:00
} ,
{
"id" : "MVP-008b" ,
"title" : "Remove playlist collaboration features (not MVP)" ,
"action" : "Disable UI for collaborators, search, share, recommendations" ,
2025-12-22 21:56:37 +00:00
"files" : [
"apps/web/src/features/playlists/services/playlistService.ts"
]
2025-12-22 21:00:50 +00:00
} ,
{
"id" : "MVP-008c" ,
"title" : "Remove HLS service calls (not MVP)" ,
"action" : "Remove or stub hlsService until streaming implemented" ,
2025-12-22 21:56:37 +00:00
"files" : [
"apps/web/src/features/streaming/services/hlsService.ts"
]
2025-12-22 21:00:50 +00:00
} ,
{
"id" : "MVP-008d" ,
"title" : "Remove role management service (not MVP)" ,
"action" : "Disable role management UI" ,
2025-12-22 21:56:37 +00:00
"files" : [
"apps/web/src/features/admin/services/roleService.ts"
]
2025-12-22 21:00:50 +00:00
} ,
{
"id" : "MVP-008e" ,
"title" : "Remove notifications API calls (not MVP)" ,
"action" : "Disable notifications until implemented" ,
2025-12-22 21:56:37 +00:00
"files" : [
"apps/web/src/features/notifications/api/notificationsApi.ts"
]
2025-12-22 21:00:50 +00:00
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Create feature flags or environment checks" ,
"code_snippet" : "const FEATURES = {\n TWO_FACTOR_AUTH: false,\n PLAYLIST_COLLABORATION: false,\n HLS_STREAMING: false,\n ROLE_MANAGEMENT: false,\n NOTIFICATIONS: false\n};"
} ,
{
"step" : 2 ,
"action" : "Wrap non-MVP service calls with feature flags"
} ,
{
"step" : 3 ,
"action" : "Hide UI elements for disabled features"
} ,
{
"step" : 4 ,
"action" : "Add TODO comments for post-MVP implementation"
}
] ,
"validation" : {
"commands" : [
"cd apps/web && npx tsc --noEmit # Should pass without endpoint errors"
] ,
"manual_tests" : [
"App loads without 404 errors in console" ,
"Core features (auth, tracks, playlists CRUD) work"
]
} ,
"acceptance_criteria" : [
"No 404 errors from frontend API calls" ,
"Non-MVP features gracefully disabled" ,
"Core MVP features fully functional"
]
} ,
{
"id" : "MVP-009" ,
"source_issue" : "INT-000015" ,
"title" : "Fix GetMe Endpoint to Return Full User" ,
"description" : "GET /auth/me returns only id, email, role but frontend needs full user object" ,
"owner" : "backend" ,
"estimated_hours" : 2 ,
"status" : "todo" ,
"priority" : 9 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "veza-backend-api/internal/handlers/auth.go" ,
"action" : "Update GetMe to fetch and return full user" ,
"lines" : "L369-L373"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Update GetMe handler to fetch user from database"
} ,
{
"step" : 2 ,
"action" : "Return full UserResponse instead of minimal fields"
} ,
{
"step" : 3 ,
"action" : "Ensure response matches frontend User type"
}
] ,
"validation" : {
"manual_tests" : [
"Call GET /api/v1/auth/me" ,
"Response includes: id, email, username, avatar, role, created_at, etc."
]
} ,
"acceptance_criteria" : [
"GetMe returns complete user object" ,
"Frontend can display all user fields after login"
]
} ,
{
"id" : "MVP-010" ,
"source_issue" : "INT-000009" ,
"title" : "Fix Error Code Type in Zod Schemas" ,
"description" : "Backend sends error code as number, but Zod schema expects string" ,
"owner" : "frontend" ,
"estimated_hours" : 1 ,
"status" : "todo" ,
"priority" : 10 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/schemas/validation.ts" ,
"action" : "Change code: z.string() to code: z.number()" ,
"lines" : "L338"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Update Zod error schema" ,
"from" : "code: z.string()" ,
"to" : "code: z.number()"
} ,
{
"step" : 2 ,
"action" : "Verify error handling still works"
}
] ,
"validation" : {
"manual_tests" : [
"Trigger API error" ,
"Error displays correctly with error code"
]
} ,
"acceptance_criteria" : [
"Error codes parsed as numbers" ,
"Error display works correctly"
]
}
]
} ,
{
"id" : "PHASE-3" ,
"name" : "Reliability & Polish" ,
"description" : "Improve robustness for production deployment" ,
"priority" : "MEDIUM" ,
"estimated_effort" : "2-3 days" ,
"tasks" : [
{
"id" : "MVP-011" ,
"source_issue" : "INT-000011" ,
"title" : "Simplify Token Refresh Response Handling" ,
"description" : "Frontend checks 3 different formats for token refresh. Simplify to single expected format." ,
"owner" : "frontend" ,
"estimated_hours" : 2 ,
"status" : "todo" ,
"priority" : 11 ,
2025-12-22 21:56:37 +00:00
"dependencies" : [
"MVP-002" ,
"MVP-004"
] ,
2025-12-22 21:00:50 +00:00
"files_to_modify" : [
{
"path" : "apps/web/src/services/tokenRefresh.ts" ,
"action" : "Remove fallback format checks, use only correct format"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Document correct format: { success: true, data: { access_token, refresh_token, expires_in } }"
} ,
{
"step" : 2 ,
"action" : "Remove fallback parsing logic (lines 70-84)"
} ,
{
"step" : 3 ,
"action" : "Add clear error if format unexpected"
}
] ,
"acceptance_criteria" : [
"Token refresh handles single documented format" ,
"Clear error on unexpected format" ,
"Token refresh works reliably"
]
} ,
{
"id" : "MVP-012" ,
"source_issue" : "INT-000012" ,
"title" : "Add Retry Logic for 503/502 Errors" ,
"description" : "Transient errors cause immediate failure. Add retry with exponential backoff." ,
"owner" : "frontend" ,
"estimated_hours" : 3 ,
"status" : "todo" ,
"priority" : 12 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/services/api/client.ts" ,
"action" : "Add retry logic for 502/503 errors"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Create retry utility function" ,
"code_snippet" : "async function retryWithBackoff<T>(\n fn: () => Promise<T>,\n maxRetries: number = 3,\n baseDelay: number = 1000\n): Promise<T> {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n if (attempt === maxRetries - 1) throw error;\n if (!isRetryableError(error)) throw error;\n await sleep(baseDelay * Math.pow(2, attempt));\n }\n }\n throw new Error('Max retries exceeded');\n}"
} ,
{
"step" : 2 ,
"action" : "Apply retry logic to 502/503 responses"
} ,
{
"step" : 3 ,
"action" : "Respect Retry-After header if present"
}
] ,
"acceptance_criteria" : [
"502/503 errors retried up to 3 times" ,
"Exponential backoff between retries" ,
"Retry-After header respected"
]
} ,
{
"id" : "MVP-013" ,
"source_issue" : "INT-000013" ,
"title" : "Add Error Correlation with Request IDs" ,
"description" : "Backend returns request_id but frontend doesn't log it. Add for debugging." ,
"owner" : "frontend" ,
"estimated_hours" : 2 ,
"status" : "todo" ,
"priority" : 13 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/services/api/client.ts" ,
"action" : "Extract and log request_id from error responses"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Update error handler to extract request_id"
} ,
{
"step" : 2 ,
"action" : "Include request_id in error logs"
} ,
{
"step" : 3 ,
"action" : "Optionally show request_id in user-facing error messages"
}
] ,
"acceptance_criteria" : [
"Error logs include request_id" ,
"Can correlate frontend errors with backend logs"
]
} ,
{
"id" : "MVP-014" ,
"source_issue" : "INT-000014" ,
"title" : "Validate CORS Credentials Configuration" ,
"description" : "Credentials=true is hardcoded. Add validation to prevent security issues." ,
"owner" : "backend" ,
"estimated_hours" : 1 ,
"status" : "todo" ,
"priority" : 14 ,
2025-12-22 21:56:37 +00:00
"dependencies" : [
"MVP-001"
] ,
2025-12-22 21:00:50 +00:00
"files_to_modify" : [
{
"path" : "veza-backend-api/internal/middleware/cors.go" ,
"action" : "Add validation: reject wildcard origins with credentials"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Check if any origin contains wildcard"
} ,
{
"step" : 2 ,
"action" : "Log warning if credentials=true with weak origins"
} ,
{
"step" : 3 ,
"action" : "Optionally fail startup if insecure configuration detected"
}
] ,
"acceptance_criteria" : [
"Warning logged for weak CORS configuration" ,
"No wildcard origins with credentials=true"
]
} ,
{
"id" : "MVP-015" ,
"source_issue" : "INT-000010" ,
"title" : "Standardize remember_me Field Name" ,
"description" : "Mixed naming: rememberMe in forms, remember_me in API. Standardize to snake_case." ,
"owner" : "frontend" ,
"estimated_hours" : 1 ,
"status" : "todo" ,
"priority" : 15 ,
"dependencies" : [ ] ,
"files_to_modify" : [
{
"path" : "apps/web/src/features/auth/types/index.ts" ,
"action" : "Ensure remember_me naming"
} ,
{
"path" : "apps/web/src/features/auth/components/LoginForm.tsx" ,
"action" : "Update form field naming"
}
] ,
"implementation_steps" : [
{
"step" : 1 ,
"action" : "Find all rememberMe references" ,
"command" : "grep -rn 'rememberMe' apps/web/src/"
} ,
{
"step" : 2 ,
"action" : "Replace with remember_me to match backend"
}
] ,
"acceptance_criteria" : [
"Consistent snake_case naming for remember_me" ,
"Login with remember me works correctly"
]
}
]
}
] ,
"summary" : {
"total_tasks" : 15 ,
"by_owner" : {
"backend" : 4 ,
"frontend" : 9 ,
"backend + frontend" : 2
} ,
"by_phase" : {
"PHASE-1 (Critical)" : 5 ,
"PHASE-2 (API Contract)" : 5 ,
"PHASE-3 (Reliability)" : 5
} ,
"estimated_total_hours" : "38-42 hours" ,
"estimated_calendar_days" : "8-12 days (solo developer)" ,
"critical_path" : [
"MVP-001 (CORS)" ,
"MVP-002 (Token Storage)" ,
"MVP-005 (CSRF)" ,
"MVP-004 (ApiService removal)"
]
} ,
"progress_tracking" : {
2025-12-22 22:01:36 +00:00
"completed" : 8 ,
2025-12-22 21:00:50 +00:00
"in_progress" : 0 ,
2025-12-22 22:01:36 +00:00
"todo" : 7 ,
2025-12-22 21:00:50 +00:00
"blocked" : 0 ,
2025-12-22 22:01:36 +00:00
"last_updated" : "2025-01-27T21:00:00Z" ,
"completion_percentage" : 53
2025-12-22 21:00:50 +00:00
} ,
"validation_checklist" : {
"description" : "Run these checks after all tasks complete to verify MVP stability" ,
"checks" : [
{
"id" : "VAL-001" ,
"name" : "TypeScript Compilation" ,
"command" : "cd apps/web && npx tsc --noEmit" ,
"expected" : "Exit code 0, no errors"
} ,
{
"id" : "VAL-002" ,
"name" : "Go Build" ,
"command" : "cd veza-backend-api && go build ./..." ,
"expected" : "Exit code 0, no errors"
} ,
{
"id" : "VAL-003" ,
"name" : "Frontend Tests" ,
"command" : "cd apps/web && npm test" ,
"expected" : "All tests pass"
} ,
{
"id" : "VAL-004" ,
"name" : "Backend Tests" ,
"command" : "cd veza-backend-api && go test ./..." ,
"expected" : "All tests pass"
} ,
{
"id" : "VAL-005" ,
"name" : "CORS Production Check" ,
"command" : "APP_ENV=production CORS_ALLOWED_ORIGINS='' go run ./cmd/api" ,
"expected" : "Server fails with clear error"
} ,
{
"id" : "VAL-006" ,
"name" : "No Deprecated ApiService" ,
"command" : "grep -r 'ApiService' apps/web/src/" ,
"expected" : "No results"
} ,
{
"id" : "VAL-007" ,
"name" : "No Token Storage Fragmentation" ,
"command" : "grep -r 'auth-storage' apps/web/src/services/" ,
"expected" : "No results"
} ,
{
"id" : "VAL-008" ,
"name" : "E2E Auth Flow" ,
"manual" : true ,
"steps" : [
"Register new user" ,
"Logout" ,
"Login with new user" ,
"Refresh page (should stay logged in)" ,
"Token refresh (wait for expiry or force)" ,
"Logout"
] ,
"expected" : "All steps complete without errors"
} ,
{
"id" : "VAL-009" ,
"name" : "No Console 404 Errors" ,
"manual" : true ,
"steps" : [
"Open browser dev tools" ,
"Navigate through main app flows" ,
"Check network tab for 404s"
] ,
"expected" : "No 404 errors from API calls"
}
]
}
2025-12-22 21:56:37 +00:00
}