Commit graph

89 commits

Author SHA1 Message Date
Vadym Samoilenko
83924901e6 fix: hero banner no bg, bigger image clamp(200,28vw,400px), full width 2026-05-23 21:48:32 +01:00
Vadym Samoilenko
5c2ba7503e feat: full-width hero banner with transparent logo on dark bg 2026-05-23 21:42:59 +01:00
Vadym Samoilenko
37f3cf1704 fix: remove dark bg from logo PNG (Pillow alpha), drop mix-blend-mode 2026-05-23 21:32:56 +01:00
Vadym Samoilenko
2d6c715c5b feat: header mark-only icon, logo banner in hero via screen blend, favicon update
- Header nav: SVG C-mark only (no PNG box, no dark bg)
- Hero: full logo PNG banner below nav with mix-blend-mode:screen (removes dark bg)
- favicon.svg: updated C-mark colours to match rebrand

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:31:12 +01:00
Vadym Samoilenko
aa2c66dbe9 fix: logo height sm 28→70px (×2.5) 2026-05-23 21:27:36 +01:00
Vadym Samoilenko
b7065cfe78 fix: correct emails, AImpress LTD links, cookie banner, wavy bg, credits math
- All hello@cohorta.ai-impress.comhello@ai-impress.com
- All AImpress LTD instances now link to https://ai-impress.com/
- Cookie consent banner (bottom sheet, accept/decline, localStorage)
- Hero background: grid → wavy SVG lines
- Fix credits math: 50cr = 5 personas × 2cr + 1 session × 40cr (not 25 personas)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:20:43 +01:00
Vadym Samoilenko
868df66f76 feat: replace SVG logo with PNG lockup (Cohorta copy.png) in horizontal variant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:08:42 +01:00
Vadym Samoilenko
93bc2f953d fix: smaller header logo (sm size) + remove stale .ico favicon fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:06:31 +01:00
Vadym Samoilenko
4c307bf00d feat: increase trial credits from 10 to 50 on signup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:05:13 +01:00
Vadym Samoilenko
a9a5fff659 feat: full visual rebrand + landing redesign + auth page refresh + email fix
- Landing: extract 513-line monolith into 12 focused section components
  (Hero, StatsBand, FeatureGrid, HowItWorks, LivePreview, Comparison,
  UseCases, Testimonials, Pricing, FAQ, FinalCTA, TrustBar)
- Auth pages: replace flat orange panel with animated live mock
  (real persona SVGs, typewriter messages, theme bars); Login label
  fixed to "Email or username"; Register wires ?plan= badge
- Brand: new Logo SVG (C-arc + 3 figures + wordmark/tagline), expanded
  palette tokens, fluid display type scale, framer-motion shared variants
- Header: scroll progress bar, removed non-functional language pill
- Footer: fixed all dead links, legal stubs, new logo
- Legal: /about /privacy /terms /cookies /gdpr real pages added
- Email: FROM_EMAIL default fixed to noreply@ai-impress.com (verified
  apex domain), HTML template rewritten to match new brand
