Skip to content

chore: improve user experience and help determine future development direction#1132

Merged
ananaBMaster merged 3 commits intomainfrom
fix/lint
Mar 17, 2026
Merged

chore: improve user experience and help determine future development direction#1132
ananaBMaster merged 3 commits intomainfrom
fix/lint

Conversation

@ananaBMaster
Copy link
Copy Markdown
Collaborator

@ananaBMaster ananaBMaster commented Mar 17, 2026

Type of Changes

  • ✨ New feature (feat)
  • 🐛 Bug fix (fix)
  • 📝 Documentation change (docs)
  • 💄 UI/style change (style)
  • ♻️ Code refactoring (refactor)
  • ⚡ Performance improvement (perf)
  • ✅ Test related (test)
  • 🔧 Build or dependencies update (build)
  • 🔄 CI/CD related (ci)
  • 🌐 Internationalization (i18n)
  • 🧠 AI model related (ai)
  • 🔄 Revert a previous commit (revert)
  • 📦 Other changes that do not modify src or test files (chore)

Description

Related Issue

Closes #

How Has This Been Tested?

  • Added unit tests
  • Verified through manual testing

Screenshots

Checklist

  • I have tested these changes locally
  • I have updated the documentation accordingly if necessary
  • My code follows the code style of this project
  • My changes do not break existing functionality
  • If my code was generated by AI, I have proofread and improved it as necessary.

Additional Information


Summary by cubic

Add anonymous, opt‑out feature usage tracking powered by posthog-js to understand which features work well and where to improve. Adds a simple toggle in Options → Config → About and instruments key translation and TTS flows with minimal latency/outcome data.

  • New Features

    • Background analytics client using PostHog; persists analyticsEnabled and anonymous install ID; registers extension_version.
    • Tracks feature_used across: page translation (popup, floating button, context menu, shortcut, auto, touch), selection translation, input translation, subtitles (manual/auto), text‑to‑speech (incl. TTS settings preview), and Translation Hub.
    • Lightweight helpers (createFeatureUsageContext, trackFeatureUsed/trackFeatureAttempt) capture latency and outcome only.
    • Options → Config → About card with “Help improve user experience” switch; localized strings added.
    • Tests added for analytics utils and background handlers.
  • Migration

    • Zip builds require WXT_POSTHOG_API_KEY and WXT_POSTHOG_HOST (validated in build).
    • Analytics is enabled by default on Chromium‑based browsers and disabled by default on Firefox; users can opt out anytime in Settings.
    • Message payloads now include optional analyticsContext when toggling page translation.
    • Adds posthog-js; bumps wxt to 0.20.19.

Written for commit 0ddc69d. Summary will update on new commits.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 17, 2026

🦋 Changeset detected

Latest commit: 75d6390

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@read-frog/extension Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Mar 17, 2026
@github-actions github-actions bot added the chore label Mar 17, 2026
@dosubot dosubot bot added the app: browser extension Related to browser extension label Mar 17, 2026
@claude
Copy link
Copy Markdown

claude bot commented Mar 17, 2026

Code Review: PostHog Analytics Integration

Security

1. WXT_POSTHOG_TEST_UUID lacks production guard
src/entrypoints/background/analytics.ts reads import.meta.env.WXT_POSTHOG_TEST_UUID as a distinctIdOverride with no environment check. If accidentally set in production, all users share one distinct ID, corrupting analytics. Add a import.meta.env.MODE !== "production" guard.

2. PII filtering is well done
The filterAnalyticsCaptureResult allowlist approach stripping $current_url, $raw_user_agent, $timezone is a strong privacy-first design. person_profiles: "never" and persistence: "memory" are also good choices.

3. Build config ordering may be fragile
In wxt.config.ts, the check-api-key-env plugin blocks any WXT_*_API_KEY in production builds, while the zip-mode check requires WXT_POSTHOG_API_KEY. The ordering depends on .env.production loading timing. This should be documented or restructured to avoid confusion.


Privacy / Legal

