- favicon.svg: indigo/purple gradient rounded square with 3 persona silhouettes (focus group concept)
- index.html: link SVG favicon (with ICO fallback), set title to Semblance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: /generate-discussion-guide now returns task_id immediately (202)
and runs generation as a background asyncio task, delivering the guide
via WebSocket task_completed event (bypasses GCP LB 30s timeout)
- Frontend: useDiscussionGuideGeneration awaits ws:task_completed event
to resolve the guide Promise instead of waiting on the HTTP response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_fg_logger was used but never defined, causing a NameError on every
PUT /focus-groups/:id request that included llm_model (i.e. all autosave
and handleSubmit updates) — resulting in a 500 Internal Server Error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FocusGroup.update: use matched_count > 0 instead of modified_count > 0
so updates succeed even when data is unchanged (was returning 500)
- useFocusGroupAutoSave: skip save if name is empty (not all-fields-empty)
preventing 400 Bad Request when autosave fires before name is filled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes all debug/verbose console.log calls across frontend to prevent
sensitive data exposure (session IDs, tokens) and reduce console noise.
Keeps only console.error and console.warn for genuine errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Don't send full persona objects in WS event — only send counts.
Frontend navigates to list page where personas load from API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: /generate-personas-full now returns task_id immediately (202)
and runs generation as a background asyncio task, delivering results
via WebSocket task_completed event (bypasses GCP LB 30s timeout)
- Frontend: AIRecruiter listens for ws:task_completed to process personas
instead of awaiting the long HTTP response
- Remove 53 debug console.log calls from websocketServiceNew.ts including
session_id exposure and a self-test emit that was firing fake events
- Remove debug logs from WebSocketContextNew, AIRecruiter, personaGenerator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
google-genai SDK uses aiohttp when it's available in the environment
(installed via llama-index-core), causing AssertionError (connector is None)
on async requests. Pass httpx_async_client in HttpOptions to bypass aiohttp.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server-level COOP prevents parent from monitoring popup.location,
so loginPopup never resolves in parent. Redirect flow is the correct
solution: page navigates to Microsoft, returns with #code, and
handleRedirectPromise exchanges the idToken with backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MSAL parent polls popup URL for auth code (same-origin), so the popup
doesn't need to render anything. Prevents app from opening inside popup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
same-origin-allow-popups allows the Microsoft auth popup to communicate
back to the parent window, which is required for loginPopup to work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The real issue was the wrong domain (oliver.solution vs oliver.solutions).
Now that Azure has the correct redirect URI configured, popup flow works.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Popup-based auth fails in production due to Cross-Origin-Opener-Policy
headers blocking window.closed calls. Redirect flow avoids this entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- deploy.sh runs entirely on server, no local Mac needed
- docker-compose: add 'frontend' service (node:20-alpine, profile=build)
builds frontend and copies dist/ to /var/www/html/semblance
- Remove server-deploy.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Build frontend locally (Node not installed on server)
- rsync dist/ to server instead of building remotely
- Change PYTHON_CMD to python3 (server has 3.12, not 3.13)
- Add step to start MongoDB in Docker (mongo:7, not installed natively)
- Add step to inject Apache proxy config for /semblance_back/ and /semblance/
- SSH_HOST=optical-dev (uses ~/.ssh/config alias)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ai-sandbox.oliver.solutions with optical-dev.oliver.solution
across all config, env, docs, and source files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix missing await on FocusGroup.get_messages() (N-L1)
- Replace time.sleep with asyncio.sleep in key_theme_service and focus_group_service (N-P10)
- Replace flask import with quart in focus_groups.py (N-S3)
- Add logger.error before all 500 returns in focus_groups.py (N-P6)
- Add logging to silent except blocks across routes (N-M10, N-M11)
- Add @rate_limit to 6 remaining AI endpoints (N-H4)
- Add --confirm flag to populate scripts before delete_many (S-H2)
- Remove hardcoded Azure ID fallbacks from msal_service.py and msalConfig.ts (A-M2, F-H4)
- Centralize make_serializable() in utils.py, remove duplicates from 3 route files (N-P7)
- Replace all datetime.utcnow() with datetime.now(timezone.utc) across entire backend (M-L2)
- AuthContext.tsx: only mark token validated on 200 success, not on non-401 errors (F-H2)
- Rename authType → auth_type in auth.py (N-S4)
- Add security_report.md and security_report.pdf with full 92-finding status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create comprehensive technical architecture document (PDF) with 11
chapters covering system architecture, frontend/backend design, data
model, auth, WebSocket communication, LLM integration, and core
feature flows. Includes 11 Mermaid diagrams rendered as PNGs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrites the outdated README to accurately reflect the current state of the
project: corrects framework references (Quart/Hypercorn instead of Flask/Gunicorn),
documents all major features (autonomous AI conversations, multi-model LLM support,
WebSocket communication, Microsoft SSO, theme extraction), updates the project
structure and tech stack, adds architecture overview, environment configuration
table, and deployment instructions using deploy.sh/systemd.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Focus groups created before the gpt-5.2 rename have llm_model='gpt-5'
stored in MongoDB. Without an alias, the backend falls through to the
Gemini provider and fails with an aiohttp AssertionError.
Adds MODEL_ALIASES mapping and _resolve_model() helper so gpt-5 is
transparently resolved to gpt-5.2. Also updates all llm_model checks
to accept both values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap model ID from gpt-5 to gpt-5.2 across all backend services,
frontend components, and documentation. Change default reasoning
effort from medium to low for faster responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stage 2 (detailed persona generation) was ignoring the audience brief and
research objective, causing the LLM to guess research context from demographics
alone. Now passes both values through to generate_persona() in all three
endpoints (generate-personas-full, complete-and-save-persona, complete-persona)
and auto-generates prompt customization via customize_persona_prompt() when
they are provided.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Version 1.52.0 has a known bug where aiohttp connector is None.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
logger is not defined at module level where get_gemini_client() lives.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This will help identify where exactly the AssertionError is occurring
in the google-genai SDK and what version is installed on the server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Log full exception details: type, module, str, repr, args, and __dict__
to diagnose why Gemini errors are producing empty messages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Catch genai_errors.APIError specifically and extract e.code and e.message
attributes for proper error logging. The generic str(e) was returning empty
strings for Google API errors, making debugging impossible.
- Import google.genai.errors for specific exception handling
- Add APIError catch before generic Exception in generate_content()
- Add APIError catch before generic Exception in generate_contextual_response()
- Properly categorize errors by HTTP code for retry logic (429/500+ retryable)
- Fix time.sleep to await asyncio.sleep in contextual response handler
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Skip WebSocket connection attempt when no auth token is available.
Previously, the WebSocketProvider would try to connect immediately
on mount (even on login page) with an empty token, causing
"Invalid token format" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous event loop tracking approach still caused issues - when replacing
a cached client, its garbage collection triggers aclose() which tries to close
the aiohttp session on the wrong event loop.
Simplest fix: create a fresh client for each call. The overhead is minimal
compared to the actual LLM API call, and this completely avoids all event
loop mismatch issues in ASGI environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous lazy initialization fix wasn't sufficient - the genai.Client
internally caches async structures bound to the event loop at creation time.
With ASGI servers like Hypercorn, subsequent requests may come on different
event loop contexts, causing "Future attached to a different loop" errors.
Now tracks which event loop the client was created on and recreates it if
the loop has changed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
These files are already in .gitignore but were committed previously.
Removing them from tracking to prevent future conflicts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>