- Tooling: Playwright screenshot script for visual self-check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:02:03 +01:00
Vadym Samoilenko
32d21d1260 feat: new logo mark, 8 persona avatars, remove unauthenticated billing API call
- Logo.tsx: C arc + 3 people silhouettes SVG mark matching brand design
- favicon.svg: updated to match new logo mark
- public/avatars/: 8 diverse persona SVGs (skin tones, hair styles, ages)
- Index.tsx: remove billingApi.getBalance() call on public landing page (was causing 401 console errors for anonymous visitors; pricing uses hardcoded defaults)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:15:33 +01:00
Vadym Samoilenko
b2a063b55f fix: move auth pages out of PublicLayout; fix nav links (remove Blog, anchor scroll to sections) 2026-05-23 19:57:09 +01:00
Vadym Samoilenko
9d2f1f2c7d feat: complete AIMPRESS visual rebrand — warm palette, new landing, real dashboard
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
- Replace cyan/violet design tokens with warm dark slate + orange (#E89B3C) palette
- Add Space Grotesk display font; new utilities: .outline-display, .orange-band, .corner-card, .persona-orb
- New brand components: Logo (hexagonal SVG), Header (pill nav + glass blur), Footer (4-col), PublicLayout, AppLayout, UserDropdown
- Rewrite Index.tsx as full sales funnel: Hero → Stats → Orange band → How it works → Pricing (API) → FAQ → Final CTA
- Rewrite Dashboard.tsx with real API data: credits balance, MTD spend, personas count, focus groups count, active tasks, recent transactions
- Rewrite auth pages (Login, Register, VerifyEmail, NotFound, Billing) with two-column orange-panel layout
- Replace hardcoded mock numbers in Dashboard with billingApi / personasApi / focusGroupsApi / usageApi calls
- Delete legacy components: Navigation.tsx, Hero.tsx, FeatureCard.tsx
- Add nested layout routing in App.tsx: PublicLayout for guests, AppLayout for protected routes
- Color sweep inner pages: replace all purple-500/600 with primary token
- Purge all semblance / Oliver / optical-dev references; rename semblance_app_documentation.md → cohorta_app_documentation.md; update backend scripts to cohorta_db

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 19:44:02 +01:00
Vadym Samoilenko
e01569c412 feat: commit all app changes — billing API, new auth, design overhaul
All checks were successful
Deploy to Production / deploy (push) Successful in 2m23s
Includes frontend redesign (Navigation, billingApi), backend updates
(auth routes, admin routes, LLM service refactor), MSAL removal,
and dependency updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:04:43 +01:00
Vadym Samoilenko
5491d2d73d Rebrand to Cohorta + full UI redesign + registration with email verification
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
- Complete dark-theme redesign inspired by ai-impress.com (navy + cyan + violet palette)
- New Syne display font + gradient logo mark + SVG favicon
- New Navigation: glass-morphism, gradient logo, Get Started CTA
- New Hero: animated glow orbs, mock focus-group chat UI, stats row
- New landing: Features grid, How-It-Works steps, CTA banner
- New Footer: AImpress LTD branding, © AImpress LTD. All rights reserved.
- New Login page: dark card, password visibility toggle, link to Register
- New Register page: full form, benefits row, 50 free credits pitch
- New VerifyEmail page: token verification flow with auto-redirect
- Backend: email_service.py using Resend API for verification emails
- Backend: /api/auth/register, /verify-email, /resend-verification endpoints
- User model: email_verified, email_verify_token, email_verify_expires fields
- Gitea Actions CI/CD: auto-deploy to aimpress server on push to main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:40:08 +01:00
Vadym Samoilenko
7b6a7c7347 Fix admin filters: ISO Z parsing crash + All time period returning month data
Two bugs caused filters to show 0 and period selector to have no effect:

1. Python < 3.11 can't parse JS toISOString() Z suffix — every request with a
   period filter threw ValueError → 500 → frontend received no data. Fixed with
   _parse_iso() helper that replaces Z with +00:00 before fromisoformat().

2. 'All time' sends no from/to params, but backend defaulted to _month_start()
   instead of omitting the ts filter. Fixed with _period_match() helper that
   returns {} (no filter) when both from and to are absent.

Also: stale _user_mtd_cost reference in get_user route replaced with
_user_period_cost(user_id, None, None); adminApi types updated with from/to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 19:17:57 +01:00
Vadym Samoilenko
57508e8e55 Add period selector to all cost-bearing admin tabs
- New usePeriod hook (day/week/month/all/custom presets) with from/to ISO string outputs
- New PeriodSelector component (button group + custom date inputs)
- UsersTab, UsageTab, FocusGroupsTab all wired up with period state
- Backend /admin/users and /admin/focus-groups now accept from/to query params
- MTD Cost column header now reflects selected period label (e.g. "Cost (MTD)")
- Logout clears local state only (no account sign-out)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 19:03:16 +01:00
Vadym Samoilenko
39ad2f00b5 Add back button to Admin page 2026-04-24 18:48:22 +01:00
Vadym Samoilenko
bc4138f332 Final pieces: decorators on LLM routes, usage self-service, billing page, WS events
Backend:
- @active_required + @with_user_context applied to all LLM-invoking routes
  in personas.py, focus_group_ai.py, ai_personas.py
- backend/app/routes/usage.py: GET /api/usage/me (MTD summary by feature),
  GET /api/usage/focus-groups/<id> (owner or admin)
- Registered usage_bp in app/__init__.py
- llm_service._record_usage now emits usage_update WS event to focus group room

Frontend:
- useMyUsage + useFocusGroupUsage hooks
- MyUsage.tsx: personal billing dashboard (cost cards + per-feature table)
- /billing route (ProtectedRoute) + Billing nav link
- FocusGroupSession: quota_warning amber banner with Progress bar,
  quota_exceeded + quota_warning WS events wired via websocketServiceNew

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:43:13 +01:00
Vadym Samoilenko
915c81b8f1 Complete phases D–G: quota enforcement, token invalidation, admin writes, backfill
Backend:
- token_version in JWT (bump_token_version, get_token_version on User model);
  jwt_required checks tv claim → 401 on mismatch; login routes embed version
- Quota pre-flight in all 3 LLM public methods (QuotaExceededError bubbles up)
- AI runner catches QuotaExceededError → sets status paused_quota + emits WS event
- Admin routes: POST /users (create), POST /users/<id>/reset-password,
  POST /pricing, GET /focus-groups with aggregated cost; PUT /users/<id>
  now bumps token_version on disable or role change
- backfill_usage.py: idempotent estimated-event generator for historical data,
  tiktoken for GPT models, char/3.8 for Gemini, --dry-run flag

Frontend:
- 402 interceptor dispatches quota_exceeded CustomEvent
- adminApi: createUser, resetPassword, createPricing, listFocusGroups
- UsersTab: New User dialog + Reset Password in edit dialog
- PricingTab: New Price dialog (model, provider, input/output/cached prices)
- FocusGroupsTab: focus groups table sorted by total cost
- Admin.tsx: 4th tab (Focus Groups)
- FocusGroupSession: admin-only cost badge + dismissable quota exceeded banner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:34:48 +01:00
Vadym Samoilenko
015e6cc5cc Add Phase D admin panel: user management + usage analytics
Backend: /api/admin/* blueprint with user CRUD (list, get, update,
disable/enable), usage summary aggregation (group by user/model/feature/
day/focus_group), usage event drill-down, and pricing list. Fixed
admin_required decorator (async-safe). Added find_all/count/update
helpers to User model.

Frontend: /admin page (AdminRoute guard, 3 tabs) — Users table with
search/filter/edit dialog, Usage tab with KPI cards + bar chart +
events table, Pricing tab showing active model rows with tier details.
Admin nav link visible only to admin role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:26:05 +01:00
Vadym Samoilenko
0bf6043fad Fix: task result not stored in useTaskPolling, causing false 'no personas' error
When a task completed, the result payload (personas_created, errors_count, etc.)
was discarded instead of being saved to state. AIRecruiter always read
generationState.result as undefined → count = 0 → showed error even when
the backend had successfully created all personas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:06:22 +01:00
Vadym Samoilenko
2e85fc1acc Fix root cause: naive vs aware datetime crash + stuck AI mode indicator
The autonomous loop was crashing on every decision with:
  TypeError: can't subtract offset-naive and offset-aware datetimes
because MongoDB stores created_at without timezone info but the code
compared it against datetime.now(timezone.utc).

- conversation_context_service: make created_at timezone-aware before
  subtraction (replace tzinfo=utc when naive)
- DiscussionPanel: fix sync effect — when server reports AI mode is
  inactive, always clear localAiModeActive regardless of its value,
  so the "AI is generating..." spinner doesn't get stuck when the
  backend fails/stops before the frontend has confirmed AI mode started

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:30:04 +00:00
Vadym Samoilenko
283b31e786 Fix AI mode: race condition, split-brain UI, and stuck local state
- Backend: set status to ai_mode in the route handler before submitting
  to AI runner, eliminating the race condition where frontend's immediate
  status poll read the old status
- Frontend: replace all raw isAiModeActive prop usages with
  effectiveAiModeActive in DiscussionPanel (13 locations) so ReasoningPanel,
  status text, loading indicator, and manual/AI controls all reflect the
  correct state instantly on Start AI Mode click
- Frontend: add useEffect to sync localAiModeActive back to null once
  the parent prop catches up, preventing permanent override after natural
  session end
- These fixes also unblock the 3-second AI message polling which was
  never activating due to isAiModeActive staying false

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:02:15 +00:00
Vadym Samoilenko
b4978989a5 Fix AI autonomous mode: cross-loop WebSocket emit + polling fallback
The AI Runner runs on a dedicated background thread with its own asyncio
event loop. When it emitted WebSocket events via sio.emit(), the call
happened on the wrong loop (AI Runner's vs ASGI/Quart's), causing silent
failures — messages were saved to MongoDB but never reached the frontend.

Additionally, the frontend HTTP polling fallback was never enabled when
WebSocket appeared connected, leaving no way to discover missed messages.

- websocket_manager_async.py: store ASGI main loop reference; detect
  cross-loop calls in emit_to_focus_group and use run_coroutine_threadsafe
  to schedule emits on the correct loop
- __init__.py: register the ASGI event loop with the WebSocket manager
  in before_serving hook
- FocusGroupSession.tsx: always poll fetchMessages every 3s during AI mode
  as a reliability fallback regardless of WebSocket status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:22:24 +00:00
Vadym Samoilenko
4b47b334d7 Fix data isolation + conversation/decision 500 errors
Data isolation:
- GET /tasks/<id>: verify requesting user owns the task (403 if not)
- DELETE /tasks/<id>: same ownership check
- GET /tasks/status: add @jwt_required()
- GET /personas/<id>: add ownership check (403 if created_by != user)
- GET /focus-groups/<id>: add ownership check
- GET /focus-groups/<id>/messages: add ownership check
- POST/DELETE /focus-groups/<id>/participants: add ownership check

Fix conversation/decision 500:
- Convert POST /conversation/decision to async 202+background (was synchronous LLM → timed out / LLM errors → 500)
- Frontend polls waitForTaskResult for decision result before calling generateResponseAsync
- GET /conversation/insights: return empty insights (200) on LLM error instead of 500

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 17:02:10 +00:00
Vadym Samoilenko
1b387daacf Migrate task result delivery from WebSocket to HTTP polling
Backend:
- task_manager.py: add result/error/completed_at storage, TTL sweeper (5min), store_task_result() helper
- tasks.py: add GET /<task_id> endpoint returning stored result; cancel route stores 'cancelled' status
- __init__.py: start TTL sweeper on app startup
- All 8 bg functions: store result before emitting lightweight WS hint (no payload data)

Frontend:
- src/lib/taskPolling.ts: waitForTaskResult() — polls GET /tasks/{id} every 2s, WS hint triggers immediate poll, 5min timeout
- src/hooks/useTaskPolling.ts: drop-in replacement for useCancellableGeneration using polling
- Migrate 6 Promise-based WS listeners → waitForTaskResult() in DiscussionPanel, FocusGroupSession (×2), PersonaProfile, PersonaModificationModal, useDiscussionGuideGeneration
- Migrate 3 hook-based consumers → useTaskPolling in AIRecruiter, SyntheticUsers, BulkExportProgressModal

Fixes WS Promise leak: polling survives disconnects, background tabs, page reloads.
WS events retained as zero-payload hints for near-zero latency when connected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:46:58 +00:00
Vadym Samoilenko
c7034634e3 Fix all async LLM routes: bypass GCP 30s load balancer timeout
Convert 6 synchronous LLM routes to async 202+WebSocket pattern:
- generate-response (focus_group_ai): persona chat response
- generate-key-themes (focus_group_ai): discussion analysis
- modify-with-ai (personas): AI persona modification
- export-profile (personas): markdown profile export
- describe-asset (focus_groups): image AI description

Each route now returns 202 + task_id immediately, runs LLM in
asyncio background task, delivers result via WebSocket task_completed
event. Frontend listeners updated to wait for ws:task_completed
instead of HTTP response body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:56:54 +00:00
Vadym Samoilenko
d8a5d6643f Fix discussion guide 504: async flow + WebSocket delivery
- 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>
2026-03-23 15:32:08 +00:00
Vadym Samoilenko
f359157949 Fix focus group create: 500 on update + 400 on autosave
- 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>
2026-03-23 15:20:40 +00:00
Vadym Samoilenko
770bdee829 Remove console.log debug calls from frontend browser console
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>
2026-03-23 14:30:56 +00:00
Vadym Samoilenko
f60d86e8cb Fix task_completed WebSocket payload too large
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>
2026-03-23 14:11:14 +00:00
Vadym Samoilenko
aa4090888d Fix persona generation 504: async flow + remove debug logging
- 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>
2026-03-23 14:00:07 +00:00
Vadym Samoilenko
2c6505f472 Switch to loginRedirect — popup blocked by server COOP header
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>
2026-03-20 14:44:19 +00:00
Vadym Samoilenko
3943be1c51 Fix MSAL popup loading full app — skip React render when in popup context
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>
2026-03-20 14:35:25 +00:00
Vadym Samoilenko
361ef7bb6a Revert to loginPopup — COOP warning was non-blocking, popup worked before
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>
2026-03-20 14:18:12 +00:00
Vadym Samoilenko
c00116eea4 Remove manual PKCE params from loginRequest — MSAL handles PKCE internally
Passing code_challenge_method without code_challenge causes AADSTS90014.
MSAL generates and sends PKCE parameters automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:05:08 +00:00
Vadym Samoilenko
7a7d3a90ac Switch MSAL from loginPopup to loginRedirect — fix COOP blocking
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>
2026-03-20 14:03:16 +00:00
Vadym Samoilenko
7f0df54de3 Fix domain typo: oliver.solution → oliver.solutions across all files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:40:00 +00:00
Vadym Samoilenko
bb4dca0fe8 Update production URL to optical-dev.oliver.solution
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>
2026-03-20 12:55:45 +00:00
Vadym Samoilenko
3e1865edbd Apply Jintech security audit remediation (sprint 3) — 87/92 findings fixed
- 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>
2026-03-20 12:51:18 +00:00
michael
b0445de18b Update GPT-5 to GPT-5.2 and lower default reasoning effort to low
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>
2026-02-11 11:24:01 -06:00
michael
ca93d24ac5 Fix WebSocket auth error on initial page load
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>
2026-01-05 12:15:04 -06:00
Michael Clervi
893b537b67 changed permissions 2025-12-19 19:26:16 +00:00
michael
947acf780b Fix gitignore to allow src/lib and add missing taskCancellation utility
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 11:38:15 -06:00
michael
6b7f8763c0 Add environment variable to control local login availability
Add VITE_ENABLE_LOCAL_LOGIN env variable to conditionally show/hide
local username/password login on the login screen. When set to 'false'
(production), only Microsoft login is shown. When 'true' (development),
both options are available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 14:20:50 -06:00
michael
8a9c7c3cb6 Improve discussion guide discoverability in Focus Group session
Add prominent "Edit Discussion Guide" button to header and enhance accordion
styling with amber color scheme, "Click to Edit" badge, and visual feedback
to help users discover the inline editing capability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 09:57:32 -06:00
michael
240a1c09f5 Add field-level inline editing for discussion guide items
Users can now edit individual questions and prompts directly without
entering section edit mode. Edit buttons appear on hover for each item,
allowing inline editing of content, time limits, and probe questions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 09:43:12 -06:00
michael
28c7bbc055 Improve Focus Group workflow UX
- Rename "Copy Discussion Guide" button to "Import Discussion Guide from Other Project"
- Reorder tabs from Setup → Review & Edit → Participants to Setup → Participant Selection → Review & Edit
- Rename "Participants" tab to "Participant Selection" for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 09:30:57 -06:00
michael
08dbd5f5a2 Add word count strength indicator for form text fields
Implements a traffic light indicator beneath paragraph fields to guide users
on content adequacy. Shows red (<33%), yellow (33-99%), or green (≥100%) based
on word count progress toward minimum targets.

Fields updated:
- Research Brief: 150 words minimum
- Discussion Topics: 15 words minimum
- Audience Brief: 150 words minimum
- Research Objective: 150 words minimum

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 09:25:31 -06:00