dev #81

Merged
root merged 5 commits from dev into main 2026-06-03 01:49:49 +03:00
2 changed files with 50 additions and 2 deletions
Showing only changes of commit 5d8e4023a5 - Show all commits

View File

@@ -0,0 +1,37 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
// Mock the underlying posthog-js SDK (not @/lib/posthog) so we exercise the
// real capturePageView implementation.
const captureMock = vi.fn();
vi.mock("posthog-js", () => ({
default: {
init: vi.fn(),
capture: captureMock,
identify: vi.fn(),
reset: vi.fn(),
people: { set: vi.fn() },
},
}));
import { capturePageView } from "../posthog";
describe("capturePageView", () => {
beforeEach(() => {
captureMock.mockClear();
});
it("includes the query string so UTM params are not dropped (attribution regression)", async () => {
window.history.pushState({}, "", "/?utm_source=facebook&utm_campaign=traffic_lpv_v1");
capturePageView("/");
await vi.waitFor(() => expect(captureMock).toHaveBeenCalledTimes(1));
const [event, props] = captureMock.mock.calls[0];
expect(event).toBe("$pageview");
// The whole point: $current_url must carry the UTMs, not just origin+path.
expect(props.$current_url).toContain("utm_source=facebook");
expect(props.$current_url).toContain("utm_campaign=traffic_lpv_v1");
expect(props.$current_url).toBe(window.location.href);
expect(props.$pathname).toBe("/");
});
});

View File

@@ -25,7 +25,11 @@ export function initPostHog(): void {
ph.init(key, {
api_host: "https://t.sase.tr",
defaults: "2026-01-30",
person_profiles: "identified_only",
// "always" (not "identified_only") so anonymous ad visitors get a person
// profile that captures first-touch UTM/referrer ($initial_utm_*). Required
// for ad→signup attribution. Volume is ~34k events/mo (PostHog free tier is
// 1M/mo), so the cost impact is negligible at current scale.
person_profiles: "always",
capture_pageview: false,
capture_pageleave: false,
autocapture: false,
@@ -61,7 +65,14 @@ export function capture(event: string, properties?: Record<string, unknown>): vo
}
export function capturePageView(path: string): void {
load().then((ph) => ph.capture("$pageview", { $current_url: window.location.origin + path }));
// Pass the full URL (incl. query string) so PostHog can parse UTM params and
// persist first-touch attribution ($initial_utm_*, $utm_*). Previously this
// sent only `origin + path`, silently dropping every ad UTM — so all paid
// traffic was mis-bucketed as "direct". The index route keeps unknown search
// params, so window.location.href still holds the UTMs at mount.
load().then((ph) =>
ph.capture("$pageview", { $current_url: window.location.href, $pathname: path }),
);
}
export function setPeopleProperties(properties: Record<string, unknown>): void {