4. Opt-out by default on Chrome/Edge — needs legal review
src/utils/constants/analytics.ts enables analytics by default on non-Firefox browsers. Users must navigate to Settings → About to find the toggle — there's no first-run consent dialog. Depending on jurisdiction (GDPR, ePrivacy), opt-in consent may be required. Recommend at minimum adding a first-run notification or consent prompt.


Code Quality

5. console.error vs logger inconsistency
src/utils/atoms/analytics.ts:26 uses console.error("Failed to persist analytics preference:", error) while the rest of the codebase uses logger.warn/logger.error. Should be consistent.

6. Unnecessary defensive check
src/utils/analytics.ts:53if (typeof logger.warn === "function") is unusual. If logger is properly typed and always available, this guard adds noise. If it's for test mocking, fix the test setup instead.

7. Minor race condition in getAnalyticsInstallId
src/entrypoints/background/analytics.ts:99-114 — Two simultaneous calls before persistence could generate different UUIDs. Non-critical since clientPromise ensures the PostHog bootstrap ID is stable, but could be tightened with a module-level promise cache.


Performance

8. PostHog SDK bundle size — Worth verifying
posthog-js/dist/module.no-external is the lighter variant, which is good. Since it only loads in the background script (not content scripts) and is lazily initialized, impact is limited. Still worth confirming via bundle analysis that it doesn't bloat the background script significantly (~40-60KB gzipped).

9. Fire-and-forget pattern is correct
All trackFeatureUsed calls use void, ensuring analytics never blocks user-facing operations. Lazy init via clientPromise means the SDK is never loaded if analytics is disabled.


Test Coverage Gaps

10. trackFeatureAttempt is untested
src/utils/analytics.ts:59-78 has branching logic (success path, failure path, re-throw) with no dedicated tests. Should cover:

  • Success: tracks success, returns result
  • Failure: tracks failure, re-throws original error
  • Analytics failure doesn't mask the original error

11. createFeatureUsageContext untested
src/utils/analytics.ts:11 — simple factory but should have basic coverage.

12. Jotai atom (src/utils/atoms/analytics.ts) untested
The write-back-on-failure rollback logic (lines 24-27) has no test coverage.

13. filterAnalyticsCaptureResult edge cases untested
Only tested with all properties present. Missing tests for when data.properties is undefined or partially populated.

14. No integration test for the full message flow
Content script → sendMessage → background handler → PostHog capture is only tested in isolation with mocks on both sides.


Summary

Area Assessment
Architecture ✅ Well-designed — DI via BackgroundAnalyticsRuntime, lazy init, allowlist filter
Privacy ⚠️ Good technical safeguards, but opt-out default needs legal review
Security ⚠️ Minor: TEST_UUID needs prod guard, build config ordering is fragile
Code quality ⚠️ Minor inconsistencies (console.error vs logger, defensive checks)
Test coverage ❌ Several untested functions and edge cases
Performance ✅ Lazy loading, fire-and-forget, background-only — good patterns

Overall this is a well-architected analytics integration. The main action items are: (1) add a consent mechanism for GDPR compliance, (2) guard TEST_UUID against production use, (3) fill the test coverage gaps for trackFeatureAttempt, the Jotai atom, and filter edge cases.

@dosubot
Copy link
Copy Markdown

dosubot bot commented Mar 17, 2026

Documentation Updates

2 document(s) were updated by changes in this PR:

Build and Development Environment Setup
View Changes
@@ -126,29 +126,44 @@
 
 This safeguard helps ensure that API keys are not inadvertently included in production bundles.
 
