- query_understanding.py: get API key/base_url/provider_type from ai_providers DB
- embedding.py: get API key from DB, pass db+tenant_id through call chain
- routes.py: pass db+tenant_id to llm_analyze_query and llm_aggregate_results
- search_engine.py: pass db+tenant_id to generate_embedding
- unified_search/jobs.py: pass db+tenant_id to generate_embedding
- Fix all default model names: ollama/deepseek-v4 -> ollama/deepseek-v4-flash
- Ollama Cloud has no embedding endpoint; embedding calls fail gracefully
- _get_llm_api_key now returns provider_type
- Model string built with provider_type prefix (openai/deepseek-v4 instead of ollama/deepseek-v4)
- api_base set from provider.base_url (https://ollama.com/v1)
- Fixes: litellm tried localhost:11434 instead of Ollama Cloud
- ai_proactive now reads API key from ai_providers table (like ai_assistant)
- Falls back to API_KEY_OLLAMA_CLOUD env var if no provider found
- Fixes: proactive engine could not generate suggestions because API key was empty
- AISidebar: full height, right of TopBar and Toolbar (all pages except AI Assistant)
- AISidebar: collapsed mode shows narrow 48px icon strip with KI button
- AISidebar: expanded mode uses ResizablePanel with drag-to-resize (240-600px)
- AppShell: moved AISidebar outside inner content container to full-height flex
- uiStore: renamed aiSidebarOpen→aiSidebarCollapsed (default: true=collapsed)
- TopBar: AI button highlights when sidebar is expanded
- MailFolderTree: reduce padding/gap ~25% (py-2→py-1, gap-2→gap-1.5)
- MailList: text-sm→text-xs for from and subject lines
- ResizablePanel: use DOM ref for width during drag instead of React state
- No re-renders during drag, only single setWidth on mouseup
- panelRef on outer div, contentRef on inner div
- pointer-events:none on content during drag
- contain:strict on content area
- Add imap_uid column + unique index for proper deduplication
- Use deterministic message_id for emails without Message-ID header
- Dedup by (account_id, folder_id, imap_uid) first, message_id fallback
- Track folder moves: update folder_id when email appears in different folder
- Fix Sent folder lookup: check Sent/INBOX.Sent/Sent Items/Sent Mail + ILIKE
- Detect IMAP delimiter from LIST response instead of hardcoding dot
- Use detected delimiter in stale folder name migration
- Migration 0004_imap_uid.sql with imap_uid column and unique index
- Frontend: decodeMimeHeader() on attachment filenames in MailDetail for display
- Backend: _decode_mime_filename() helper with Q-encoding fallback for edge cases
- Attachments now visible in mail detail UI (was 0 records, now 44+)
- Notifications table updated_at column added via migration 0020
- Add _decode_mime_filename() helper with Q-encoding fallback decoder
- Use helper for both new and existing email attachment saving
- Handles edge cases where email.header.decode_header fails on partially processed filenames
- Decode =?utf-8?q?...?= filenames before sanitizing for both new and existing emails
- Prevents filenames like __utf-8_q_Wire_236095209372_2Epdf_2Ehtml__
- Uses email.header.decode_header + make_header for proper decoding
- IMAP sync now saves attachments for existing emails that have has_attachments=true
but no attachment records in the database (retroactive attachment saving)
- Replace silent exception catching with logger.warning for attachment save failures
- Add logging module import and logger instance
- This fixes the issue where 62 emails had has_attachments=true but 0 attachment records
- Add username field to account creation form (defaults to email if empty)
- Add is_shared checkbox toggle for shared mailboxes
- Update CreateAccountPayload type with optional username and is_shared
- The infinite re-render fix (previous commit) resolves the main issue:
accounts were not shown in settings because loadAccounts was looping,
flooding the browser with requests and preventing API calls from completing
- Fix infinite re-render: loadAccounts had selectedAccountId in deps causing
repeated fetches every render; use functional setState and empty deps
- Fix all error handlers in MailSettings: err?.message || err?.detail instead of String(err)
- Fix sync result handling: backend returns {synced, error?} not {success, synced_count}
- This infinite re-render was flooding the browser with requests, causing
Network Error for SignatureManager API calls
- Fix error handling in SignatureManager load/save/delete to extract message from ApiError
- Same pattern as sync fix: err?.message || err?.detail instead of String(err)
- Replace textarea in SignatureManager with TipTap RichTextEditor
- Add placeholder variable buttons: {{user.name}}, {{user.first_name}},
{{user.last_name}}, {{user.email}}, {{user.role}}, {{tenant.name}}, {{date}}
- Add replaceSignatureVariables() utility in mail API
- ComposeModal now replaces variables with actual user/tenant data when inserting signature
- Variables resolved from auth store (user name, email, role, tenant name)