sillsdev / sillsdev/languageforge-lexbox

LexBox web Mixpanel analytics

Open
#2,398 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

πŸ“¦ Lexbox
Dominant language
C#
Stars
9
Forks
8
Avg merge
2d 13h
Merged PRs (30d)
49

Description

LexBox Web Mixpanel Analytics Plan

Related: #2383 (FwLite analytics β€” team decisions and privacy patterns apply here too)

Context

  • Scope: LexBox web app (frontend/) plus backend Send/Receive (SyncReverseProxy/). Not FwLite (frontend/viewer/) or FwHeadless service-account sync jobs.
  • Issue #2383 is FwLite-focused, but its team decisions transfer well: opt-out in production, hard-off in dev, Simplified ID Merge identity, no linguistic/PII payloads, phased event rollout.
  • Today: LexBox has OpenTelemetry for operational tracing (frontend/src/lib/otel/) β€” user id is already attached to spans as app.user.id. Product analytics is a separate concern; OTEL stays as-is.
  • User identity is already available: JWT β†’ LexAuthUser.id (stable Guid). This should be $user_id in Mixpanel, same as the FwLite spec recommends.
flowchart LR
    subgraph client [LexBox SvelteKit Client]
        Layout["+layout.svelte"]
        Hooks["hooks.client.ts"]
        Pages["Routes and mutations"]
        ClientAnalytics["$lib/analytics"]
    end
    subgraph server [LexBox Backend]
        Proxy["SyncReverseProxy Forward"]
        ProxyEvents["ProxyEventsService"]
        ServerAnalytics["IAnalyticsService"]
    end
    MP[Mixpanel]

    Layout --> ClientAnalytics
    Hooks --> ClientAnalytics
    Pages --> ClientAnalytics
    ClientAnalytics -->|"browser SDK or API proxy"| MP
    Proxy --> ProxyEvents
    ProxyEvents --> ServerAnalytics
    ServerAnalytics -->|"mixpanel-csharp"| MP

Architecture (high level)