-#### Required Google Client ID for Extension Packaging
-When creating a production zip of the browser extension (using `pnpm zip`, `pnpm zip:edge`, `pnpm zip:firefox`, or `pnpm zip:all`), the build system requires the environment variable `WXT_GOOGLE_CLIENT_ID` to be set. If this variable is missing, the build will fail with an error message listing the missing variables.
+#### Required Environment Variables for Extension Packaging
+When creating a production zip of the browser extension (using `pnpm zip`, `pnpm zip:edge`, `pnpm zip:firefox`, or `pnpm zip:all`), the build system requires the following environment variables to be set:
+
+- **`WXT_GOOGLE_CLIENT_ID`**: Google OAuth client ID for extension authentication
+- **`WXT_POSTHOG_API_KEY`**: PostHog API key for anonymous opt-in analytics tracking
+- **`WXT_POSTHOG_HOST`**: PostHog host URL for analytics tracking
+
+If any of these variables are missing, the build will fail with an error message listing the missing variables.
+
+**Purpose of Analytics Variables:**
+The PostHog environment variables enable anonymous, opt-in analytics tracking to help improve user experience and guide future development direction. Analytics is enabled by default on Chromium-based browsers and disabled by default on Firefox. Users can opt out anytime in Settings → Config → About.
 
 - **Migration:**
-  - Set `WXT_GOOGLE_CLIENT_ID` in your `.env.production` file or export it in your environment before running any zip command (`pnpm zip`, `pnpm zip:edge`, `pnpm zip:firefox`, or `pnpm zip:all`).
+  - Set all required variables in your `.env.production` file or export them in your environment before running any zip command (`pnpm zip`, `pnpm zip:edge`, `pnpm zip:firefox`, or `pnpm zip:all`).
   - All zip builds are affected by this check; development builds (`pnpm dev`, etc.) are unchanged.
 
 **Example:**
 ```sh
 # .env.production
 WXT_GOOGLE_CLIENT_ID=your-google-client-id
-```
-
-Or, set it in your shell before running the zip command:
+WXT_POSTHOG_API_KEY=your-posthog-api-key
+WXT_POSTHOG_HOST=https://your-posthog-host.com
+```
+
+Or, set them in your shell before running the zip command:
 ```sh
 export WXT_GOOGLE_CLIENT_ID=your-google-client-id
+export WXT_POSTHOG_API_KEY=your-posthog-api-key
+export WXT_POSTHOG_HOST=https://your-posthog-host.com
 pnpm zip:all
 ```
 
-If the variable is missing, you will see an error like:
+If any variables are missing, you will see an error like:
 ```
 Missing required environment variables for zip:
    - WXT_GOOGLE_CLIENT_ID
+   - WXT_POSTHOG_API_KEY
+   - WXT_POSTHOG_HOST
 
 Set them in .env.production or your environment.
 ```
@@ -417,4 +432,4 @@
 
 ---
 
-**Note:** When packaging the browser extension for production (zip builds), you must set `WXT_GOOGLE_CLIENT_ID` in your environment or `.env.production`. This is a required step for successful production packaging.
+**Note:** When packaging the browser extension for production (zip builds), you must set `WXT_GOOGLE_CLIENT_ID`, `WXT_POSTHOG_API_KEY`, and `WXT_POSTHOG_HOST` in your environment or `.env.production`. These variables are required for successful production packaging.
How to contribute Read Frog
View Changes
@@ -41,6 +41,13 @@
    ```bash
    cp apps/website/.env.example apps/website/.env.local
    ```
+
+   **Extension Build Environment Variables**  
+   When building zip releases of the extension (`WXT_ZIP_MODE=true`), additional environment variables are required:
+   - `WXT_POSTHOG_API_KEY`: PostHog API key for analytics tracking
+   - `WXT_POSTHOG_HOST`: PostHog host URL for analytics tracking
+
+   These variables enable anonymous analytics tracking (opt-in by default on Chromium-based browsers, opt-out on Firefox) to help improve user experience. The build will fail validation if these are missing during a zip build.
 
 4. **Start Development Server**  
    ```bash

How did I do? Any feedback?  Join Discord

@claude
Copy link
Copy Markdown

claude bot commented Mar 17, 2026

Code Review: PR #1132 — Analytics/Telemetry Integration

Overall Assessment: 🟡 Acceptable — merge with required fixes

The analytics implementation is well-architected with good privacy defaults and clean separation of concerns. However, there are 2 critical test gaps and several minor issues to address before merging.


🟢 Strengths

  1. Privacy-First Design

    • Anonymous UUID install ID (crypto.randomUUID()), no PII
    • person_profiles: "never", autocapture: false, disable_session_recording: true
    • Property allowlist in filterAnalyticsCaptureResult() prevents accidental data leakage
    • Disabled by default on Firefox; opt-out toggle in Settings
    • save_referrer: false — no URLs collected
  2. Clean Architecture (SOLID)

    • Components only create FeatureUsageContext, never transmit directly
    • Background handler owns the PostHog client — proper dependency inversion
    • trackFeatureAttempt() helper is a clean DRY pattern for try/catch outcome tracking
    • Analytics context passed as optional parameters, not global state
  3. Smart Integration Details

    • window === window.top guard prevents duplicate iframe tracking
    • Only positive user actions tracked (not disables): nextEnabled ? context : undefined
    • Abort errors correctly ignored in selection toolbar
    • VIDEO_SUBTITLES vs VIDEO_SUBTITLES_AUTO — proper surface granularity
    • Auto-start paths intentionally omit analytics context
  4. Build-Time Validation

    • WXT_POSTHOG_API_KEY and WXT_POSTHOG_HOST validated in wxt.config.ts for zip builds
    • Prevents shipping with missing config

🔴 Critical Issues

1. trackFeatureAttempt() has zero test coverage

File: src/utils/analytics.ts (exported, used in production paths like input translation and translation hub)

This function wraps async operations and auto-tracks success/failure outcomes. It's fire-and-forget (void ignores the tracking promise), which is fine for telemetry but must be tested:

  • Success path: verify outcome: "success" is tracked and result returned
  • Failure path: verify outcome: "failure" is tracked AND original error is re-thrown
  • Tracking failure: verify original error is still thrown even if sendMessage rejects

2. analyticsEnabledAtom has zero test coverage

File: src/utils/atoms/analytics.ts

This atom is responsible for persisting the user's opt-in/opt-out preference and re-syncing on tab visibility changes. Missing tests for:

  • Atom initialization with browser-aware default value
  • Persistence to storage on update
  • Rollback on storage write failure
  • Visibility change re-fetch behavior
  • Event listener cleanup

🟡 Secondary Issues

3. Inconsistent logger usage

File: src/utils/atoms/analytics.ts:25

console.error("Failed to persist analytics preference:", error)

Should use logger.error() for consistency with the rest of the codebase.

4. Property allowlist is fragile

File: src/entrypoints/background/analytics.ts:53-86

15 hardcoded setPropertyIfDefined() calls with no compile-time safety. If properties expand, it's easy to forget one. Consider a data-driven approach:

const ALLOWED_KEYS = new Set(["token", "distinct_id", "feature", ...])
for (const [key, value] of Object.entries(properties)) {
  if (ALLOWED_KEYS.has(key) && value !== undefined) {
    filtered[key] = value
  }
}

5. Defensive logger check is suspicious

File: src/utils/analytics.ts:53

