Web App Agent Guide
Agent documentation for the Next.js web application (Peripheral).
How to Run
npm install
npm run dev # Dev server at http://localhost:3000
npm run build # Production build
npm run lint # ESLint
npm run test # Jest tests
Environment Variables
Required in .env.local:
| Variable | Purpose |
|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Supabase project URL (public) |
| SUPABASE_URL | Supabase URL for server routes |
| SUPABASE_ANON_KEY | Anon/public key for API calls |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Anon key (browser) |
| SUPABASE_SERVICE_ROLE_KEY | Service role key (server only, writes) |
| NEXT_PUBLIC_MAPBOX_TOKEN | Mapbox GL JS access token |
NEVER commit secrets. Reference env var names only.
Agent Roles
1. Frontend Agent (Next.js / Mapbox / UI)
Scope: React components, Mapbox integration, Tailwind styling, ThemeContext.
Key Files:
app/venezuela/page.tsx- Live article with map + timelineapp/contexts/ThemeContext.tsx- Light/dark theme toggleapp/layout.tsx- Root layout, Source Serif 4 font via next/font
Responsibilities:
- Modify UI components and styling
- Handle Mapbox marker interactions
- Manage edit mode for pin dragging
- Ensure hydration-safe rendering (see hydration-timezones skill)
2. Backend/API Agent
Scope: Next.js API routes, data shaping, response formatting.
Key Files:
app/api/videos/[country]/route.ts- Fetch geolocated videos by country (primary)app/api/ice/videos/[country]/route.ts- Fetch videos by country (deprecated, backwards compatibility)app/api/stories/country/[country]/route.ts- Fetch stories by countryapp/api/video/[id]/position/route.ts- Update video coordinatesapp/api/admin/add-videos/route.ts- Insert videos (service role)app/api/admin/remove-video/route.ts- Search/delete videos
Responsibilities:
- Shape API responses for frontend consumption
- Handle query params (limit, offset, filters)
- Log requests for debugging
- Use appropriate Supabase client (server vs service)
3. Data / Supabase Operator
Scope: Database queries, migrations, data integrity.
Key Tables:
video- Video metadata + coordinates (country, latitude, longitude)story- Clustered stories with title, summary, timestampsnews_item- Individual news articles linked to storiesentity_location- Location entitiesstory_entity_location- Story-location join (rank=1 for primary)osint_source- Source metadata
Supabase Clients (lib/supabase/):
server.ts-supabaseServer()- Uses SUPABASE_URL + SUPABASE_ANON_KEYclient.ts-supabaseClient()- Uses NEXT_PUBLIC_* vars (browser)service.ts-createServiceClient()- Uses SERVICE_ROLE_KEY (writes)
Safety Rules:
- Select before update - Always query first to verify record exists
- DB writes only when explicitly requested - Never auto-commit changes
- Use service client for writes - Only
createServiceClient()has write perms - Log before/after - Log state before and after mutations
4. SEO / Editor Agent
Scope: Metadata, structured data, crawlability, SSR strategy.
Responsibilities:
- Add semantic HTML (article, header, time, etc.)
- Configure metadata in page exports
- Add JSON-LD structured data (NewsArticle/Article)
- Ensure SSR for critical content (client components affect indexing)
- Maintain sitemap.xml and robots.txt
Reference: See skills/seo-article-pages/SKILL.md
5. QA / Debug Agent
Scope: Hydration errors, timezone issues, regressions.
Common Issues:
- Hydration mismatch from locale-dependent date formatting
- Timezone drift between server/client
- Missing Mapbox token
- Stale video positions after edit mode
Reference: See skills/hydration-timezones/SKILL.md
Venezuela Episode Runbook
Video Data Flow
- Source: Videos stored in
videotable withcountry='VE' - API:
GET /api/videos/VEqueries and transforms toVideoMarker[] - Fields:
video_id,title,channel,published_date,latitude,longitude,public_url,source_url - Display: Mapbox markers with draggable pins in edit mode
Story Data Flow
- Source: Stories linked via
entity_locationwhere name ilike 'venezuela' - API:
GET /api/stories/country/venezuela?limit=200 - Join Path:
story->story_entity_location(rank=1) ->entity_location - Frontend Pruning: Filtered by episode keywords (NOT database filtered)
Episode Keywords (Client-Side Pruning)
const episodeKeywords = [
'venezuela', 'maduro', 'tanker', 'oil', 'blockade',
'naval', 'ship', 'vessel', 'seize', 'capture',
'strike', 'coast', 'fleet'
]
Stories are fetched with limit=200, then filtered client-side:
const filtered = stories.filter(s => {
const haystack = `${s.title} ${s.summary}`.toLowerCase()
return episodeKeywords.some(k => haystack.includes(k))
})
Do NOT modify the database to add pruning - keep filtering in frontend.
Timeline Computation
Location: app/venezuela/page.tsx lines 257-312
- UTC Normalization: All dates converted to UTC midnight to avoid hydration mismatch
- Day Bucketing: Videos + stories grouped by
YYYY-MM-DDUTC key - Date Range: From earliest story/video to today (UTC)
- Stacked Segments: Stories (lighter) stacked under videos (darker)
- Click Behavior: Clicking a bar with videos triggers
handleVideoClick(firstVideo)
const utcDate = new Date(Date.UTC(
parsed.getUTCFullYear(),
parsed.getUTCMonth(),
parsed.getUTCDate()
))
Edit Mode
- Toggle via edit button in header
- Markers become draggable when
editMode=true - Drags tracked in
pendingChangesMap - Save button calls
PATCH /api/video/[id]/positionfor each - After save, videos refetched to verify positions
Known Pitfalls
| Issue | Cause | Fix |
|---|---|---|
| Hydration mismatch | Locale-dependent toLocaleDateString() | Use UTC + deterministic Intl config |
| Timeline dates off | Timezone drift | Use Date.UTC() for all bucketing |
| Mapbox blank | Missing NEXT_PUBLIC_MAPBOX_TOKEN | Check .env.local |
| Pins not saving | Wrong Supabase client | Use createServiceClient() for writes |
| Stale positions | Browser cache | API returns Cache-Control: no-store |
Quick Reference
| Task | File/Command |
|---|---|
| Run dev server | npm run dev |
| Venezuela page | app/venezuela/page.tsx |
| Videos API | app/api/videos/[country]/route.ts |
| Stories API | app/api/stories/country/[country]/route.ts |
| Position update | app/api/video/[id]/position/route.ts |
| Supabase service | lib/supabase/service.ts |
| Theme context | app/contexts/ThemeContext.tsx |