- Account tab bar with tab switching (loadAccounts, switchAccount)
- Per-account dashboard panel showing auth status, channels, jobs, continuous
- Settings dialog with account management (add/delete accounts, credentials)
- Auth forms per account (QR, phone, password, set credentials)
- All action buttons with error handling (alert on failure)
- AMOLED dark theme with Telegram-style message bubbles
- Account tab and account list CSS styles
- OptimizedTelegramScraper accepts account_id parameter;
sets DATA_DIR to data/accounts/<id>/ when provided
- Session path uses account_session_path() for per-account session files
- initialize_client() reads per-account credentials from account state store
- ScraperJobService passes account_id from payload to scraper constructor
- Per-account state loaded from AccountStateStore before scraping
- Chat bubbles with 1px border-radius, angular tail arrows
- Own messages right (blue), others left (dark) via sender_id vs user_id
- Reply preview with left border accent, clickable scroll to original
- Infinite scroll up via IntersectionObserver on scroll-sentinel
- Date separators (Today/Yesterday/date) between messages
- Full-height messages panel with sidebar (channels) + header
- Backend: reply_to_message data in load_messages(), user_id exposed in auth snapshot
- Auto-scroll to bottom on channel load (triple scrollTop fallback)
- history.scrollRestoration = manual to prevent mid-page jumps
- Graceful shutdown: SIGTERM/SIGINT handlers stop continuous scraper,
disconnect Telegram client, drain running jobs with timeout
- Logging: replace print() with logging module throughout web server,
basicConfig in main.py with structured log format
- Streaming: chunked file reads (64KB) instead of loading entire files
into memory; full HTTP Range Request support (206 Partial Content)
for video seeking in the browser
- Cache: TTL-based in-memory cache (1s) for state.json reads in
StateStore, thread-safe with existing RLock
- Bugfix: send_error_json now converts HTTPStatus to int for JSON