if (typeof logger.warn === "function") {

Why wouldn't logger.warn be a function? If logger setup can be incomplete, document it. Otherwise remove the guard and let it fail fast.

6. Minor env var validation gap

File: wxt.config.ts:93-97

Checks truthiness of WXT_POSTHOG_API_KEY / WXT_POSTHOG_HOST but doesn't verify they're non-empty strings. An empty string "" would pass.


📊 Test Coverage Summary

Module Tests Status
background/analytics.ts 8 🟢 Good
utils/analytics.ts 5 🟡 Missing trackFeatureAttempt()
utils/atoms/analytics.ts 0 🔴 None
constants/analytics.ts 2 🟢 Good

🔒 Privacy/Security Checklist

  • No PII in event properties (only feature, surface, outcome, latency_ms)
  • Anonymous distinct ID (UUID)
  • User opt-out respected via analyticsEnabledAtom
  • Property allowlist prevents accidental data leakage
  • No URLs, page content, or translated text collected
  • No session recording, no autocapture
  • Disabled by default on Firefox
  • Does NOT respect browser DNT header (respect_dnt config) — acceptable for opt-in system but worth noting

📋 Recommendations

Must fix before merge:

  1. Add tests for trackFeatureAttempt() (success, failure, tracking-failure paths)
  2. Add tests for analyticsEnabledAtom (init, persistence, visibility sync, cleanup)

Should fix:
3. Replace console.error with logger.error in src/utils/atoms/analytics.ts
4. Refactor property allowlist to data-driven approach
5. Remove or document the defensive typeof logger.warn check

Nice to have:
6. Validate env vars are non-empty strings, not just truthy
7. Add JSDoc comments explaining fire-and-forget analytics design choice


📝 Additional Notes

  • The PR title says "chore: improve user experience" which is vague — consider a more descriptive title like feat: add opt-in anonymous feature usage analytics
  • The PR description template is unfilled (no type selected, no description, no related issue). Please fill it out for reviewer context.
  • The posthog-js dependency adds bundle size — worth noting the impact in the PR description.

🤖 Generated with Claude Code

@ananaBMaster ananaBMaster merged commit b2d74df into main Mar 17, 2026
5 checks passed
@ananaBMaster ananaBMaster deleted the fix/lint branch March 17, 2026 01:46
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

16 issues found across 56 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/entrypoints/selection.content/selection-toolbar/translate-button/index.tsx">

<violation number="1" location="src/entrypoints/selection.content/selection-toolbar/translate-button/index.tsx:184">
P2: Guard the success analytics call with `runIdRef.current === runId` so canceled or superseded translations are not counted as successful uses.</violation>

<violation number="2" location="src/entrypoints/selection.content/selection-toolbar/translate-button/index.tsx:256">
P2: Guard failure analytics with the current `runId` check so errors from superseded translation runs are not reported as real failures.</violation>
</file>

<file name="src/entrypoints/options/pages/custom-actions/action-config-form/output-schema-field.tsx">

<violation number="1" location="src/entrypoints/options/pages/custom-actions/action-config-form/output-schema-field.tsx:402">
P2: Render `meta.errors` like the other form fields; raw `.join(", ")` will turn schema error objects into `[object Object]` and hide output-schema validation messages.</violation>
</file>

<file name="src/utils/atoms/analytics.ts">

<violation number="1" location="src/utils/atoms/analytics.ts:32">
P1: Reading this boolean preference through `storageAdapter.get` breaks persisted `false` values, so an analytics opt-out resets to the default on remount or visibility refresh.</violation>
</file>

<file name=".changeset/add-anonymous-analytics.md">

<violation number="1" location=".changeset/add-anonymous-analytics.md:2">
P2: Use a minor changeset here; this file describes a new feature, so `patch` under-versions the release.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:104">
P2: Update `pnpm-lock.yaml` alongside these dependency changes so installs stay reproducible.</violation>
</file>

<file name="src/entrypoints/background/analytics.ts">

<violation number="1" location="src/entrypoints/background/analytics.ts:126">
P2: Reset the cached PostHog client promise when initialization fails so later events can retry.</violation>
</file>

<file name="src/entrypoints/subtitles.content/ui/subtitles-translate-button.tsx">

<violation number="1" location="src/entrypoints/subtitles.content/ui/subtitles-translate-button.tsx:23">
P2: Skip the auto-start callback when subtitles are already visible, otherwise remounts can emit duplicate `video_subtitles_auto` usage events.</violation>
</file>

<file name="src/locales/ru.yml">

<violation number="1" location="src/locales/ru.yml:581">
P3: Fix the typo in the new Russian mission text: `знанияемких` should be `знаниеёмких`.</violation>
</file>

<file name="src/entrypoints/options/pages/custom-actions/action-config-form/provider-field.tsx">

<violation number="1" location="src/entrypoints/options/pages/custom-actions/action-config-form/provider-field.tsx:54">
P2: Joining `meta.errors` directly can render validation objects as `[object Object]` instead of their message text.</violation>
</file>

<file name="src/entrypoints/translation-hub/components/translation-card.tsx">

<violation number="1" location="src/entrypoints/translation-hub/components/translation-card.tsx:44">
P2: This wraps the stale-response discard path in `trackFeatureAttempt`, so superseded translations are now reported as successful Translation Hub uses even though the UI ignores them.</violation>
</file>

<file name="src/entrypoints/options/pages/custom-actions/action-config-form/icon-field.tsx">

<violation number="1" location="src/entrypoints/options/pages/custom-actions/action-config-form/icon-field.tsx:54">
P2: Keep mapping schema issues to `.message`; joining the raw errors array can render `[object Object]` instead of the validation text.</violation>
</file>

<file name="src/entrypoints/subtitles.content/universal-adapter.ts">

<violation number="1" location="src/entrypoints/subtitles.content/universal-adapter.ts:247">
P2: Don't record a successful feature-use event until subtitle translation has actually succeeded; this currently counts setup-starts as successes even when translation later fails.</violation>
</file>

<file name="src/entrypoints/host.content/translation-control/page-translation.ts">

<violation number="1" location="src/entrypoints/host.content/translation-control/page-translation.ts:162">
P1: Roll back the active state in this catch block. `start()` marks translation enabled before the remaining async setup finishes, so a later failure leaves the manager stuck active and blocks retries.</violation>
</file>

<file name="wxt.config.ts">

<violation number="1" location="wxt.config.ts:93">
P1: `WXT_POSTHOG_API_KEY` is now both forbidden and required, so production zip builds will always fail.</violation>
</file>

<file name="src/utils/atoms/theme.ts">

<violation number="1" location="src/utils/atoms/theme.ts:34">
P2: Guard the visibility-triggered storage read so it cannot overwrite a newer watcher update.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

)

