diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx index 946f0f54b6..7aa36c70d6 100755 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx @@ -18,6 +18,7 @@ import { } from "date-fns"; import { TFunction } from "i18next"; import { Loader2 } from "lucide-react"; +import posthog from "posthog-js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -252,6 +253,12 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => { }); if (responsesDownloadUrlResponse?.data) { + posthog.capture("responses_exported", { + survey_id: survey.id, + survey_name: survey.name, + file_type: fileType, + filter_type: filter, + }); downloadResponsesFile( responsesDownloadUrlResponse.data.fileName, responsesDownloadUrlResponse.data.fileContents, diff --git a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts index b18567eebb..24d8cbdd1f 100644 --- a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts +++ b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts @@ -8,6 +8,7 @@ import { checkSurveyValidity } from "@/app/api/v2/client/[environmentId]/respons import { responses } from "@/app/lib/api/response"; import { transformErrorToDetails } from "@/app/lib/api/validator"; import { sendToPipeline } from "@/app/lib/pipelines"; +import { getPostHogClient } from "@/lib/posthog-server"; import { getSurvey } from "@/lib/survey/service"; import { getElementsFromBlocks } from "@/lib/survey/utils"; import { getClientIpFromHeaders } from "@/lib/utils/client-ip"; @@ -173,6 +174,21 @@ export const POST = async (request: Request, context: Context): Promise { + if (posthogClient) { + await posthogClient.shutdown(); + } +} diff --git a/apps/web/modules/auth/login/components/login-form.tsx b/apps/web/modules/auth/login/components/login-form.tsx index d94cb65ca7..14f808aefc 100644 --- a/apps/web/modules/auth/login/components/login-form.tsx +++ b/apps/web/modules/auth/login/components/login-form.tsx @@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { signIn } from "next-auth/react"; import Link from "next/dist/client/link"; import { useRouter, useSearchParams } from "next/navigation"; +import posthog from "posthog-js"; import { useEffect, useMemo, useRef, useState } from "react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; @@ -119,9 +120,15 @@ export const LoginForm = ({ } if (!signInResponse?.error) { + posthog.identify(data.email.toLowerCase(), { email: data.email.toLowerCase() }); + posthog.capture("user_logged_in", { + email: data.email.toLowerCase(), + method: "email", + }); router.push(searchParams?.get("callbackUrl") ?? "/"); } } catch (error) { + posthog.captureException(error); toast.error(error.toString()); } }; diff --git a/apps/web/modules/auth/signup/components/signup-form.tsx b/apps/web/modules/auth/signup/components/signup-form.tsx index b994550b5d..1cc5b66e12 100644 --- a/apps/web/modules/auth/signup/components/signup-form.tsx +++ b/apps/web/modules/auth/signup/components/signup-form.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; +import posthog from "posthog-js"; import { useMemo, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import toast from "react-hot-toast"; @@ -128,6 +129,14 @@ export const SignupForm = ({ : `/auth/verification-requested?token=${token}`; if (createUserResponse?.data) { + posthog.identify(data.email, { name: data.name, email: data.email }); + posthog.capture("user_signed_up", { + name: data.name, + email: data.email, + is_formbricks_cloud: isFormbricksCloud, + has_invite_token: !!inviteToken, + email_verification_disabled: emailVerificationDisabled, + }); router.push(url); if (!emailTokenActionResponse?.data) { @@ -149,6 +158,7 @@ export const SignupForm = ({ toast.error(errorMessage); } } catch (e: any) { + posthog.captureException(e); toast.error(e.message); } }; diff --git a/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx b/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx index 4247a0f2f6..05de789fe0 100644 --- a/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx +++ b/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx @@ -4,6 +4,7 @@ import { PipelineTriggers, Webhook } from "@prisma/client"; import clsx from "clsx"; import { Webhook as WebhookIcon } from "lucide-react"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; @@ -153,6 +154,11 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe webhookSecret: testResult.secret, }); if (createWebhookActionResult?.data) { + posthog.capture("webhook_created", { + environment_id: environmentId, + webhook_triggers: selectedTriggers, + survey_count: selectedAllSurveys ? "all" : selectedSurveys.length, + }); router.refresh(); setCreatedWebhook(createWebhookActionResult.data); toast.success(t("environments.integrations.webhooks.webhook_added_successfully")); diff --git a/apps/web/modules/organization/components/CreateOrganizationModal/index.tsx b/apps/web/modules/organization/components/CreateOrganizationModal/index.tsx index 4062811f8b..a6bdbf1f78 100644 --- a/apps/web/modules/organization/components/CreateOrganizationModal/index.tsx +++ b/apps/web/modules/organization/components/CreateOrganizationModal/index.tsx @@ -2,6 +2,7 @@ import { PlusCircleIcon } from "lucide-react"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; @@ -45,6 +46,10 @@ export const CreateOrganizationModal = ({ open, setOpen }: CreateOrganizationMod setLoading(true); const createOrganizationResponse = await createOrganizationAction({ organizationName: data.name }); if (createOrganizationResponse?.data) { + posthog.capture("organization_created", { + organization_id: createOrganizationResponse.data.id, + organization_name: data.name, + }); toast.success(t("environments.settings.general.organization_created_successfully")); router.push(`/organizations/${createOrganizationResponse.data.id}`); setOpen(false); diff --git a/apps/web/modules/organization/settings/api-keys/components/edit-api-keys.tsx b/apps/web/modules/organization/settings/api-keys/components/edit-api-keys.tsx index b40b99e05d..5c676849ca 100644 --- a/apps/web/modules/organization/settings/api-keys/components/edit-api-keys.tsx +++ b/apps/web/modules/organization/settings/api-keys/components/edit-api-keys.tsx @@ -2,6 +2,7 @@ import { ApiKeyPermission } from "@prisma/client"; import { FilesIcon, TrashIcon } from "lucide-react"; +import posthog from "posthog-js"; import { useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -80,6 +81,10 @@ export const EditAPIKeys = ({ organizationId, apiKeys, locale, isReadOnly, proje const updatedApiKeys = [...apiKeysLocal, createApiKeyResponse.data]; setApiKeysLocal(updatedApiKeys); setIsLoading(false); + posthog.capture("api_key_created", { + organization_id: organizationId, + api_key_label: data.label, + }); toast.success(t("environments.workspace.api_keys.api_key_created")); } else { setIsLoading(false); diff --git a/apps/web/modules/setup/organization/create/components/create-organization.tsx b/apps/web/modules/setup/organization/create/components/create-organization.tsx index 92b701db3f..2222737e90 100644 --- a/apps/web/modules/setup/organization/create/components/create-organization.tsx +++ b/apps/web/modules/setup/organization/create/components/create-organization.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; @@ -36,6 +37,11 @@ export const CreateOrganization = () => { setIsSubmitting(true); const createOrganizationResponse = await createOrganizationAction({ organizationName }); if (createOrganizationResponse?.data) { + posthog.capture("organization_created", { + organization_id: createOrganizationResponse.data.id, + organization_name: organizationName, + source: "setup", + }); router.push(`/setup/organization/${createOrganizationResponse.data.id}/invite`); } } catch (error) { diff --git a/apps/web/modules/survey/components/template-list/index.tsx b/apps/web/modules/survey/components/template-list/index.tsx index 30723fbcae..e60b29b713 100644 --- a/apps/web/modules/survey/components/template-list/index.tsx +++ b/apps/web/modules/survey/components/template-list/index.tsx @@ -2,6 +2,7 @@ import { Project } from "@prisma/client"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useMemo, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -64,6 +65,12 @@ export const TemplateList = ({ }); if (createSurveyResponse?.data) { + const eventName = surveyType === "link" ? "link_survey_created" : "in_app_survey_created"; + posthog.capture(eventName, { + survey_id: createSurveyResponse.data.id, + template_name: activeTemplate.name, + environment_id: environmentId, + }); router.push(`/environments/${environmentId}/surveys/${createSurveyResponse.data.id}/edit`); } else { const errorMessage = getFormattedErrorMessage(createSurveyResponse); diff --git a/apps/web/modules/survey/editor/components/how-to-send-card.tsx b/apps/web/modules/survey/editor/components/how-to-send-card.tsx index 0450caa3f9..cb38918e18 100644 --- a/apps/web/modules/survey/editor/components/how-to-send-card.tsx +++ b/apps/web/modules/survey/editor/components/how-to-send-card.tsx @@ -4,6 +4,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { Environment } from "@prisma/client"; import * as Collapsible from "@radix-ui/react-collapsible"; import { CheckIcon, LinkIcon, MonitorIcon } from "lucide-react"; +import posthog from "posthog-js"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { TSegment } from "@formbricks/types/segment"; @@ -42,6 +43,12 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT endings: endingsTemp, })); + posthog.capture("survey_type_selected", { + survey_id: localSurvey.id, + survey_type: type, + previous_type: localSurvey.type, + }); + // if the type is "app" and the local survey does not already have a segment, we create a new temporary segment if (type === "app" && !localSurvey.segment) { const tempSegment: TSegment = { diff --git a/apps/web/modules/survey/editor/components/survey-menu-bar.tsx b/apps/web/modules/survey/editor/components/survey-menu-bar.tsx index 3177edb504..e39b44ed9d 100644 --- a/apps/web/modules/survey/editor/components/survey-menu-bar.tsx +++ b/apps/web/modules/survey/editor/components/survey-menu-bar.tsx @@ -4,6 +4,7 @@ import { Project } from "@prisma/client"; import { isEqual } from "lodash"; import { ArrowLeftIcon, SettingsIcon } from "lucide-react"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -391,6 +392,12 @@ export const SurveyMenuBar = ({ if (updatedSurveyResponse?.data) { setLocalSurvey(updatedSurveyResponse.data); toast.success(t("environments.surveys.edit.changes_saved")); + posthog.capture("survey_saved", { + survey_id: localSurvey.id, + survey_name: localSurvey.name, + survey_type: localSurvey.type, + survey_status: localSurvey.status, + }); // Set flag to prevent beforeunload warning during router.refresh() isSuccessfullySavedRef.current = true; router.refresh(); @@ -402,6 +409,7 @@ export const SurveyMenuBar = ({ return true; } catch (e) { + posthog.captureException(e); console.error(e); setIsSurveySaving(false); toast.error(t("environments.surveys.edit.error_saving_changes")); @@ -450,10 +458,16 @@ export const SurveyMenuBar = ({ } setIsSurveyPublishing(false); + posthog.capture("survey_published", { + survey_id: localSurvey.id, + survey_name: localSurvey.name, + survey_type: localSurvey.type, + }); // Set flag to prevent beforeunload warning during navigation isSuccessfullySavedRef.current = true; router.push(`/environments/${environmentId}/surveys/${localSurvey.id}/summary?success=true`); } catch (error) { + posthog.captureException(error); console.error(error); toast.error(t("environments.surveys.edit.error_publishing_survey")); setIsSurveyPublishing(false); diff --git a/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx b/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx index 2d8fef2ecb..5d47e5415f 100644 --- a/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx +++ b/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; +import posthog from "posthog-js"; import { useMemo, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -74,6 +75,12 @@ export const SurveyDropDownMenu = ({ toast.error(getFormattedErrorMessage(result)); return; } + posthog.capture("survey_deleted", { + survey_id: surveyId, + survey_name: survey.name, + survey_type: survey.type, + survey_status: survey.status, + }); deleteSurvey(surveyId); toast.success(t("environments.surveys.survey_deleted_successfully")); } catch (error) { @@ -90,6 +97,11 @@ export const SurveyDropDownMenu = ({ // For single-use surveys, this button is disabled, so we just copy the base link const copiedLink = copySurveyLink(surveyLink); navigator.clipboard.writeText(copiedLink); + posthog.capture("survey_link_copied", { + survey_id: survey.id, + survey_name: survey.name, + survey_type: survey.type, + }); toast.success(t("common.copied_to_clipboard")); } catch (error) { logger.error(error); @@ -112,6 +124,13 @@ export const SurveyDropDownMenu = ({ if (transformedDuplicatedSurvey?.data) { onSurveysCopied?.(); } + posthog.capture("survey_duplicated", { + original_survey_id: surveyId, + new_survey_id: duplicatedSurveyResponse.data.id, + survey_name: survey.name, + survey_type: survey.type, + environment_id: environmentId, + }); toast.success(t("environments.surveys.survey_duplicated_successfully")); } else { const errorMessage = getFormattedErrorMessage(duplicatedSurveyResponse); diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index b7a4a85d60..aecdf70332 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -404,8 +404,17 @@ const nextConfig = { }, ]; }, + skipTrailingSlashRedirect: true, async rewrites() { return [ + { + source: "/ingest/static/:path*", + destination: "https://eu-assets.i.posthog.com/static/:path*", + }, + { + source: "/ingest/:path*", + destination: "https://eu.i.posthog.com/:path*", + }, { source: "/api/packages/website", destination: "/js/formbricks.umd.cjs", diff --git a/apps/web/package.json b/apps/web/package.json index b924994daf..bb4d880e83 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -102,6 +102,8 @@ "nodemailer": "8.0.1", "otplib": "12.0.1", "papaparse": "5.5.3", + "posthog-js": "1.360.0", + "posthog-node": "5.28.0", "prismjs": "1.30.0", "qr-code-styling": "1.9.2", "qrcode": "1.5.4", diff --git a/apps/web/posthog-setup-report.md b/apps/web/posthog-setup-report.md new file mode 100644 index 0000000000..51f2b4609d --- /dev/null +++ b/apps/web/posthog-setup-report.md @@ -0,0 +1,107 @@ +# PostHog Setup Report + +## Overview + +PostHog analytics has been integrated into the Formbricks web app (`apps/web`) using the Next.js App Router approach with `instrumentation-client.ts`. + +## Configuration + +| Setting | Value | +|---|---| +| PostHog Host | `https://eu.i.posthog.com` | +| Reverse Proxy | `/ingest/*` → `https://eu.i.posthog.com/*` | +| Client Init | `apps/web/instrumentation-client.ts` | +| Server Client | `apps/web/lib/posthog-server.ts` | + +## Environment Variables + +Added to `apps/web/.env`: + +``` +NEXT_PUBLIC_POSTHOG_KEY=phc_ies97ZFTIL3f8T1sQOTCWQycBzqqPvkPcOkpxYJ1sOA +NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com +``` + +## Packages Installed + +- `posthog-js` — client-side tracking +- `posthog-node` — server-side tracking + +## Files Modified + +### New Files + +- **`apps/web/instrumentation-client.ts`** — Client-side PostHog initialization (Next.js 15.3+ App Router pattern) +- **`apps/web/lib/posthog-server.ts`** — Singleton server-side PostHog client + +### Modified Files + +- **`apps/web/next.config.mjs`** — Added `skipTrailingSlashRedirect: true` and `/ingest/*` reverse proxy rewrites +- **`apps/web/modules/auth/signup/components/signup-form.tsx`** — Added `user_signed_up` event + `posthog.identify()` +- **`apps/web/modules/auth/login/components/login-form.tsx`** — Added `user_logged_in` event + `posthog.identify()` +- **`apps/web/modules/survey/editor/components/survey-menu-bar.tsx`** — Added `survey_saved` and `survey_published` events +- **`apps/web/modules/survey/list/components/survey-dropdown-menu.tsx`** — Added `survey_deleted`, `survey_duplicated`, and `survey_link_copied` events +- **`apps/web/modules/survey/components/template-list/index.tsx`** — Added `link_survey_created` / `in_app_survey_created` events +- **`apps/web/modules/survey/editor/components/how-to-send-card.tsx`** — Added `survey_type_selected` event +- **`apps/web/modules/organization/settings/api-keys/components/edit-api-keys.tsx`** — Added `api_key_created` event +- **`apps/web/modules/organization/components/CreateOrganizationModal/index.tsx`** — Added `organization_created` event +- **`apps/web/modules/setup/organization/create/components/create-organization.tsx`** — Added `organization_created` event (setup flow) +- **`apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx`** — Added `webhook_created` event +- **`apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx`** — Added `responses_exported` event +- **`apps/web/app/api/v2/client/[environmentId]/responses/route.ts`** — Added server-side `survey_response_finished` event + +## Events Tracked + +| Event | Location | Properties | +|---|---|---| +| `user_signed_up` | `signup-form.tsx` | `name`, `email`, `is_formbricks_cloud`, `has_invite_token`, `email_verification_disabled` | +| `user_logged_in` | `login-form.tsx` | `email`, `method` | +| `link_survey_created` | `template-list/index.tsx` | `survey_id`, `template_name`, `environment_id` | +| `in_app_survey_created` | `template-list/index.tsx` | `survey_id`, `template_name`, `environment_id` | +| `survey_type_selected` | `how-to-send-card.tsx` | `survey_id`, `survey_type`, `previous_type` | +| `survey_saved` | `survey-menu-bar.tsx` | `survey_id`, `survey_name`, `survey_type`, `survey_status` | +| `survey_published` | `survey-menu-bar.tsx` | `survey_id`, `survey_name`, `survey_type` | +| `survey_deleted` | `survey-dropdown-menu.tsx` | `survey_id`, `survey_name`, `survey_type`, `survey_status` | +| `survey_duplicated` | `survey-dropdown-menu.tsx` | `original_survey_id`, `new_survey_id`, `survey_name`, `survey_type`, `environment_id` | +| `survey_link_copied` | `survey-dropdown-menu.tsx` | `survey_id`, `survey_name`, `survey_type` | +| `survey_response_finished` | `responses/route.ts` (server) | `survey_id`, `response_id`, `environment_id` | +| `organization_created` | `CreateOrganizationModal`, `create-organization.tsx` | `organization_id`, `organization_name` | +| `api_key_created` | `edit-api-keys.tsx` | `organization_id`, `api_key_label` | +| `webhook_created` | `add-webhook-modal.tsx` | `environment_id`, `webhook_triggers`, `survey_count` | +| `responses_exported` | `CustomFilter.tsx` | `survey_id`, `survey_name`, `file_type`, `filter_type` | + +## PostHog Dashboards + +### 1. Analytics Basics (ID: 563010) + +**URL:** https://eu.posthog.com/project/138028/dashboard/563010 + +| Insight | Type | URL | +|---|---|---| +| User Signups & Logins Over Time | Trends (line) | https://eu.posthog.com/project/138028/insights/vRTZYMqz | +| Signup to First Survey Published Funnel | Funnel | https://eu.posthog.com/project/138028/insights/SXvzJPQz | +| Survey Lifecycle Events | Trends (line) — includes `link_survey_created` + `in_app_survey_created` | https://eu.posthog.com/project/138028/insights/8NJpApcm | +| Survey Engagement Actions | Trends (bar) | https://eu.posthog.com/project/138028/insights/cQRrcfBq | +| Full User Journey Funnel | Funnel (unordered) — includes both survey creation events | https://eu.posthog.com/project/138028/insights/kFjH9atY | +| Total Surveys Created | Trends (bar) — combines `link_survey_created` + `in_app_survey_created` | https://eu.posthog.com/project/138028/insights/rAPBfnmR | + +### 2. Survey Creation & Types (ID: 563022) + +**URL:** https://eu.posthog.com/project/138028/dashboard/563022 + +| Insight | Type | URL | +|---|---|---| +| Link vs In-App Survey Creation | Trends (line) | https://eu.posthog.com/project/138028/insights/QL0LkWoI | +| Survey Type Selection Distribution | Trends (pie) | https://eu.posthog.com/project/138028/insights/KPSUe2IF | +| Survey Creation to Response Funnel | Funnel (unordered) — includes both `link_survey_created` + `in_app_survey_created` | https://eu.posthog.com/project/138028/insights/9UPWbBAf | +| Total Surveys Created | Trends (bar) — combines both events | https://eu.posthog.com/project/138028/insights/rAPBfnmR | + +### 3. Integrations & API (ID: 563021) + +**URL:** https://eu.posthog.com/project/138028/dashboard/563021 + +| Insight | Type | URL | +|---|---|---| +| Integrations & API Usage | Trends (line) | https://eu.posthog.com/project/138028/insights/ATYgsDY2 | +| Organizations & Responses Over Time | Trends (line) | https://eu.posthog.com/project/138028/insights/DZVTeqql | +| Response Export Format Distribution | Trends (bar value) | https://eu.posthog.com/project/138028/insights/00bQNehy | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a5bc93630..8214c44bf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -360,6 +360,12 @@ importers: papaparse: specifier: 5.5.3 version: 5.5.3 + posthog-js: + specifier: 1.360.0 + version: 1.360.0 + posthog-node: + specifier: 5.28.0 + version: 5.28.0(rxjs@7.8.2) prismjs: specifier: 1.30.0 version: 1.30.0 @@ -2795,6 +2801,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.5.0': resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2825,6 +2837,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-logs-otlp-http@0.208.0': + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-logs-otlp-http@0.212.0': resolution: {integrity: sha512-JidJasLwG/7M9RTxV/64xotDKmFAUSBc9SNlxI32QYuUMK5rVKhHNWMPDzC7E0pCAL3cu+FyiKvsTwLi2KqPYw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3311,6 +3329,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.208.0': + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.212.0': resolution: {integrity: sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3347,6 +3371,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.208.0': + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.212.0': resolution: {integrity: sha512-bj7zYFOg6Db7NUwsRZQ/WoVXpAf41WY2gsd3kShSfdpZQDRKHWJiRZIg7A8HvWsf97wb05rMFzPbmSHyjEl9tw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3417,6 +3447,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.5.1': resolution: {integrity: sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3429,6 +3465,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-logs@0.208.0': + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.212.0': resolution: {integrity: sha512-qglb5cqTf0mOC1sDdZ7nfrPjgmAqs2OxkzOPIf2+Rqx8yKBK0pS7wRtB1xH30rqahBIut9QJDbDePyvtyqvH/Q==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3453,6 +3495,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.5.1': resolution: {integrity: sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3477,6 +3525,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.5.1': resolution: {integrity: sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3553,6 +3607,12 @@ packages: engines: {node: '>=18'} hasBin: true + '@posthog/core@1.23.2': + resolution: {integrity: sha512-zTDdda9NuSHrnwSOfFMxX/pyXiycF4jtU1kTr8DL61dHhV+7LF6XF1ndRZZTuaGGbfbb/GJYkEsjEX9SXfNZeQ==} + + '@posthog/types@1.360.0': + resolution: {integrity: sha512-roypbiJ49V3jWlV/lzhXGf0cKLLRj69L4H4ZHW6YsITHlnjQ12cgdPhPS88Bb9nW9xZTVSGWWDjfNGsdgAxsNg==} + '@preact/preset-vite@2.10.3': resolution: {integrity: sha512-1SiS+vFItpkNdBs7q585PSAIln0wBeBdcpJYbzPs1qipsb/FssnkUioNXuRsb8ZnU8YEQHr+3v8+/mzWSnTQmg==} peerDependencies: @@ -6946,6 +7006,9 @@ packages: core-js-compat@3.47.0: resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + core-js@3.48.0: + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -7214,6 +7277,10 @@ packages: dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.3.2: + resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} + engines: {node: '>=20'} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -7728,6 +7795,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -9584,6 +9654,18 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} + posthog-js@1.360.0: + resolution: {integrity: sha512-jkyO+T97yi6RuiexOaXC7AnEGiC+yIfGU5DIUzI5rqBH6MltmtJw/ve2Oxc4jeua2WDr5sXMzo+SS+acbpueAA==} + + posthog-node@5.28.0: + resolution: {integrity: sha512-EETYV0zA+7BLQmXzY+vGyDMoQK8uHf8f/1utbRjKncI41gPkw+4piGP7l4UT5Luld+4vQpJPOR1q1YrbXm7XjQ==} + engines: {node: ^20.20.0 || >=22.22.0} + peerDependencies: + rxjs: ^7.0.0 + peerDependenciesMeta: + rxjs: + optional: true + powershell-utils@0.1.0: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} @@ -9802,6 +9884,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -11186,6 +11271,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-vitals@5.1.0: + resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -13971,6 +14059,11 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14006,6 +14099,15 @@ snapshots: '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http@0.212.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14719,6 +14821,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.212.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14761,6 +14869,17 @@ snapshots: '@opentelemetry/otlp-exporter-base': 0.57.1(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.57.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + '@opentelemetry/otlp-transformer@0.212.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14847,6 +14966,12 @@ snapshots: '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14859,6 +14984,13 @@ snapshots: '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs@0.212.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14887,6 +15019,12 @@ snapshots: '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14936,6 +15074,13 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -15008,6 +15153,12 @@ snapshots: dependencies: playwright: 1.58.2 + '@posthog/core@1.23.2': + dependencies: + cross-spawn: 7.0.6 + + '@posthog/types@1.360.0': {} + '@preact/preset-vite@2.10.3(@babel/core@7.29.0)(preact@10.28.2)(rollup@4.59.0)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 @@ -18788,6 +18939,8 @@ snapshots: dependencies: browserslist: 4.28.1 + core-js@3.48.0: {} + core-util-is@1.0.3: {} cors@2.8.5: @@ -19028,6 +19181,10 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dompurify@3.3.2: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -19792,6 +19949,8 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fflate@0.4.8: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -21784,6 +21943,28 @@ snapshots: postgres@3.4.7: optional: true + posthog-js@1.360.0: + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@posthog/core': 1.23.2 + '@posthog/types': 1.360.0 + core-js: 3.48.0 + dompurify: 3.3.2 + fflate: 0.4.8 + preact: 10.28.2 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.1.0 + + posthog-node@5.28.0(rxjs@7.8.2): + dependencies: + '@posthog/core': 1.23.2 + optionalDependencies: + rxjs: 7.8.2 + powershell-utils@0.1.0: {} preact-render-to-string@5.2.6(preact@10.28.2): @@ -21970,6 +22151,8 @@ snapshots: quansync@0.2.11: {} + query-selector-shadow-dom@1.0.1: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -23556,6 +23739,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-vitals@5.1.0: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {}