CR-1: Path traversal in createReceiptZip — validate image_path is within storage/
CR-2: Missing authz on EditExpense/UpdateExpense — verify event ownership
CR-3: OTP timing side-channel — use crypto/subtle.ConstantTimeCompare
CR-4: Logout doesn't invalidate session — moved to AuthHandler with Sessions.Delete()
CR-5: OTP reuse race condition — mutex lock around validate+delete
CR-6: Live credentials on disk — removed .env from disk entirely
CR-7: No TLS — documented as expected behind-proxy deployment
Additional:
- Removed stale github.com/expenseflow import path from auth.go
- Made EnvironmentFile optional (prefix with -) so .env is not required
- App runs and starts clean without any .env file
- glm-ocr is a 1.1B parameter model built specifically for OCR
- No reasoning overhead, no thinking field issues
- Faster inference than qwen3.5 on CPU
- Removed old qwen3.5 models (2B + 0.8B) to free ~4GB disk
- Updated install.sh, ollama.go, .env.example defaults
- ollamaResponse now reads both content and thinking fields
- Falls back to thinking if content is empty
- Install script adds model warm-up prompt to preload into memory
- Warm-up uses OLLAMA_HOST for correct user context
- Makes first real receipt analysis faster
- .env.example: placeholder values only
- gemini.go: error on missing API key instead of fallback
- main.go: dynamic from address from SMTP_USER
- Security: old credentials removed from active codebase
- Zero CGO dependencies — builds with CGO_ENABLED=0
- Fully static binaries, no libc required
- Cross-compilation now works without any GCC cross-compilers
- ldd confirms: 'not a dynamic executable'
- Updates go.mod to Go 1.23 (required by modernc.org/sqlite)
- New provider architecture with common interface
- Provider selected via AI_PROVIDER env var (gemini/openai/ollama)
- Gemini (default): existing implementation, uses GEMINI_API_KEY
- OpenAI-compatible: uses OPENAI_API_KEY + AI_MODEL + AI_BASE_URL
- Works with OpenAI, Perplexity, Together AI, Groq, etc.
- Ollama: local LLM, uses AI_BASE_URL + AI_MODEL
- Supports llava, bakllava, and other vision models
- deepseek.go renamed to llm.go (cleanup)
- .env.example updated with all AI provider options
- Two upload buttons: Camera (capture) and Upload (gallery/PDF)
- PDF receipts from Uber/email now accepted and processed by Gemini Vision
- PDF detection via %PDF magic bytes in both handler and AI module
- Descriptions updated to reflect broader file support
- Email now includes both report (CSV/PDF) + ZIP of all receipt images
- ZIP images named {event-name}-{index}.{ext} matching list order
- Uses Go's archive/zip (stdlib, no external deps)
- Sender.SendReport now accepts []*Attachment for multiple files
- Gracefully skips missing image files with warnings
- New favicon: Rx symbol in emerald green square (prescription/receipt)
- Dark theme: #0F172A base, #1E293B cards, #F8FAFC text
- Emerald #10B981 primary accent throughout
- Dashboard simplified to list view with event name + Open button
- Event page: receipt list with edit, Add Receipt button, Submit Event form
- Submit form shows total claim amount + email + format selection
- PWA manifest + service worker updated for ReceiptNext branding
- Variables-based theming for easy palette switching
- Added ✏️ edit button on every expense in the list
- Clicking loads the receipt form pre-filled with the expense data
- Changes are saved via PUT /expenses/{id}
- Receipt form auto-switches between create (POST) and edit (PUT) mode
- Full HTMX multi-target response: form resets + list refreshes
- Instead of entering a hard-to-calculate rate like 0.00773,
users now enter a real sample (e.g. receipt=1000 KES, claimed=7.73 USD)
- The system computes the rate automatically: 7.73 / 1000 = 0.00773
- Users can get the sample values from their payment app notification
- Much more intuitive, especially for currencies with small exchange rates
- Added KES and 12+ additional currencies to receipt form
- Events now have base_currency (claim currency) and exchange_rate fields
- Receipts show original amount + auto-computed converted amount
- Converted amounts stored per expense in database
- CSV and PDF reports include both original and converted amounts
- Dashboard shows claim currency per event card
- DeepSeek API does not support vision (only chat UI supports images)
- Google Gemini Vision supports native image analysis via inline_data
- Images are sent directly as base64 with proper MIME type detection
- No more OCR pipeline needed - Gemini sees the image directly
- Supports: JPEG, PNG, WebP, GIF, BMP, TIFF, HEIC, AVIF
- Updated .env.example to use GEMINI_API_KEY instead of DEEPSEEK_API_KEY
- Also: log OTP code in server log for easier debugging
- Problem: deepseek-v4-flash is text-only, cannot process base64 images - hallucinated fake data
- Solution: Two-step pipeline that actually extracts real data:
1. Tesseract OCR extracts raw text from the receipt image
2. DeepSeek v4 parses the OCR text into structured JSON
- Benefits: works with any image format, fast, accurate, no hallucinated data
- Properly handles HEIC/HEIF via heif-convert before OCR
- DeepSeek no longer supports image_url in chat completions
- Images are now resized (max 300px) and JPEG-compressed (quality 50)
- Base64 data embedded directly in text prompt for processing
- Increased API timeout to 120s for larger prompts
- Also fixed mobile receipt capture (missing name attribute on file input)
- Also fixed OTP htmx:targetError (outerHTML → innerHTML swap)
- Passwordless email OTP authentication
- Event-based expense tracking with HTMX UI
- AI receipt extraction via DeepSeek Vision API
- CSV/PDF report generation with email filing
- PWA with service worker and manifest
- Mobile-first responsive design
- SQLite database with auto-migration