baseAnalyticsEnabledAtom.onMount = (setAtom) => {
void storageAdapter.get(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Reading this boolean preference through storageAdapter.get breaks persisted false values, so an analytics opt-out resets to the default on remount or visibility refresh.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/atoms/analytics.ts, line 32:

<comment>Reading this boolean preference through `storageAdapter.get` breaks persisted `false` values, so an analytics opt-out resets to the default on remount or visibility refresh.</comment>

<file context>
@@ -0,0 +1,59 @@
+)
+
+baseAnalyticsEnabledAtom.onMount = (setAtom) => {
+  void storageAdapter.get(
+    ANALYTICS_ENABLED_STORAGE_KEY,
+    DEFAULT_ANALYTICS_ENABLED,
</file context>
Fix with Cubic

Comment on lines +162 to +170
catch (error) {
if (trackedContext) {
void trackFeatureUsed({
...trackedContext,
outcome: "failure",
})
}
throw error
}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Roll back the active state in this catch block. start() marks translation enabled before the remaining async setup finishes, so a later failure leaves the manager stuck active and blocks retries.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/host.content/translation-control/page-translation.ts, line 162:

<comment>Roll back the active state in this catch block. `start()` marks translation enabled before the remaining async setup finishes, so a later failure leaves the manager stuck active and blocks retries.</comment>

<file context>
@@ -95,44 +106,68 @@ export class PageTranslationManager implements IPageTranslationManager {
+        })
+      }
+    }
+    catch (error) {
+      if (trackedContext) {
+        void trackFeatureUsed({
</file context>
Suggested change
catch (error) {
if (trackedContext) {
void trackFeatureUsed({
...trackedContext,
outcome: "failure",
})
}
throw error
}
catch (error) {
void sendMessage("setAndNotifyPageTranslationStateChangedByManager", {
enabled: false,
})
this.isPageTranslating = false
this.walkId = null
this.dontWalkIntoElementsCache = new WeakSet()
this.stopDocumentTitleTracking()
if (this.intersectionObserver) {
this.intersectionObserver.disconnect()
this.intersectionObserver = null
}
this.mutationObservers.forEach(observer => observer.disconnect())
this.mutationObservers = []
void removeAllTranslatedWrapperNodes()
if (trackedContext) {
void trackFeatureUsed({
...trackedContext,
outcome: "failure",
})
}
throw error
}
Fix with Cubic

Comment thread wxt.config.ts
// Check required env vars only for zip builds
if (process.env.WXT_ZIP_MODE) {
const requiredEnvVars = ["WXT_GOOGLE_CLIENT_ID"]
const requiredEnvVars = [
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: WXT_POSTHOG_API_KEY is now both forbidden and required, so production zip builds will always fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At wxt.config.ts, line 93:

<comment>`WXT_POSTHOG_API_KEY` is now both forbidden and required, so production zip builds will always fail.</comment>

<file context>
@@ -90,7 +90,11 @@ export default defineConfig({
               // Check required env vars only for zip builds
               if (process.env.WXT_ZIP_MODE) {
-                const requiredEnvVars = ["WXT_GOOGLE_CLIENT_ID"]
+                const requiredEnvVars = [
+                  "WXT_GOOGLE_CLIENT_ID",
+                  "WXT_POSTHOG_API_KEY",
</file context>
Fix with Cubic

Comment on lines +256 to +260
if (!isAbortError(error)) {
void trackFeatureUsed({
...analyticsContext,
outcome: "failure",
})
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard failure analytics with the current runId check so errors from superseded translation runs are not reported as real failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/selection.content/selection-toolbar/translate-button/index.tsx, line 256:

<comment>Guard failure analytics with the current `runId` check so errors from superseded translation runs are not reported as real failures.</comment>

<file context>
@@ -225,13 +240,25 @@ export function TranslateButton() {
         setError(createSelectionToolbarRuntimeError("translate", error))
       }
+
+      if (!isAbortError(error)) {
+        void trackFeatureUsed({
+          ...analyticsContext,
</file context>
Suggested change
if (!isAbortError(error)) {
void trackFeatureUsed({
...analyticsContext,
outcome: "failure",
})
if (!isAbortError(error) && runIdRef.current === runId) {
void trackFeatureUsed({
...analyticsContext,
outcome: "failure",
})
}
Fix with Cubic

setIsTranslating(false)
setError(createSelectionToolbarPrecheckError("translate", "providerUnavailable"))
}
void trackFeatureUsed({
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard the success analytics call with runIdRef.current === runId so canceled or superseded translations are not counted as successful uses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/selection.content/selection-toolbar/translate-button/index.tsx, line 184:

<comment>Guard the success analytics call with `runIdRef.current === runId` so canceled or superseded translations are not counted as successful uses.</comment>

<file context>
@@ -174,6 +181,10 @@ export function TranslateButton() {
         setIsTranslating(false)
         setError(createSelectionToolbarPrecheckError("translate", "providerUnavailable"))
       }
+      void trackFeatureUsed({
+        ...analyticsContext,
+        outcome: "failure",
</file context>
Fix with Cubic

}

return result
return await trackFeatureAttempt(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This wraps the stale-response discard path in trackFeatureAttempt, so superseded translations are now reported as successful Translation Hub uses even though the UI ignores them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/translation-hub/components/translation-card.tsx, line 44:

<comment>This wraps the stale-response discard path in `trackFeatureAttempt`, so superseded translations are now reported as successful Translation Hub uses even though the UI ignores them.</comment>

<file context>
@@ -39,22 +41,30 @@ export function TranslationCard({ providerId, isExpanded, onExpandedChange }: Tr
-      }
-
-      return result
+      return await trackFeatureAttempt(
+        createFeatureUsageContext(
+          ANALYTICS_FEATURE.TRANSLATION_HUB,
</file context>
Fix with Cubic

{field.state.meta.errors.length > 0 && (
<span className="text-sm font-normal text-destructive">
{field.state.meta.errors.map(error => typeof error === "string" ? error : error?.message).join(", ")}
{field.state.meta.errors.join(", ")}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keep mapping schema issues to .message; joining the raw errors array can render [object Object] instead of the validation text.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/options/pages/custom-actions/action-config-form/icon-field.tsx, line 54:

<comment>Keep mapping schema issues to `.message`; joining the raw errors array can render `[object Object]` instead of the validation text.</comment>

<file context>
@@ -51,7 +51,7 @@ export const IconField = withForm({
             {field.state.meta.errors.length > 0 && (
               <span className="text-sm font-normal text-destructive">
-                {field.state.meta.errors.map(error => typeof error === "string" ? error : error?.message).join(", ")}
+                {field.state.meta.errors.join(", ")}
               </span>
             )}
</file context>
Suggested change
{field.state.meta.errors.join(", ")}
{field.state.meta.errors.map(error => typeof error === "string" ? error : error?.message).join(", ")}
Fix with Cubic

return
}

await this.processSubtitles()
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Don't record a successful feature-use event until subtitle translation has actually succeeded; this currently counts setup-starts as successes even when translation later fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entrypoints/subtitles.content/universal-adapter.ts, line 247:

<comment>Don't record a successful feature-use event until subtitle translation has actually succeeded; this currently counts setup-starts as successes even when translation later fails.</comment>

<file context>
@@ -189,45 +191,75 @@ export class UniversalVideoAdapter {
+        return
+      }
+
+      await this.processSubtitles()
+      if (analyticsContext) {
+        void trackFeatureUsed({
</file context>
Fix with Cubic

Comment thread src/utils/atoms/theme.ts
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
logger.info("baseThemeModeAtom onMount handleVisibilityChange when: ", new Date())
void storageAdapter.get<ThemeMode>(THEME_STORAGE_KEY, DEFAULT_THEME_MODE, themeModeSchema).then(setAtom)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard the visibility-triggered storage read so it cannot overwrite a newer watcher update.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/atoms/theme.ts, line 34:

<comment>Guard the visibility-triggered storage read so it cannot overwrite a newer watcher update.</comment>

<file context>
@@ -27,5 +28,16 @@ baseThemeModeAtom.onMount = (setAtom: (newValue: ThemeMode) => void) => {
+  const handleVisibilityChange = () => {
+    if (document.visibilityState === "visible") {
+      logger.info("baseThemeModeAtom onMount handleVisibilityChange when: ", new Date())
+      void storageAdapter.get<ThemeMode>(THEME_STORAGE_KEY, DEFAULT_THEME_MODE, themeModeSchema).then(setAtom)
+    }
+  }
</file context>
Fix with Cubic

Comment thread src/locales/ru.yml
about:
title: О приложении
description: Read Frog появился в апреле 2025 года
mission: Read Frog стремится расширить равный доступ к качественному образованию в эпоху ИИ, делая обучение, максимально похожее на занятия с настоящим преподавателем, доступным по цене в языках, медицине и других знанияемких областях.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Fix the typo in the new Russian mission text: знанияемких should be знаниеёмких.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/locales/ru.yml, line 581:

<comment>Fix the typo in the new Russian mission text: `знанияемких` should be `знаниеёмких`.</comment>

<file context>
@@ -575,6 +575,13 @@ options:
+    about:
+      title: О приложении
+      description: Read Frog появился в апреле 2025 года
+      mission: Read Frog стремится расширить равный доступ к качественному образованию в эпоху ИИ, делая обучение, максимально похожее на занятия с настоящим преподавателем, доступным по цене в языках, медицине и других знанияемких областях.
+      analytics:
+        title: Помочь улучшить пользовательский опыт
</file context>
Suggested change
mission: Read Frog стремится расширить равный доступ к качественному образованию в эпоху ИИ, делая обучение, максимально похожее на занятия с настоящим преподавателем, доступным по цене в языках, медицине и других знанияемких областях.
mission: Read Frog стремится расширить равный доступ к качественному образованию в эпоху ИИ, делая обучение, максимально похожее на занятия с настоящим преподавателем, доступным по цене в языках, медицине и других знаниеёмких областях.
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: browser extension Related to browser extension chore size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant