Purpose: Quick reference for technical challenges encountered during Phase 2 and their solutions
When to use this document:
- 🔍 Debugging a similar issue
- 📖 Understanding why a specific solution was chosen
- 🧠 Learning about Vite + code-server proxy configuration
- 🚀 Quick problem-solving without reading full technical log
- 🧪 NEW: Running automated diagnostic tests
Search modal opened but no results appeared, even though videos existed in gallery.
- Gallery displays:
video.original_filenamefield - Search filtered:
video.filenamefield (always null) - Result: Search found 0 videos
File: frontend/src/js/search.js
// performSearch() - line 158
const filename = (video.original_filename || video.filename || '').toLowerCase();
// createVideoResultCard() - line 198
const displayName = video.original_filename || video.filename || 'Untitled Video';5558ef2- fix: Use original_filename in search to match gallery display
Upload shows "failed" for few minutes, then changes to "success" - confusing UX.
Worker tasks re-raised exceptions after marking status "failed", causing Dramatiq to retry (8 times default). On retry, task often succeeded, updating status to "completed".
File: app/workers/tasks.py
# Before: Re-raised exception, causing retries
except Exception as e:
supabase.table("videos").update({"status": "failed"}).eq("id", video_id).execute()
raise # ← THIS CAUSES DRAMATIQ TO RETRY!
# After: Don't re-raise - failed is final state
except Exception as e:
supabase.table("videos").update({"status": "failed", "error_message": str(e)}).eq("id", video_id).execute()
# No re-raise - prevents confusing "failed then success" behaviorNote: For smart retries later: only retry transient errors (timeout, 429), not permanent errors.
Avatar showed 'TM' on settings but 'V' on other pages.
- Dashboard & Settings: Fetched fresh
/users/medata → correct initials - Gallery, Upload, Status: Used stale sessionStorage → incomplete data
Updated all pages to fetch fresh user data (like dashboard):
File: gallery.js, upload.js, status.js
try {
const user = await apiClient.makeRequest('/users/me', { method: 'GET' });
if (user && !user.error) {
sessionManager.updateAvatarInitials(user);
}
} catch (error) {
// Fallback to sessionStorage
if (sessionManager.user) {
sessionManager.updateAvatarInitials(sessionManager.user);
}
}Root Cause: Endpoint used password_change: dict instead of Pydantic model.
Solution: Created PasswordChange Pydantic model.
Root Cause: Code only checked response.message, never response.error.
Solution: Check error first:
if (response && response.error) {
// handle error
} else if (response && response.message) {
// handle success
}Root Cause: 401 redirect handler caught password validation errors (401), redirecting settings page to login. Solution: Exclude settings.html from auto-redirect:
if (!currentPath.includes('/login.html') && !currentPath.includes('/register.html') && !currentPath.includes('/settings.html')) {
window.location.href = '/absproxy/3000/src/pages/login.html';
}Key Learning: 401 can mean: session expired (redirect), authentication failed (show error), or validation failed (show error).
- HTML: Hardcoded placeholders, no IDs on inputs
- Backend:
/users/meusedprofile_data: dictinstead of Pydantic model - Frontend: Error objects not stringified before display (
[object Object])
Created Pydantic models:
class ProfileUpdate(BaseModel):
display_name: Optional[str] = None
company: Optional[str] = None
avatar_url: Optional[str] = None
class PreferencesUpdate(BaseModel):
notifications_enabled: Optional[bool] = None
marketing_emails: Optional[bool] = None
default_quality: Optional[str] = NoneKey Learning: Always use Pydantic models for request bodies, never raw dict.
Problem: [object Object],[object Object] error
Solution: Hybrid parameter passing - context params + clear processing params
Problem: 29 videos stuck, oldest 45 days Solution: 3-phase database status tracking in workers
Problem: unsupportedArchitectureWidth error
Solution: Removed width/height from IVideoInference request (Kling doesn't support these)
Problem: AttributeError: 'bool' object has no attribute 'get'
Solution: Changed pipeline to return {"video_path": path} instead of boolean
pytest tests/test_debug_integration.py::TestSystemHealthCheck -v -spytest tests/test_debug_endpoints.py -vDocumentation:
Complete Settings page with profile, security, preferences, and account actions.
1. Profile Management
- Update display name and company
- Fetch from API, real-time validation
- Backend:
PUT /users/me
2. Password Change
- Real-time validation: current password, strength indicator (Red→Orange→Yellow→Green), match indicator
- 6-point validation before submit
- Visual feedback: red border on incorrect password
- Backend:
POST /auth/change-password
3. Preferences
- Toggles: notifications, marketing emails
- Quality selector: standard/pro
- Backend:
PUT /users/me/preferences
4. Danger Zone
- Delete Account: requires "DELETE MY ACCOUNT" confirmation
- Clear All Videos: requires confirmation
- Backends:
DELETE /users/me,DELETE /videos/clear-all
- Current password verified server-side via Supabase
- Account deletion requires exact string match
- All endpoints require JWT authentication
Unstyled HTML, no Tailwind classes, images not loading.
Complex path resolution conflict:
- Code-server
/proxy/3000/strips prefix before forwarding - Vite
base: '/proxy/3000/'expected prefix in requests - Mismatch: Vite received
/src/main.js, expected/proxy/3000/src/main.js
- Vite Config:
base: '/' - HTML: Relative paths (
../main.js,../styles/index.css) - CSS Loading: Direct
<link>tag instead of JS import
404 errors for all .js modules, buttons did nothing.
Path-stripping proxy (/proxy/) vs. Vite expecting /proxy/3000/ prefix.
Switch to path-preserving proxy:
- Changed:
/proxy/3000/→/absproxy/3000/ - Vite config:
base: '/absproxy/3000/' - How it works:
/absproxy/preserves full path when forwarding
POST /api/v1/auth/login 405 (Method Not Allowed)
GET /api/v1/users/me 404 (Not Found)
- API base URL missing
/absproxy/3000/prefix - Vite proxy not matching
/absproxy/3000/apipaths
Update API base URL:
const baseUrl = '/absproxy/3000/api/v1';Add Vite proxy rule:
'/absproxy/3000/api': {
target: 'http://localhost:5000',
rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
}Login succeeds → dashboard appears briefly → redirects to login.
API login returned 405 error (Challenge 3), but frontend treated as success. No session data saved, so dashboard redirected.
Fixed by Challenge 3 (correct API URL). Enhanced error handling in login.js.
Credentials appeared in URL: login.html?email=user@example.com&password=mypassword
Race condition: HTML form submitted before JavaScript loaded to prevent default behavior.
- ❌ Passwords in URL bar, browser history, server logs, proxy logs, Referer header
<form action="javascript:void(0);">Disables default form submission at HTML level, no race condition.
Error message flashes briefly, then page reloads and error disappears.
API client auto-redirected to login on 401, but user already on login page → reload loop.
if (response.status === 401) {
const currentPath = window.location.pathname;
if (!currentPath.includes('/login.html') && !currentPath.includes('/register.html')) {
window.location.href = '/absproxy/3000/src/pages/login.html';
}
}Gallery showed 3 static example cards instead of real videos.
Complete rewrite:
HTML: Dynamic containers
<div id="video-grid-container" class="grid...">
<!-- Videos inserted via JS -->
</div>JavaScript:
async function loadVideos() {
const videos = await apiClient.getUserVideos();
videos.forEach(video => {
const card = createVideoCard(video);
gridContainer.appendChild(card);
});
}
function createVideoCard(video) {
// Generate HTML from video data
// Conditional rendering for status badges
// Smart download button (enabled only when completed)
}Features: Dynamic rendering, status badges (processing/completed/failed), smart buttons, loading/empty/error states.
- Path-Stripping (
/proxy/): Usebase: '/' - Path-Preserving (
/absproxy/): Usebase: '/absproxy/3000/' - Best practice: Path-preserving for consistency
- ❌ JS import:
import './styles/index.css'(transforms to absolute path) - ✅ HTML link:
<link rel="stylesheet" href="../styles/index.css">(relative)
'/absproxy/3000/api': {
target: 'http://localhost:5000',
rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
}- Never rely solely on JavaScript to prevent submission
- Always use
action="javascript:void(0);"+preventDefault() - Never use GET for passwords
- sessionStorage: Tab-scoped, cleared on close
- Alternative: localStorage (persists, less secure)
- Backend always validates JWT
if (response.status === 401 && !isAuthPage) {
window.location.href = '/login.html';
}function createCard(data) {
const card = document.createElement('div');
card.innerHTML = `
<h3>${data.title}</h3>
${data.ready ? `<a href="${data.url}">Download</a>` : `<button disabled>Processing...</button>`}
`;
return card;
}- Always check actual API docs, not just SDK types
- Return types must be consistent (don't mix bool/dict)
- Use
isinstance()checks when mixing types - Test with real API to catch validation errors
export default defineConfig({
base: '/absproxy/3000/',
appType: 'mpa',
server: {
port: 3000,
proxy: {
'/absproxy/3000/api': {
target: 'http://localhost:5000',
rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
}
}
}
});const baseUrl = '/absproxy/3000/api/v1';
credentials: 'include' // Send JWT cookies<link rel="stylesheet" href="../styles/index.css">
<form action="javascript:void(0);">
<script type="module" src="../main.js"></script>- Check Vite running:
ps aux | grep vite - Check
basein vite.config.js - Restart:
npm run dev
- Use
/absproxy/3000/in URL - Check
base: '/absproxy/3000/'in config - Use relative paths in HTML
- Check
baseUrl = '/absproxy/3000/api/v1' - Check proxy rule in vite.config.js
- Restart backend:
docker-compose restart api
- Check login succeeded (Network tab)
- Check sessionStorage:
sessionStorage.getItem('pm_user') - Re-login
- Add
action="javascript:void(0);"to form
Custom debug endpoints for troubleshooting (require JWT auth):
GET /debug/supabase-status- Database connectivityGET /debug/user-videos/{user_id}- User's videosGET /debug/user-storage/{user_id}- Disk usageGET /debug/runware-jobs/{user_id}- Processing jobsGET /debug/gemini-status- Gemini API healthGET /debug/workers-status- Dramatiq workers
Usage:
fetch('/absproxy/3000/api/v1/debug/supabase-status', {credentials: 'include'})
.then(r => r.json())
.then(data => console.log('DB Status:', data))Root Cause: Missing Content-Type: application/json header
Solution: Added explicit header to request
Required columns: company, notifications_enabled, marketing_emails, default_quality Solution: Applied migration via Supabase Dashboard
ALTER TABLE users ADD COLUMN IF NOT EXISTS company VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS notifications_enabled BOOLEAN DEFAULT TRUE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS marketing_emails BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS default_quality VARCHAR(50) DEFAULT 'standard';Company saved successfully but showed placeholder on page reload.
- Frontend didn't call
updateProfileUI()after save UserResponsePydantic model missingcompanyfield - Pydantic stripped it from API response
Frontend: Added updateProfileUI(response.user) after save
Backend: Added fields to UserResponse
class UserResponse(BaseModel):
company: Optional[str] = None # ADDED
notifications_enabled: Optional[bool] = None # ADDED
marketing_emails: Optional[bool] = None # ADDED
default_quality: Optional[str] = None # ADDEDKey Learning: Pydantic response models actively filter - must include all fields you want returned.
Problem: Account deleted but user could still login with old JWT.
- JWT validation didn't check user existence - Only validated signature
- Delete endpoint didn't clear JWT cookies - Browser kept valid token
- Frontend session clearing delayed 1.5s - Race condition window
Fix #1: Validate User Existence (CRITICAL)
async def get_current_user(access_token: Optional[str] = Cookie(None)):
payload = verify_supabase_jwt(access_token)
user_id = payload.get("sub")
# THE FIX: Query database
response = supabase.table("users").select("*").eq("id", user_id).execute()
if not response.data:
raise HTTPException(status_code=401, detail="User account no longer exists")
return response.data[0]Fix #2: Clear JWT Cookies
async def delete_my_account(response: Response):
supabase.table("users").delete().eq("id", user_id).execute()
response.delete_cookie(key="access_token")
response.delete_cookie(key="refresh_token")Fix #3: Clear Session Immediately
sessionManager.clearSession(); // Immediate, no delay
setTimeout(() => window.location.href = '/login.html', 1500); // Only redirect delayedProblem: Supabase auth deletion permission error Solution: Wrapped in try-catch, gracefully continues. Database deletion is the security boundary.
- Never trust tokens alone - validate database state
- Defense in depth - multiple layers
- Graceful degradation - if one fails, others protect
- Clear user communication
- Fail-safe design - prioritize blocking access over cleanup
Programmatic account creation (Python script, not /register endpoint) only creates auth record, missing database profile.
- Supabase Auth - Handles passwords, issues JWTs
usersTable - Stores profiles, required byget_current_user()
When only auth created:
- Login succeeds (auth has credentials)
- Dashboard loads (JWT valid)
- API calls fail (user not in database)
- 401 "User account no longer exists"
Always create both:
# Step 1: Create auth
auth_response = supabase.auth.sign_up({"email": "...", "password": "..."})
# Step 2: Create database profile (REQUIRED)
supabase.table("users").insert({
"id": auth_response.user.id,
"email": "...",
"display_name": "..."
}).execute()Best Practice: Use /register endpoint - handles sync automatically.
Upload succeeded but processing failed with:
RunwareAPIError: 'failedToTransferImage' - Image URL not publicly accessible
NOT a code bug. Normal timing validation:
- Project idle 2 days, Vite dev server stopped
- Upload attempted anyway (API responsive)
- Image URL not yet publicly accessible
- Runware correctly validated: "not accessible"
- Error was accurate, expected behavior
Restarted Vite server: pm2 restart frontend
Next upload succeeded without code changes.
- ✅ No reliability issues
- ✅ 34-62% cheaper than direct Kling ($0.92 vs $1.40)
- ✅ Clear error messages
- ✅ Solid choice
Retry logic with exponential backoff (2s, 5s, 10s) for transient timing issues.
- Check service status first after inactivity
- Runware validation is correct
- Timing issues normal in distributed systems
- Clear error messages help debugging
For detailed session logs: → PHASE_2_TECHNICAL_LOG.md
For current status: → PHASE_2_PROGRESS.md
For implementation guides: → JavaScript Integration Guide → API Endpoints Reference
Document Version: 2.0 (Condensed) Created: 2025-10-12 Last Updated: 2025-11-07 Purpose: Quick reference for technical solutions and known issues