Files
PocketVeto/TROUBLESHOOTING.md
Jack Levy 989419665e chore: add robots.txt, update docs with mobile layout fix
- Add frontend/public/robots.txt: allow public pages, block auth/user-private routes and /api/
- ROADMAP.md: add mobile layout fix to UX & Polish shipped items
- TROUBLESHOOTING.md: add section on Android Chrome viewport overflow root cause and fix

Authored by: Jack Levy
2026-03-15 21:36:46 -04:00

5.4 KiB

Troubleshooting

Common issues encountered during development and deployment of PocketVeto.


502 Bad Gateway after rebuilding a container

Symptom

All API calls return 502. nginx error log shows:

connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://172.18.0.X:8000/api/..."

The IP in the error is the old IP of the container before the rebuild.

Root cause

When nginx uses upstream blocks, it resolves hostnames once at process startup and caches the result for the lifetime of the process. Rebuilding a container (e.g. docker compose build api && docker compose up -d api) assigns it a new Docker network IP. nginx still holds the old IP and all connections are refused.

Immediate fix

docker compose restart nginx

This forces nginx to re-resolve all upstream hostnames from Docker's internal DNS (127.0.0.11).

Permanent fix (already applied)

Replace upstream blocks with set $variable in proxy_pass. nginx only activates the resolver directive when a variable is used — making it re-resolve on each request cycle (every valid=N seconds).

resolver 127.0.0.11 valid=10s ipv6=off;

# BAD — resolves once at startup, caches forever
upstream api {
    server api:8000;
}
location /api/ {
    proxy_pass http://api;
}

# GOOD — re-resolves via resolver every 10 s
location /api/ {
    set $api http://api:8000;
    proxy_pass $api;
}

Wrong service name for docker compose exec

The API service is named api in docker-compose.yml, not backend.

# Wrong
docker compose exec backend alembic upgrade head

# Correct
docker compose exec api alembic upgrade head

Alembic migration not applied after rebuild

If a new migration file was added after the last image build, the API container won't have it baked in. The container runs alembic upgrade head at startup from the built image.

Fix: rebuild the API image so the new migration file is included, then restart:

docker compose build api && docker compose up -d api

Wrong postgres user

The database superuser is congress (set via POSTGRES_USER in .env / docker-compose.yml), not the default postgres.

# Wrong
docker compose exec postgres psql -U postgres pocketveto

# Correct
docker compose exec postgres psql -U congress pocketveto

Frontend changes not showing after editing source files

The frontend runs as a production Next.js build (NODE_ENV=production) — there is no hot reload. Code changes require a full image rebuild:

docker compose build frontend && docker compose up -d frontend

Static assets are cache-busted automatically by Next.js (content-hashed filenames), so a hard refresh in the browser is not required after the new container starts.


Celery tasks not reflecting code changes

Celery worker and beat processes also run from the built image. After changing any worker code:

docker compose build worker beat && docker compose up -d worker beat

Checking logs

# All services
docker compose logs -f

# Single service (last 50 lines)
docker compose logs --tail=50 api
docker compose logs --tail=50 nginx
docker compose logs --tail=50 worker

# Follow in real time
docker compose logs -f api worker

Mobile page wider than viewport (Android Chrome zoom / dead space)

Symptom

On Android Chrome, a page zooms out to fit an oversized layout. The content is readable but the bottom third of the screen is dead space (background color only). The same page looks fine on all other devices, and other pages on the same device are fine.

Root cause

Flex items default to min-width: auto, meaning they cannot shrink below their content's natural width. If any descendant (e.g. a long URL in an input field) is wider than the viewport, the containing flex column expands to match, making the entire page layout wider than the viewport. Android Chrome then zooms the viewport out to fit the full layout width — which makes the layout taller than the visual viewport, leaving dead space at the bottom.

Fix

Add min-w-0 (Tailwind) / min-width: 0 (CSS) to the flex item that forms the main content column. This allows the column to shrink to zero, enabling children to wrap and truncate normally.

In AuthGuard.tsx the content column needs both flex-1 and min-w-0:

// Before — missing min-w-0
<div className="flex-1 flex flex-col min-h-0">

// After
<div className="flex-1 flex flex-col min-h-0 min-w-0">

Also switch the outer shell from h-dvh to fixed inset-0 flex overflow-hidden so the shell is anchored to the visual viewport directly (avoids dvh staleness issues on Android):

// Before
<div className="flex h-dvh bg-background">

// After
<div className="fixed inset-0 flex overflow-hidden bg-background">

General rule: any flex-1 column that can contain user-supplied text or long URLs should also carry min-w-0.


Inspecting the database

docker compose exec postgres psql -U congress pocketveto

Useful queries:

-- Recent notification events
SELECT event_type, bill_id, dispatched_at, created_at
FROM notification_events
ORDER BY created_at DESC
LIMIT 20;

-- Follow modes per user
SELECT u.email, f.follow_type, f.follow_value, f.follow_mode
FROM follows f
JOIN users u ON u.id = f.user_id
ORDER BY u.email, f.follow_type;

-- Users and their RSS tokens
SELECT id, email, rss_token IS NOT NULL AS has_rss_token FROM users;