WANK 2.0 — WhatsApp Web Client Feature Expansion
Everything built on top of WANK 1 (the base system documented in
PROJECT-BREAKDOWN.md). This covers all new features, database changes, backend services, frontend components, and deployment infrastructure added across multiple development sessions.
Table of Contents
- What Changed Since WANK 1
- Architecture Additions
- Database Migrations
- Backend — New Services
- Backend — New & Enhanced Controllers
- Backend — New Routes
- Frontend — New Pages
- Frontend — New Components
- Frontend — Enhanced Components
- Frontend — New Hooks & Contexts
- Frontend — Type Additions
- Frontend — API Layer Additions
- Tooltip System
- Background Backfill System
- Avatar System
- Production Deployment Infrastructure
- Complete File Reference
- Key Lessons & Gotchas
What Changed Since WANK 1
WANK 1 delivered a working WhatsApp Web replacement: session management, chat list, message sending/receiving, media archival, QR login, and a dark-themed UI. WANK 2 adds everything that makes it useful:
| Category | What Was Added |
|---|---|
| AI Auto-Reply | Per-contact AI personalities with configurable providers (Mistral, OpenAI, Groq), delay modes, pause/resume, conversation context, activity logging |
| Contact Management | Editable contact fields (name, company, email, notes, tags, social media), VCF/CSV import/export with fuzzy phone matching |
| Background Backfill | Idle-time system that auto-populates phone numbers, contact details, message history, media, thumbnails, and avatars for top 25 recent chats |
| Avatar System | Custom avatar selection from contact’s media photos or WhatsApp profile pic, fetched via Puppeteer window.Store.ProfilePic API |
| Message Actions | Reply, react, star, pin, forward, copy, download media, delete (with for-everyone option) |
| Chat Pinning | Pin/unpin chats with timestamp ordering |
| Phone Mismatch Detection | Warning when a different phone number connects to an existing session |
| Media Storage Controls | Toggle original/thumbnail retention for outgoing media |
| Tooltip System | App-wide hover tooltips on every interactive element, toggleable from TopBar |
| Multi-Slice Deployment | 8 isolated WhatsApp instances on a single DigitalOcean droplet, managed by one script |
By the numbers:
| Metric | WANK 1 | WANK 2 | Growth |
|---|---|---|---|
| Backend services | 2 | 6 | +4 new |
| Controllers | 4 | 8 | +4 new, 4 enhanced |
| Route files | 4 | 8 | +4 new |
| Frontend components | 12 | 22 | +10 new, 8 enhanced |
| Frontend pages | 4 | 6 | +2 new |
| Types | 10 | 22 | +12 new |
| API methods | 14 | 45+ | +31 new |
| Database tables | 3 | 8 | +5 new |
| Database columns | ~30 | 80+ | +50 new |
| Total LOC | ~5,000 | ~11,500 | +130% |
Architecture Additions
WANK 1’s architecture remains intact. WANK 2 adds these layers:
Phone (linked device)
↕
WhatsApp Servers
↕
Headless Chromium (Puppeteer)
↕ whatsapp-web.js
↕ window.Store.ProfilePic ← NEW: Direct WA internal API for avatars
Express Backend (port 3101)
↕ PersonalityService ← NEW: AI personality assignment
↕ AIService ← NEW: LLM completion (Mistral/OpenAI/Groq)
↕ BackfillService ← NEW: Background contact enrichment
↕ ContactImportService ← NEW: VCF/CSV import with fuzzy matching
↕ REST API + Socket.io
React Frontend (port 3100)
↕ useIdleBackfill ← NEW: Idle detection + polling
↕ TooltipContext ← NEW: App-wide hover tips
↕ Vite dev proxy → backend
Browser
↕
Nginx (production) ← NEW: Reverse proxy for 8 slices
↕ wa1-wa8.ipnoelp.com
Key new design decisions:
- AI auto-reply uses OpenAI-compatible endpoint abstraction (works with any provider)
- Backfill is frontend-driven: idle detection in browser, one-step-per-call to backend
- Avatars fetched via Puppeteer page evaluate (bypasses WA API limitations)
- Tooltip state persisted in localStorage (survives page reload)
- Multi-slice deployment shares one frontend, proxies API to per-slice backends
Database Migrations
8 ordered migrations, all backwards-compatible (nullable columns or defaults).
Migration 001: AI Personality Auto-Reply System
File: database/migrations/001-ai-personalities.sql (79 lines)
5 new tables:
| Table | Purpose |
|---|---|
ai_config |
Single-row provider config (base_url, api_key, model, max_tokens, temperature) |
personalities |
Named personalities with emoji + system_prompt |
contact_personalities |
Which personality is assigned to which contact, plus mode/delay settings |
auto_reply_log |
Every AI-generated reply with timing + token metrics |
user_settings |
Generic key-value store for app settings |
Also adds messages.is_ai_reply BOOLEAN to track AI-generated messages.
Migration 002: Delay Presets & Custom Traits
File: database/migrations/002-delay-presets-and-custom-fields.sql (7 lines)
- Replaces
delay_seconds INTwithdelay_preset VARCHAR(‘30s’, ‘5m’, ‘random’) - Adds
custom_traits TEXTandknown_facts TEXTtocontact_personalities
Migration 003: Contact Management & Media Storage
File: database/migrations/003-contact-management-and-media-storage.sql (36 lines)
Contact fields: full_name, company, email, notes, location_met, tags TEXT[]
New media_files table for tracking storage tiers:
original_path, thumbnail_path, mime_type, file_size_bytes, thumb_size_bytesstorage_state(‘original’ | ‘thumbnail’ | ‘deleted’)from_meboolean for sent vs received
Settings: outgoing_store_original, outgoing_store_thumbnail
Migration 004: Social Media Fields
File: database/migrations/004-social-media-fields.sql (6 lines)
Adds instagram, tiktok, telegram to contacts.
Migration 005: Avatar Media ID
File: database/migrations/005-avatar-media-id.sql (4 lines)
Adds avatar_media_id INTEGER referencing media_files.id for custom avatars.
Migration 006: Backfill Settings
File: database/migrations/006-backfill-settings.sql (5 lines)
Seeds backfill_contact_count = '25' and backfill_photo_days = '28'.
Migration 007: Pinned Timestamp
File: database/migrations/007-pinned-at.sql (5 lines)
Adds pinned_at TIMESTAMPTZ to chats for pin ordering.
Migration 008: Avatar Checked
File: database/migrations/008-avatar-checked.sql (2 lines)
Adds avatar_checked BOOLEAN to contacts (tracks if WA profile pic has been fetched).
Backend — New Services
PersonalityService.ts (364 lines)
Singleton managing AI personalities and contact assignments.
Key methods:
getAll()/getById(id)/create()/update()/delete()setDefault(id)— marks one personality as the default for new contactsgetAssignments()— all contacts with personality assignmentsassignContact(waId, config)— assign personality + mode + delay + traitstogglePause(waId)— pause/resume auto-reply for one contactremoveAssignment(waId)— remove personality from contact
State: pendingDelays: Map<string, NodeJS.Timeout> for managing delayed reply timers.
AIService.ts (99 lines)
Singleton for LLM completion via OpenAI-compatible API.
Key methods:
getConfig()— fetch provider config fromai_configtableupdateConfig(updates)— save new provider/model/keygenerateReply(systemPrompt, history[], incomingMessage)→{ reply, tokensUsed, responseTimeMs }
Supports any OpenAI-compatible endpoint: Mistral, OpenAI, Groq, local models.
BackfillService.ts (617 lines)
Singleton for background contact enrichment. Processes one step per call (non-blocking).
6 steps per contact:
| # | Step | What It Does |
|---|---|---|
| 1 | resolve_phone |
Get phone number from chat via client.getContactById() |
| 2 | fetch_details |
Pull push_name, saved_name, about, is_business from WA |
| 3 | archive_messages |
Fetch last 10 messages, upsert to messages table |
| 4 | sync_media |
Download photos from last N days via mediaService.saveMedia() |
| 5 | generate_thumbs |
Create micro-thumbnails via mediaService.generateMissingMicroThumbnails() |
| 6 | cache_avatar |
Fetch WhatsApp profile pic via pupPage Puppeteer approach |
State (in-memory, resets on restart):
{ initialized, contacts: [{waId, chatName, currentStep, completed}],
currentIndex, totalContacts, completedContacts }
All operations are idempotent — safe to re-run after restart.
ContactImportService.ts (357 lines)
Import contacts from VCF/CSV with fuzzy phone matching.
Key methods:
parseVCF(content)→ParsedContact[]parseCSV(content)→ParsedContact[]matchContacts(parsed)→{ matched[], unmatched[] }(fuzzy match on last 10 digits)applyImport(matches)→{ applied: number }(upsert matched data)exportVCF()/exportCSV()— export all contacts
Backend — New & Enhanced Controllers
personalityController.ts (236 lines) — NEW
Handles both personality CRUD and AI config. Endpoints:
Personality CRUD:
GET /api/personalities→ all personalitiesPOST /api/personalities→ create{ name, emoji, system_prompt }PUT /api/personalities/:id→ updateDELETE /api/personalities/:id→ deletePUT /api/personalities/:id/default→ set as default
Contact Assignments:
GET /api/personalities/assignments→ all assignmentsGET /api/personalities/assign/:waId→ one contact’s assignmentPUT /api/personalities/assign/:waId→ assign{ personality_id, auto_reply_mode, delay_preset, custom_traits, known_facts }PUT /api/personalities/assign/:waId/toggle-pause→ pause/resumeDELETE /api/personalities/assign/:waId→ remove assignment
AI Config (also in this controller):
GET /api/ai/config→ provider configPUT /api/ai/config→ update{ provider_name, base_url, api_key, model, max_tokens, temperature }POST /api/ai/test→ test reply{ message }→{ reply, tokensUsed, responseTimeMs }GET /api/ai/log→ auto-reply logDELETE /api/ai/log/:id→ delete log entry
backfillController.ts (28 lines) — NEW
GET /api/backfill/status→ current backfill statePOST /api/backfill/next→ process one step, return progress
contactImportController.ts (82 lines) — NEW
POST /api/contacts/import(multipart) → parse + match → previewPOST /api/contacts/import/apply→ apply confirmed matches
contactController.ts (515 lines) — NEW
Contact CRUD:
GET /api/contacts→ list all contactsGET /api/contacts/:waId→ full contact detailsPUT /api/contacts/:waId→ update editable fieldsGET /api/contacts/tags→ all unique tags
Avatar Management:
GET /api/contacts/:waId/avatar→ avatar image dataPUT /api/contacts/:waId/avatar→ set avatar from media_idGET /api/contacts/:waId/avatar-photos→ list photos in contact’s media folderPOST /api/contacts/:waId/avatar-from-photo→ set specific photo as avatar
chatController.ts (415 lines) — ENHANCED
New endpoint:
POST /api/chats/:chatId/pin→ toggle pin with timestamp
messageController.ts (341 lines) — ENHANCED
New endpoints:
POST /api/messages/:chatId/react→ emoji reaction{ messageId, emoji }POST /api/messages/:chatId/reply→ quoted reply{ messageId, text }POST /api/messages/:chatId/forward→ forward{ messageId, targetChatId }POST /api/messages/:chatId/star→ toggle star{ messageId }POST /api/messages/:chatId/pin→ pin message{ messageId }POST /api/messages/:chatId/delete→ delete{ messageId, forEveryone }POST /api/messages/:chatId/download-media→ download{ messageId }→{ data, mimetype, filename }
Backend — New Routes
| File | Lines | Mounts At |
|---|---|---|
personalityRoutes.ts |
20 | /api/personalities |
aiRoutes.ts |
20 | /api/ai |
backfillRoutes.ts |
9 | /api/backfill |
contactRoutes.ts |
29 | /api/contacts |
Frontend — New Pages
PersonalitiesPage.tsx (289 lines)
Full-page personality manager at /personalities.
Sections:
- Personality Cards — grid of cards with emoji, name, system prompt preview. Each has star (set default), edit, delete buttons.
- Contact Assignments Table — shows all assigned contacts with personality emoji/name, auto-reply mode (disabled/instant/delayed), delay preset, pause state. Remove button per row.
- Create/Edit Modal — emoji input, name input, system_prompt textarea. Validation requires all three fields.
AISettingsPage.tsx (292 lines)
AI provider configuration at /ai-settings.
Sections:
- Provider Presets — one-click buttons for Mistral, OpenAI, Groq (fills base_url + model)
- Configuration Form — base_url, API key (password field with show/hide), model, max_tokens, temperature
- Test Panel — send a test message, see AI reply + tokens + response time
- Reply Log — scrollable list of all auto-replies with incoming message, AI response, personality, tokens, timing, timestamp
Frontend — New Components
PersonalityPicker.tsx (251 lines)
Inline personality assignment widget embedded in ContactProfile.
- Dropdown to select personality (or None)
- Mode selector: Disabled / Instant / Delayed
- Delay preset: 30s / 5m / Random 1-5m
- Custom traits textarea
- Known facts textarea
- Pause/play toggle button
- Auto-saves on every change
AvatarPicker.tsx (173 lines)
Modal for selecting a contact’s avatar.
- Grid of photos from contact’s media folder
- “Use WhatsApp profile picture” button (fetches via pupPage)
- “Clear avatar” button
- Single selection with confirm
BackfillBanner.tsx (157 lines)
Banner between ActionBar and content area showing backfill progress.
Phases:
| Phase | Display |
|---|---|
idle |
Hidden |
intro |
“Leave the app idle and I’ll populate your contacts’ info” (auto-dismiss 8s) |
running |
Spinner + current action + “5/25 (20%)” + thin green progress bar |
paused |
Paused indicator (user became active) |
complete |
Checkmark + “Backfill complete” |
error |
Error state |
ContactEditForm.tsx (286 lines)
Editable contact details form in ContactProfile.
- Full name, company, email, location met (text inputs)
- Notes (multiline textarea)
- Tags (type + Enter to add, autocomplete from all existing tags)
- Instagram, TikTok, Telegram handles
- Auto-save with 500ms debounce (no save button)
ContactImportExport.tsx (210 lines)
Import/export panel in Settings page.
- File upload area (drag-drop or click) for VCF/CSV
- Preview modal showing matched vs unmatched contacts
- Apply button to commit matched data
- Export VCF / Export CSV buttons
- Help accordion with platform-specific instructions (Google, iCloud, Android, Outlook)
ForwardModal.tsx (99 lines)
Chat picker modal for message forwarding.
- Search input to filter chats
- Chat list with avatars and names
- Single selection → forward
MediaStorageSettings.tsx (147 lines)
Toggle switches for outgoing media storage in Settings.
- Store outgoing originals toggle
- Store outgoing thumbnails toggle
- Storage stats display (total tracked, originals, thumb-only, expunged, bytes used)
MessageContextMenu.tsx (223 lines)
Right-click context menu on messages.
Actions: Reply, React (emoji), Star/Unstar, Pin, Forward, Copy text, Download media, Delete (with for-everyone option)
- Positioned relative to click coordinates
- Click-outside dismissal
- All actions call messageAPI methods
PersonalityBadge.tsx (24 lines)
Small emoji + name badge showing a contact’s assigned personality.
CollapsibleSection.tsx (76 lines)
Generic expandable section with chevron animation. Used in ContactProfile to organize Contact Details, AI Auto-Reply, and WhatsApp Info.
Frontend — Enhanced Components
TopBar.tsx (334 lines, was 190)
Added:
- Tooltip toggle button (? icon, toggles all hover tips app-wide)
- Backfill status indicator
- Navigation to new pages (Personalities, AI Settings)
ActionBar.tsx (214 lines, was 209)
Added:
- Tip wrappers on all action buttons
ContactProfile.tsx (219 lines, was 163)
Restructured into collapsible sections:
- Contact Details →
ContactEditForm - AI Auto-Reply →
PersonalityPicker - WhatsApp Info → existing WA data display
- Avatar click →
AvatarPickermodal - Social media links (Instagram, TikTok, Telegram)
ChatListItem.tsx (212 lines, was 76)
Added:
- Pin/unpin toggle (pin icon appears on hover)
- Custom avatar display (from avatar_media_id)
- Avatar retry logic (useRef → useState + useEffect fix)
- Red X indicator when WA profile pic unavailable
ConversationView.tsx (153 lines, was 55)
Added:
- Forward modal integration
- Message context menu integration
- Reply-to preview
MessageBubble.tsx (140 lines, was 48)
Added:
- Dropdown arrow for context menu
- Star indicator
- AI reply indicator
- Reaction display
MessageComposer.tsx (105 lines, was 77)
Added:
- Reply-to preview with cancel
- Tip wrappers on buttons
ChatList.tsx (146 lines, was 84)
Added:
- Pinned chats sorted to top by pinned_at timestamp
LoginPage.tsx (173 lines, was 69)
Added:
- Phone mismatch detection and warning UI
- Confirm/disconnect buttons when different phone connects
- Loading progress bar
- Connect elapsed timer
SettingsPage.tsx (336 lines, was 62)
Added:
- MediaStorageSettings component
- ContactImportExport component
- Backfill configuration section
Frontend — New Hooks & Contexts
useIdleBackfill.ts (169 lines)
Manages idle-time background contact backfill.
How it works:
- On WA
ready, shows intro toast for 8 seconds - After intro, starts monitoring user activity (mousemove, keydown, click, touchstart, scroll)
- After 5 seconds idle → begins polling
POST /api/backfill/nextevery 3 seconds - Each poll processes ONE step for ONE contact (~1-3s each)
- Any user activity → pauses polling immediately
- User goes idle again → resumes where it left off
- When all contacts complete → shows completion message
Returns: { phase, totalContacts, completedContacts, currentContact, currentStep, detail }
TooltipContext.tsx (28 lines)
Context provider for app-wide tooltip toggle.
enabled: boolean— persisted inlocalStorageastooltips_enabledtoggle()— flip enabled state- Wraps entire app in
TooltipProvider
Frontend — Type Additions
12 new types added to frontend/src/types/index.ts (252 lines, was 105):
| Type | Fields |
|---|---|
Personality |
id, name, emoji, system_prompt, is_default, created_at, updated_at |
ContactAssignment |
contact_wa_id, personality_id, auto_reply_mode, delay_preset, is_paused, custom_traits, known_facts, personality_name, personality_emoji |
AIConfig |
id, provider_name, base_url, api_key_preview, model, max_tokens, temperature |
AutoReplyLog |
id, contact_wa_id, personality_id, incoming_message, ai_response, model_used, tokens_used, response_time_ms, personality_name, personality_emoji, created_at |
UserSettings |
key-value: auto_reply_enabled, away_mode, backfill_contact_count, etc. |
ContactEditable |
full_name, company, email, notes, location_met, tags[], instagram, tiktok, telegram |
ContactFull |
extends ContactEditable + avatar_media_id, avatar_url |
ImportPreview |
matched[], unmatched[], totalParsed |
MatchResult |
wa_id, push_name, imported_name, imported_phone, imported_email, imported_company |
MediaFile |
id, message_wa_id, chat_wa_id, original_path, thumbnail_path, mime_type, storage_state |
ReplyContext |
messageId, text, senderName |
BackfillState |
phase, totalContacts, completedContacts, currentContact, currentStep, detail |
Frontend — API Layer Additions
frontend/src/services/api.ts (268 lines, was 84) now exports 8 API modules:
personalityAPI (10 methods)
getAll, create, update, delete, setDefault,
getAssignments, getContactAssignment, assignContact, togglePause, removeAssignment
aiAPI (5 methods)
getConfig, updateConfig, test, getLog, deleteLogEntry
backfillAPI (4 methods)
getStatus, processNext, initialize, reset
contactAPI (10 methods)
listContacts, getContact, updateContact, getTags,
getAvatar, setAvatar, getAvatarPhotos, setAvatarFromPhoto,
importContacts, applyImport, exportContacts
messageAPI — Enhanced (7 new methods)
react, reply, forward, star, pin, deleteMessage, downloadMedia
mediaStorageAPI (3 methods)
getSettings, updateSettings, getStats
statusAPI — Enhanced (1 new method)
confirmPhone
Tooltip System
A complete app-wide tooltip system added across every interactive element.
Infrastructure
| File | Purpose |
|---|---|
TooltipContext.tsx |
Boolean enabled state + localStorage persistence |
Tip.tsx |
Wrapper component: hover-triggered, 4-position, viewport-clamped |
| TopBar toggle button | ? icon in top bar to enable/disable all tips |
Tip Component Details
<Tip text="Descriptive help text" position="bottom">
<button>Click me</button>
</Tip>
- Positions: top, bottom, left, right
- Styling:
bg-wa-bg-deep/95, green border (border-wa-green/30), backdrop blur - Max width: 280px, multiline support via
whitespace-pre-line - Viewport clamping: Auto-adjusts horizontal position if tooltip overflows screen edges
- Zero-cost when disabled: Returns bare
{children}whenenabled === false
Files with Tips (22 total)
Every interactive element in the app has a descriptive tooltip:
Components (16): TopBar, ActionBar, ChatList, ChatListItem, ConversationView, MessageComposer, MessageBubble, ContactProfile, ContactEditForm, PersonalityPicker, AvatarPicker, CollapsibleSection, MessageContextMenu, ForwardModal, ContactImportExport, MediaStorageSettings
Pages (6): LoginPage, ChatPage, SettingsPage, PersonalitiesPage, AISettingsPage, MediaBrowserPage
Background Backfill System
Problem
When opening the app, the chat list shows names but no photos, contact details, or message history.
Solution
Frontend-driven idle polling: detect user inactivity, process contacts one step at a time.
Flow
App loads → WA ready → intro toast (8s)
↓
User goes idle (5s no activity)
↓
Poll POST /api/backfill/next every 3s
↓ (each call = 1 step for 1 contact)
User moves mouse → pause
↓
User idle again (5s) → resume
↓
All 25 contacts × 6 steps = 150 calls ≈ 7-8 min
↓
"Backfill complete" toast
Settings (user_settings table)
| Key | Default | Purpose |
|---|---|---|
backfill_contact_count |
'25' |
How many recent non-group chats to process |
backfill_photo_days |
'28' |
How far back to sync media |
Avatar System
The Problem
WhatsApp profile pictures aren’t available through the standard whatsapp-web.js API for many contacts.
The pupPage Breakthrough
The client.pupPage property exposes the Puppeteer page running WhatsApp Web internally. We can page.evaluate() to call WhatsApp’s internal JavaScript APIs directly:
const url = await client.pupPage.evaluate(async (jid: string) => {
const result = await window.Store.ProfilePic.profilePicFind(jid);
return result?.eurl || null;
}, contact.id._serialized);
This uses WhatsApp Web’s internal Store.ProfilePic module to query the profile picture cache, which has far better coverage than the official API.
Avatar Priority
- Custom avatar (user-selected from media photos via
avatar_media_id) - WhatsApp profile picture (fetched via pupPage, cached on disk)
- Default placeholder
Red X Indicator
When a contact’s WhatsApp profile pic is unavailable (private or no photo), ChatListItem shows a small red X on the avatar to indicate the photo couldn’t be fetched. Controlled by the avatar_checked flag — only shows after we’ve actually tried to fetch.
Production Deployment Infrastructure
Multi-Slice Architecture
DigitalOcean Droplet (178.128.183.166)
↓
Nginx (ports 80/443)
├── wa1.ipnoelp.com → wa-slice-1 (port 4001, db: wa_slice_1)
├── wa2.ipnoelp.com → wa-slice-2 (port 4002, db: wa_slice_2)
├── wa3.ipnoelp.com → wa-slice-3 (port 4003, db: wa_slice_3)
└── ... up to wa8 (port 4008)
└── All share: /deploy/frontend-dist/ (static React build)
Each slice is a fully isolated WhatsApp client: own database, own WA session, own media storage. Managed by a single script.
manage.sh (285 lines)
./manage.sh status # Show all slices
./manage.sh create N # Create + start slice N
./manage.sh stop N # Stop slice N
./manage.sh start N # Start existing slice N
./manage.sh restart N # Restart slice N
./manage.sh destroy N # Stop + keep data
./manage.sh purge N # Delete everything
./manage.sh logs N # Tail logs
./manage.sh build # Rebuild Docker image
./manage.sh start-all # Start all slices
./manage.sh stop-all # Stop all
Deployment Workflow
# 1. Sync source to droplet
rsync -avz --exclude='node_modules' --exclude='dist' \
-e "ssh -i ~/.ssh/cms_droplet" \
frontend/src/ root@178.128.183.166:/home/chas-watkins/code/WhatsApp/frontend/src/
# 2. Build on droplet
ssh -i ~/.ssh/cms_droplet root@178.128.183.166 \
"cd /home/chas-watkins/code/WhatsApp/frontend && npx vite build"
# 3. Copy to Nginx root (all slices share this)
ssh -i ~/.ssh/cms_droplet root@178.128.183.166 \
"cp -r /home/chas-watkins/code/WhatsApp/frontend/dist/* \
/home/chas-watkins/code/WhatsApp/deploy/frontend-dist/"
Per-Slice Resources
| Resource | Pattern |
|---|---|
| Backend port | 400N (4001-4008) |
| Database port | 543N (5431-5438) |
| Database name | wa_slice_N |
| Docker container | wa-slice-N |
| Session volume | wa_session_N |
| Media directory | /data/wa-slice-N/media/ |
Complete File Reference
New Backend Files
| File | Lines | Purpose |
|---|---|---|
backend/src/services/PersonalityService.ts |
364 | AI personality CRUD + contact assignments |
backend/src/services/AIService.ts |
99 | LLM completion via OpenAI-compatible API |
backend/src/services/BackfillService.ts |
617 | Background contact enrichment pipeline |
backend/src/services/ContactImportService.ts |
357 | VCF/CSV import with fuzzy phone matching |
backend/src/controllers/personalityController.ts |
236 | Personality + AI config endpoints |
backend/src/controllers/backfillController.ts |
28 | Backfill status + next-step endpoint |
backend/src/controllers/contactImportController.ts |
82 | Import/export endpoints |
backend/src/controllers/contactController.ts |
515 | Contact CRUD + avatar management |
backend/src/routes/personalityRoutes.ts |
20 | Personality endpoint routing |
backend/src/routes/aiRoutes.ts |
20 | AI config endpoint routing |
backend/src/routes/backfillRoutes.ts |
9 | Backfill endpoint routing |
backend/src/routes/contactRoutes.ts |
29 | Contact endpoint routing |
Enhanced Backend Files
| File | Lines | Was | Change |
|---|---|---|---|
backend/src/services/MediaService.ts |
622 | ~455 | +media_files tracking, storage states, deletion |
backend/src/services/WhatsAppService.ts |
448 | ~341 | +auto-reply, personality integration, conversation history |
backend/src/controllers/chatController.ts |
415 | ~200 | +pin/unpin, avatar integration |
backend/src/controllers/messageController.ts |
341 | ~124 | +react, reply, forward, star, pin, delete, download |
backend/src/controllers/mediaController.ts |
346 | ~347 | ~same (minor adjustments) |
New Frontend Files
| File | Lines | Purpose |
|---|---|---|
frontend/src/pages/PersonalitiesPage.tsx |
289 | AI personality management page |
frontend/src/pages/AISettingsPage.tsx |
292 | AI provider config + test + log page |
frontend/src/components/PersonalityPicker.tsx |
251 | Inline personality assignment widget |
frontend/src/components/AvatarPicker.tsx |
173 | Modal avatar selection from photos/WA |
frontend/src/components/BackfillBanner.tsx |
157 | Backfill progress banner |
frontend/src/components/ContactEditForm.tsx |
286 | Editable contact fields with auto-save |
frontend/src/components/ContactImportExport.tsx |
210 | VCF/CSV import/export UI |
frontend/src/components/ForwardModal.tsx |
99 | Chat picker for message forwarding |
frontend/src/components/MediaStorageSettings.tsx |
147 | Outgoing media storage toggles |
frontend/src/components/MessageContextMenu.tsx |
223 | Right-click message action menu |
frontend/src/components/PersonalityBadge.tsx |
24 | Emoji + name badge |
frontend/src/components/CollapsibleSection.tsx |
76 | Generic expandable section |
frontend/src/components/Tip.tsx |
90 | Hover tooltip component |
frontend/src/hooks/useIdleBackfill.ts |
169 | Idle detection + backfill polling |
frontend/src/contexts/TooltipContext.tsx |
28 | Tooltip enabled state + localStorage |
Enhanced Frontend Files
| File | Lines | Was | Change |
|---|---|---|---|
frontend/src/App.tsx |
67 | 46 | +TooltipProvider, AppLayout extraction, backfill integration |
frontend/src/services/api.ts |
268 | 84 | +6 new API modules (31 new methods) |
frontend/src/types/index.ts |
252 | 105 | +12 new type interfaces |
frontend/src/contexts/WhatsAppContext.tsx |
321 | 233 | +phone mismatch, confirmPhone, backfill state |
frontend/src/components/TopBar.tsx |
334 | 190 | +tooltip toggle, new page links |
frontend/src/components/ActionBar.tsx |
214 | 209 | +tip wrappers |
frontend/src/components/ContactProfile.tsx |
219 | 163 | +collapsible sections, personality, edit form, avatar |
frontend/src/components/ChatListItem.tsx |
212 | 76 | +pinning, custom avatar, retry logic, red X |
frontend/src/components/ConversationView.tsx |
153 | 55 | +forward modal, context menu, reply preview |
frontend/src/components/MessageBubble.tsx |
140 | 48 | +context menu trigger, star/AI indicators, reactions |
frontend/src/components/MessageComposer.tsx |
105 | 77 | +reply preview, tip wrappers |
frontend/src/components/ChatList.tsx |
146 | 84 | +pinned sort |
frontend/src/components/QRCode.tsx |
61 | 21 | +enhanced styling |
frontend/src/pages/LoginPage.tsx |
173 | 69 | +phone mismatch UI, progress bar |
frontend/src/pages/ChatPage.tsx |
100 | 64 | +backfill banner integration |
frontend/src/pages/SettingsPage.tsx |
336 | 62 | +media storage, import/export, backfill config |
frontend/src/pages/MediaBrowserPage.tsx |
903 | 835 | +tip wrappers, minor enhancements |
Database Files
| File | Lines | Purpose |
|---|---|---|
database/migrations/001-ai-personalities.sql |
79 | 5 tables + is_ai_reply column |
database/migrations/002-delay-presets-and-custom-fields.sql |
7 | Delay preset + custom traits |
database/migrations/003-contact-management-and-media-storage.sql |
36 | Contact fields + media_files table |
database/migrations/004-social-media-fields.sql |
6 | Instagram, TikTok, Telegram |
database/migrations/005-avatar-media-id.sql |
4 | Custom avatar reference |
database/migrations/006-backfill-settings.sql |
5 | Backfill defaults |
database/migrations/007-pinned-at.sql |
5 | Chat pin timestamp |
database/migrations/008-avatar-checked.sql |
2 | Avatar fetch tracking |
Deployment Files
| File | Lines | Purpose |
|---|---|---|
deploy/manage.sh |
285 | Multi-slice management script |
deploy/docker-compose.yml |
~100 | Production orchestration |
deploy/init-databases.sh |
~150 | Per-slice DB initialization |
deploy/Dockerfile.backend |
~44 | Production backend image |
deploy/nginx/ |
various | Reverse proxy configs |
Key Lessons & Gotchas
-
pupPage is the avatar breakthrough —
client.pupPage.evaluate()lets you call WhatsApp Web’s internalwindow.Store.ProfilePicAPI, which has far better profile picture coverage than the officialwhatsapp-web.jsmethods. Use string-template evaluate to avoid TypeScript window reference errors. -
useRef → useState for avatar retry —
ChatListItemavatar loading used auseReffor retry count, but changes to refs don’t trigger re-renders. Converting touseState+useEffectfixed the avatar display bug. -
Frontend-driven backfill beats backend cron — Making the frontend poll (with idle detection) instead of running a background job means zero wasted work when nobody’s watching, natural pause/resume on user activity, and simple state management.
-
All new columns must be nullable or have defaults — Every migration adds nullable columns. This keeps the schema backwards-compatible and means migrations can run on a live database without downtime.
-
Auto-save with debounce > explicit save buttons —
ContactEditFormsaves every field change with a 500ms debounce. Users never lose data, and there’s no “did I save?” anxiety. -
One frontend, N backends — Production deploys a single built frontend to Nginx’s root directory (
/deploy/frontend-dist/). All 8 WhatsApp slices share the same UI. API calls are routed by Nginx based on subdomain. -
Backend runs from
dist/notsrc/— After editing.tsfiles, you MUST runnpx tscbefore restarting. The Docker container runs compiled JS fromdist/, not TypeScript fromsrc/. -
node_moduleslives inside Docker only — On the host machine,node_modulesis empty. To runtsc --noEmitor any Node tooling, usedocker exec whatsapp-frontend npx tsc --noEmit. -
Tooltip zero-cost pattern — When
enabled === false, theTipcomponent returns bare{children}with no wrapper div, event listeners, or state. This means tooltips have literally zero runtime cost when disabled. -
VCF phone matching uses last-10-digits fuzzy — International phone number normalization is unreliable, so the import system falls back to matching on the last 10 digits of normalized numbers. This handles most country code / formatting differences.