Decision Recommendation
SDK (web) Mixpanel browser SDK (mixpanel-browser) for SvelteKit UI events
SDK (server) mixpanel-csharp (same library as FLEx / #2383) for Send/Receive and any future server-side LexBox events
Token handling Server-only token β€” never in client bundles. Web events via API proxy; S&R events emitted directly from SyncReverseProxy
Central modules $lib/analytics/ (client) + shared IAnalyticsService in LexCore or LexBoxApi (server)
Instrumentation style Explicit track() at funnel boundaries (login, project create) + sanitized route tracking for page views; S&R at completion hooks in ProxyEventsService
Mixpanel project Separate project from FwLite, but use the same $user_id (Lexbox User Guid) so cross-product journeys can be joined later if desired

Init hook points: hooks.client.ts (bootstrap) and authenticated +layout.svelte (re-identify() when JWT user loads). Auth events in login/+page.svelte, register/+page.svelte, logout route.


Consent and guardrails

Mirror #2383 decisions, adapted for web:

Environment Tracking
Production (lexbox.org etc.) On by default, user can opt out
Dev (pnpm run dev, local stack) Hard disabled β€” import.meta.env.DEV
CI / Playwright (frontend/tests/) Hard disabled β€” env flag or hostname check
Sandbox routes (routes/(unauthenticated)/sandbox/) Never track

User control: Add a "Send usage statistics" toggle under Account settings (user/+page.svelte), persisted server-side (new user preference on LexAuthUser or a lightweight API). Call Mixpanel opt_out_tracking() when disabled.

Disclosure: Short notice on first visit (or in About/Privacy) β€” anonymous device ID before login, stable user Guid after login, link to what is never collected.


Identity (Simplified ID Merge)

Same model as docs/issues/2383-fwlite-analytics-spec.md:

ID When Source
$device_id Always (anonymous + authenticated) Generated on first visit, localStorage
$user_id After login/register success LexAuthUser.id from JWT

Lifecycle:

  • Pre-login (login, register, forgot password, accept invitation): events with $device_id only
  • Login/register success: identify(user.id) β€” next events include both IDs (triggers merge)
  • App load while authenticated: re-identify() from layout load data
  • Logout: reset() β€” new $device_id, clear $user_id (shared-device safety)

Never send: email, username, display name, tokens. $user_id Guid only.


Super properties (every event)

Property Source
$device_id / $user_id Identity layer
app_version APP_VERSION
product lexbox_web (distinguishes from future FwLite events if projects merge)
ui_language Active locale
is_admin user.isAdmin
feature_flags Enabled flags only (enum names, not custom data)

Contextual (when applicable, never identifying):

  • project_id: Lexbox Project.Id Guid β€” allowed for S&R events; still omit human-readable project_code
  • project_type: FwData | Crdt | etc. (enum from GraphQL)
  • project_role: Manager | Editor | Observer
  • org_role: enum
  • auth_method: password | google | bearer (S&R JWT)
  • view_mode: grid | table (home project list)
  • sr_protocol: hgweb | resumable (S&R only)

Send/Receive tracking (backend)

LexBox exposes Mercurial Send/Receive to FLEx and other FieldWorks clients via SyncReverseProxy. This is entirely server-side β€” no browser involved β€” but is a core collaboration signal worth tracking in the same Mixpanel project.

Where to hook

S&R traffic is proxied in ProxyKernel.Forward(), which already tags OTEL spans with app.project_code and app.send_receive. Completion is already detected in ProxyEventsService:

Protocol Completion signal Meaning
hgweb cmd=unbundle request succeeds Client pushed a changeset bundle to the server
resumable Last chunk of pushBundleChunk (offset + chunksize >= bundlesize, status 200) Client finished uploading a resumable bundle

These are the right places to fire send_receive_completed β€” they align with existing QueueProjectMetadataUpdate calls (metadata refresh after real data landed).

Identity on S&R requests: BasicAuthHandler resolves the caller to LexAuthUser (password, or bearer JWT with SendAndReceive scope from IntegrationController). $user_id = LexAuthUser.Id.

Project ID: Resolve projectCode β†’ Project.Id Guid via ProjectService.LookupProjectId (cache lookups β€” S&R is hot path).

S&R events (Phase 1)
Event When Key properties
send_receive_completed ProxyEventsService completion hooks above $user_id, project_id, sr_protocol: hgweb | resumable
send_receive_failed Proxy auth failure, ProjectLockedException, or non-2xx on completion request $user_id (if known), project_id (if resolved), failure_reason: unauthorized | project_locked | proxy_error

Defer for later: per-request hg command tracking, pull/receive direction inference, bytes transferred, commit hashes, abandoned-transaction details (OTEL already tags app.abandoned_transaction_detected for ops).

Out of scope for S&R analytics: FwHeadless automated S&R during CRDT sync jobs (SyncHostedService) β€” service-account initiated, not end-user collaboration. Track only user-authenticated proxy traffic unless product later wants infra metrics separately.

S&R consent

S&R events must respect the same per-user opt-out preference as web events. Check User.SendUsageStatistics (or equivalent) before emitting; skip tracking if opted out. No anonymous S&R β€” callers are always authenticated.

S&R vs FwLite sync (#2383)

Issue #2383 defers FwLite CRDT sync tracking. LexBox Mercurial Send/Receive is a distinct, user-visible collaboration action and is explicitly in scope here.


What to track β€” event catalog by phase

Phase 1 β€” MVP funnels (ship first)

Answers: Do people sign up, create/join projects, and come back?

Event Trigger Key properties
session_started Client bootstrap entry_route (sanitized route id, no params)
page_viewed SvelteKit navigation route_id (e.g. /(authenticated)/project/[project_code]), not project_code value
login_started Login form submit or Google button auth_method
login_succeeded / login_failed login() result failure_reason: bad_credentials | locked | unknown
register_started / register_succeeded / register_failed Register / accept-invitation flows failure_reason enums (turnstile, account exists, etc.)
logout Logout route β€”
project_create_started project/create submit β€”
project_create_completed Create mutation result outcome: created | join_request_sent | draft_saved | failed
project_opened Project detail page load project_type, user_role, is_draft, confidentiality β€” not code/name
email_verification_completed Email verification callback β€”
send_receive_completed Backend: ProxyEventsService hgweb unbundle or resumable push complete $user_id, project_id, sr_protocol

Activation milestone: first_project_created β€” fire once per user when first project_create_completed with outcome: created.

Collaboration milestone: first_send_receive_completed β€” fire once per user on first successful S&R.

Phase 2 β€” Engagement and collaboration

Answers: Are teams forming? Are project admins using key features?

Event Trigger
project_list_viewed Home +page.svelte
project_member_invited Add member modal success
project_member_added Bulk add / direct add success
ask_to_join_project Join request from create flow
project_left / project_deleted Leave/delete confirmed
org_created org/create
org_member_invited Org member invite
user_settings_updated Account settings save (which fields changed: locale, name β€” not values)
open_in_flex_clicked OpenInFlexButton
send_receive_url_copied Copy Send/Receive URL on project page (UI intent β€” complements backend send_receive_completed)
fwlite_beta_requested wheresMyProject
oauth_authorize_completed OAuth consent for third-party apps (authorize)

Retention signals: segment on project_opened, project_create_completed, project_member_invited, send_receive_completed (weekly, by $user_id).

Phase 3 β€” Feature adoption (lower priority)
Event Question it answers
project_backup_downloaded Are admins backing up before destructive ops?
project_reset_completed Reset flow completion rate
crdt_sync_ui_opened Interest in FwLite sync UI (not sync outcomes)
admin_action Sparse admin-panel actions (user created, project approved)
invitation_link_opened Invitation funnel drop-off
Explicitly out of scope (never track)

Same privacy bar as #2383, plus web-specific items:

  • Project names and codes (human-readable identifiers)
  • project_id and $user_id are allowed where needed for product analytics (S&R)
  • Member emails, usernames, display names
  • Entry/linguistic content (LexBox web does not edit entries, but guard anyway)
  • hg commit hashes, repo URLs, bundle contents, per-hg-command telemetry
  • FwLite CRDT sync events (deferred per #2383; distinct from LexBox Mercurial S&R)
  • GraphQL query variables, form field values
  • Full URLs with query params containing identifiers

Funnel definitions

flowchart TD
    Visit[session_started] --> Auth{authenticated?}
    Auth -->|no| Register[register_succeeded]
    Auth -->|yes| Home[project_list_viewed]
    Register --> Verify[email_verification_completed]
    Verify --> Create[project_create_completed]
    Home --> Create
    Create --> Collaborate[project_member_invited]
    Collaborate --> Retained[project_opened on D7]
Funnel Steps Product question
Signup activation register_succeeded β†’ email_verification_completed β†’ first_project_created Is self-serve onboarding working?
Invitation activation register_succeeded (invitation) β†’ project_opened Do invited users land in a project?
Project creation project_create_started β†’ project_create_completed Where do create flows fail?
Collaboration project_create_completed β†’ project_member_invited Do creators invite teammates?
FLEx collaboration open_in_flex_clicked or send_receive_url_copied β†’ send_receive_completed Are web users actually syncing with FLEx?
Retention first_project_created β†’ project_opened on D7/D30 Do admins return?
S&R retention first_send_receive_completed β†’ send_receive_completed on D7/D30 Are collaborators syncing regularly?

Implementation sequencing

  1. Mixpanel project setup β€” enable Simplified ID Merge; create dev/staging/prod projects or use env-based tokens
  2. Server analytics service β€” mixpanel-csharp wrapper with Simplified ID Merge ($user_id only for server events; no $device_id needed); register in LexBoxApi + SyncReverseProxy
  3. Analytics module + consent β€” $lib/analytics, env gating, user opt-out preference (API + UI); server checks same preference
  4. Identity wiring β€” login, register, logout, authenticated layout re-identify (client)
  5. Phase 1 events β€” session, auth, project create/open, activation milestones, S&R completion in ProxyEventsService
  6. Dashboards β€” signup activation, project creation, S&R adoption, D7 retention boards in Mixpanel
  7. Privacy docs β€” user-facing disclosure + internal event dictionary (reuse structure from docs/issues/2383-fwlite-analytics-spec.md)
  8. Phase 2/3 β€” iterate based on which product questions remain unanswered

Key files to touch (when implementing)

Area Files
Analytics core (client) New frontend/src/lib/analytics/
Analytics core (server) New LexCore or LexBoxApi analytics service; wire into SyncReverseProxy DI
Send/Receive ProxyEventsService.cs, ProxyKernel.cs
Bootstrap hooks.client.ts, root +layout.svelte
Auth lib/user.ts, login/register/logout routes
Projects project/create/+page.svelte, project/[project_code]/+page.svelte
Settings user/+page.svelte + backend preference endpoint
Config frontend/.env pattern, deployment secrets, Mixpanel token in K8s secrets

Open decisions (resolve before implementation)

  1. Server proxy vs direct SDK β€” proxy is safer for token; direct SDK is simpler. Recommend proxy unless latency is a concern.
  2. Opt-out storage β€” new User.SendUsageStatistics DB field vs client-only cookie (server field survives devices and matches FLEx pattern).
  3. Shared vs separate Mixpanel project from FwLite β€” separate projects with shared $user_id is the pragmatic default; merge later if cross-product dashboards are needed.
  4. S&R failure instrumentation depth β€” track failures only at ProxyKernel.Forward boundary initially, or also per-protocol in ProxyEventsService?

Implementation checklist

  • Extend or wrap mixpanel-csharp for Simplified ID Merge on server; add browser SDK + optional API proxy for web
  • Add User.SendUsageStatistics (or equivalent) + account settings UI + dev-build hard disable
  • Wire identity: login, register, logout, authenticated layout re-identify
  • Instrument Phase 1 web events + send_receive_completed / send_receive_failed in ProxyEventsService
  • Draft user-facing privacy disclosure + internal event dictionary
  • Create Mixpanel boards: signup activation, project creation, S&R adoption, D7 retention
  • Prioritize Phase 2/3 events after Phase 1 ships

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up β€” it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the related #2383 decisions, then inspect frontend/src/hooks.client.ts, the authenticated +layout.svelte, and SyncReverseProxy/Services/ProxyEventsService.cs. Review the listed authentication, project, consent, and Send/Receive hook points before choosing an implementation breakdown. Done means the agreed Mixpanel phases, opt-out rules, identity handling, and listed server and web events are implemented without sending prohibited data.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, typescript
Domain
analytics, full-stack
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.