clearCache() removes MSAL tokens locally; next visit auto-SSOs silently
if the user is still logged into Microsoft in the browser.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- registerTokenRefresher() lets AuthGate inject acquireTokenSilent into axios
- Request interceptor checks JWT exp, proactively refreshes if within 60s of expiry
- Response interceptor retries on 401 with a fresh token, reloads on double failure
- Previously the idToken was cached once in sessionStorage and never refreshed,
causing all requests to fail after 1 hour with "Token expired"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GEMINI_MODEL for AI commands: gemini-2.0-flash-exp → gemini-3-flash-preview
- Language/Country: handle plain 2-letter codes (EN→Language, UK→Country)
and "EN-UK" split format; previously only split format worked
- Handsontable black screen: add min-h-0 on flex-1 container so height:100%
resolves correctly inside the flexbox chain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BriefReviewPage: call loadSheet() after importDeliverables so store
is refreshed before navigation — fixes 0 items on Sheet page
- Google model: gemini-3.1-pro-preview → gemini-2.5-pro-preview-03-25
(old model name was invalid, caused API errors)
- docker-compose default: gemini-2.0-flash-exp → gemini-2.5-pro-preview-03-25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend Job interface used snake_case (file_name, file_size, progress_pct,
step_label, provider_updates, result_csv_url, created_at) but backend
returns camelCase (fileName, fileSize, progressPct, stepLabel,
providerUpdates, resultCsvUrl, createdAt) — causing all fields to be
undefined and showing 'NaN MB', broken progress bar, empty labels.
Updated types/index.ts Job, ProviderUpdate, JobSummary interfaces and
JobProgressCard.tsx to use the correct camelCase field names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs fixed:
1. api/jobs.ts: remove manual Content-Type header on FormData upload.
Setting it without the multipart boundary caused Quart to reject the
request body — the root cause of brief upload failures.
2. progress.py: include full job.to_dict() in job.progress / job.completed
/ job.failed WebSocket messages. Frontend checks msg.job to call
updateJob() — without it, job cards never updated in real-time.
3. AppShell: move useWebSocket() here from BriefUploadPage so the WS
connection persists across all pages, not just the upload page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- index.css: bump text-secondary #888→#b0b0b0, text-muted #555→#787878,
border slightly lighter for better readability on dark backgrounds
- TopBar: add '← Dashboard' back button on all non-dashboard pages
- Sidebar: make AC logo clickable to navigate home
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
loading starts as true in the store, so the previous condition
`!user && !loading` prevented fetchMe from ever being called after
the Azure AD redirect — causing a permanent Loading spinner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- msal_auth.py: replace verify_signature=False with real JWKS verification
using PyJWKClient; validates RS256 signature, aud=clientId, issuer v2.0
- App.tsx: split DEV bypass from empty-accounts case — in production,
accounts.length === 0 now correctly triggers loginRedirect instead of
calling fetchMe without a token
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>