Compare commits

...

3 Commits

Author SHA1 Message Date
Jiang Bohan
d9bf076eac feat(analytics): migrate from Mixpanel to PostHog
Replace mixpanel-browser with posthog-js for frontend analytics.
PostHog provides CLI + SQL query support (HogQL) which enables
agents to query analytics data directly.

Key changes:
- Replace mixpanel-browser dep with posthog-js
- Rewrite analytics module: mixpanel.ts → posthog.ts
  - mixpanel.track() → posthog.capture()
  - mixpanel.identify() → posthog.identify()
  - mixpanel.register() → posthog.register()
  - mixpanel.reset() → posthog.reset()
- Remove incrementUserProperty (PostHog counts events natively via HogQL)
- Env vars: NEXT_PUBLIC_MIXPANEL_TOKEN → NEXT_PUBLIC_POSTHOG_KEY + NEXT_PUBLIC_POSTHOG_HOST
- All existing tracking points preserved, same event names and properties
2026-04-09 16:18:42 +08:00
Jiang Bohan
a47113173f feat(analytics): add remaining Mixpanel tracking events
Adds tracking to all key user interactions:
- Issue pickers: status, priority, assignee, due date changes
- Issue lifecycle: delete, batch update/delete, comments, reactions
- Agent creation with people.increment
- Inbox notification clicks
- Settings tab changes
- Landing page CTA clicks (hero + header)
- Member invite
2026-04-03 15:47:26 +08:00
Jiang Bohan
fde3afe289 feat(analytics): add Mixpanel tracking with env-based toggle
Introduces a `features/analytics/` module that wraps the Mixpanel JS SDK.
All tracking is gated behind `NEXT_PUBLIC_MIXPANEL_TOKEN` — when the env
var is absent, every analytics call is a silent no-op, so open-source
users who clone the repo are unaffected.

Core integration points:
- AnalyticsProvider in root layout initializes SDK and tracks page views
- Auth store/initializer: identify on login, reset on logout
- Workspace store: register super properties on workspace hydrate/switch
- Login page: track login_started on OTP request
- Create issue modal: track issue_created with metadata

Also adds event name constants (events.ts) and user profile helpers
(identifyUser, incrementUserProperty).
2026-04-03 15:35:58 +08:00
27 changed files with 553 additions and 7 deletions

View File

@@ -45,6 +45,10 @@ FRONTEND_ORIGIN=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws
# Analytics (optional) — leave empty to disable PostHog tracking
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
# Remote API (optional) — set to proxy local frontend to a remote backend
# Leave empty to use local backend (localhost:8080)
# REMOTE_API_URL=https://multica-api.copilothub.ai

View File

@@ -21,6 +21,7 @@ import {
InputOTPGroup,
InputOTPSlot,
} from "@/components/ui/input-otp";
import { track, AnalyticsEvents } from "@/features/analytics";
import type { User } from "@/shared/types";
function validateCliCallback(cliCallback: string): boolean {
@@ -118,6 +119,9 @@ function LoginPageContent() {
setSubmitting(true);
try {
await sendCode(email);
track(AnalyticsEvents.LOGIN_STARTED, {
email_domain: email.split("@")[1],
});
setStep("code");
setCode("");
setCooldown(10);

View File

@@ -73,6 +73,7 @@ import { Label } from "@/components/ui/label";
import { toast } from "sonner";
import { Skeleton } from "@/components/ui/skeleton";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore } from "@/features/workspace";
import { useRuntimeStore } from "@/features/runtimes";
@@ -1575,6 +1576,7 @@ export default function AgentsPage() {
const handleCreate = async (data: CreateAgentRequest) => {
const agent = await api.createAgent(data);
track(AnalyticsEvents.AGENT_CREATED, { visibility: data.visibility });
await refreshAgents();
setSelectedId(agent.id);
};

View File

@@ -34,6 +34,7 @@ import {
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
// ---------------------------------------------------------------------------
// Helpers
@@ -247,6 +248,7 @@ export default function InboxPage() {
// Click-to-read: select + auto-mark-read
const handleSelect = async (item: InboxItem) => {
track(AnalyticsEvents.NOTIFICATION_CLICKED, { notification_type: item.type, related_issue_id: item.issue_id });
setSelectedKey(item.issue_id ?? item.id);
if (!item.read) {
useInboxStore.getState().markRead(item.id);

View File

@@ -39,6 +39,7 @@ import { toast } from "sonner";
import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore } from "@/features/workspace";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
const roleConfig: Record<MemberRole, { label: string; icon: typeof Crown; description: string }> = {
owner: { label: "Owner", icon: Crown, description: "Full access, manage all settings" },
@@ -169,6 +170,7 @@ export function MembersTab() {
setInviteEmail("");
setInviteRole("member");
await refreshMembers();
track(AnalyticsEvents.MEMBER_INVITED, { role: inviteRole });
toast.success("Member added");
} catch (e) {
toast.error(e instanceof Error ? e.message : "Failed to add member");

View File

@@ -3,6 +3,7 @@
import { User, Palette, Key, Settings, Users, FolderGit2 } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { useWorkspaceStore } from "@/features/workspace";
import { track, AnalyticsEvents } from "@/features/analytics";
import { AccountTab } from "./_components/account-tab";
import { AppearanceTab } from "./_components/general-tab";
import { TokensTab } from "./_components/tokens-tab";
@@ -26,7 +27,7 @@ export default function SettingsPage() {
const workspaceName = useWorkspaceStore((s) => s.workspace?.name);
return (
<Tabs defaultValue="profile" orientation="vertical" className="flex-1 min-h-0 gap-0">
<Tabs defaultValue="profile" orientation="vertical" className="flex-1 min-h-0 gap-0" onValueChange={(tab) => track(AnalyticsEvents.SETTINGS_OPENED, { tab })}>
{/* Left nav */}
<div className="w-52 shrink-0 border-r overflow-y-auto p-4">
<h1 className="text-sm font-semibold mb-4 px-2">Settings</h1>

View File

@@ -7,6 +7,7 @@ import { cn } from "@/lib/utils";
import { AuthInitializer } from "@/features/auth";
import { WSProvider } from "@/features/realtime";
import { ModalRegistry } from "@/features/modals";
import { AnalyticsProvider } from "@/features/analytics";
import "./globals.css";
const geist = Geist({ subsets: ["latin"], variable: "--font-sans" });
@@ -67,11 +68,13 @@ export default async function RootLayout({
>
<body className="h-full overflow-hidden">
<ThemeProvider>
<AnalyticsProvider>
<AuthInitializer>
<WSProvider>{children}</WSProvider>
</AuthInitializer>
<ModalRegistry />
<Toaster />
</AnalyticsProvider>
</ThemeProvider>
</body>
</html>

View File

@@ -0,0 +1,61 @@
/**
* Centralized event name constants for PostHog tracking.
* Using constants prevents typos and makes it easy to audit all tracked events.
*/
export const AnalyticsEvents = {
// Page views
PAGE_VIEWED: "page_viewed",
// Auth
LOGIN_STARTED: "login_started",
LOGIN_OTP_VERIFIED: "login_otp_verified",
LOGOUT: "logout",
// Issue management
ISSUE_LIST_VIEWED: "issue_list_viewed",
ISSUE_VIEW_MODE_CHANGED: "issue_view_mode_changed",
ISSUE_FILTER_APPLIED: "issue_filter_applied",
ISSUE_CREATED: "issue_created",
ISSUE_OPENED: "issue_opened",
ISSUE_STATUS_CHANGED: "issue_status_changed",
ISSUE_PRIORITY_CHANGED: "issue_priority_changed",
ISSUE_ASSIGNEE_CHANGED: "issue_assignee_changed",
ISSUE_DESCRIPTION_EDITED: "issue_description_edited",
ISSUE_DUE_DATE_CHANGED: "issue_due_date_changed",
ISSUE_COMMENT_ADDED: "issue_comment_added",
ISSUE_REACTION_ADDED: "issue_reaction_added",
ISSUE_DELETED: "issue_deleted",
ISSUE_BATCH_ACTION: "issue_batch_action",
// Agent management
AGENT_LIST_VIEWED: "agent_list_viewed",
AGENT_CREATED: "agent_created",
AGENT_OPENED: "agent_opened",
AGENT_CONFIG_UPDATED: "agent_config_updated",
AGENT_ASSIGNED_TO_ISSUE: "agent_assigned_to_issue",
// Workspace
WORKSPACE_CREATED: "workspace_created",
WORKSPACE_SWITCHED: "workspace_switched",
MEMBER_INVITED: "member_invited",
MEMBER_REMOVED: "member_removed",
REPOSITORY_CONNECTED: "repository_connected",
// Runtimes
RUNTIMES_PAGE_VIEWED: "runtimes_page_viewed",
RUNTIME_DETAIL_OPENED: "runtime_detail_opened",
// Settings
SETTINGS_OPENED: "settings_opened",
APPEARANCE_CHANGED: "appearance_changed",
API_TOKEN_CREATED: "api_token_created",
PROFILE_UPDATED: "profile_updated",
// Inbox
INBOX_OPENED: "inbox_opened",
NOTIFICATION_CLICKED: "notification_clicked",
// Landing
LANDING_PAGE_VIEWED: "landing_page_viewed",
CTA_CLICKED: "cta_clicked",
} as const;

View File

@@ -0,0 +1,9 @@
export { AnalyticsProvider } from "./provider";
export { AnalyticsEvents } from "./events";
export {
track,
identifyUser,
resetUser,
registerSuperProperties,
isAnalyticsEnabled,
} from "./posthog";

View File

@@ -0,0 +1,64 @@
"use client";
import posthog from "posthog-js";
import { createLogger } from "@/shared/logger";
const logger = createLogger("analytics");
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com";
/** Whether PostHog is enabled (API key is configured). */
export const isAnalyticsEnabled = Boolean(POSTHOG_KEY);
let initialized = false;
/**
* Initialize PostHog. Safe to call multiple times — subsequent calls are no-ops.
* If NEXT_PUBLIC_POSTHOG_KEY is not set, all analytics calls become silent no-ops.
*/
export function initAnalytics() {
if (!isAnalyticsEnabled || initialized) return;
posthog.init(POSTHOG_KEY!, {
api_host: POSTHOG_HOST,
capture_pageview: false, // we track page views manually for more control
capture_pageleave: true,
persistence: "localStorage",
});
initialized = true;
logger.info("posthog initialized");
}
/**
* Identify the current user. Call after login or session restore.
*/
export function identifyUser(userId: string, properties?: Record<string, unknown>) {
if (!isAnalyticsEnabled) return;
posthog.identify(userId, properties);
}
/**
* Reset identity. Call on logout.
*/
export function resetUser() {
if (!isAnalyticsEnabled) return;
posthog.reset();
}
/**
* Register super properties — attached to every subsequent event.
*/
export function registerSuperProperties(properties: Record<string, unknown>) {
if (!isAnalyticsEnabled) return;
posthog.register(properties);
}
/**
* Track an event with optional properties.
*/
export function track(event: string, properties?: Record<string, unknown>) {
if (!isAnalyticsEnabled) return;
posthog.capture(event, properties);
}

View File

@@ -0,0 +1,38 @@
"use client";
import { useEffect, useRef, type ReactNode } from "react";
import { usePathname } from "next/navigation";
import { initAnalytics, isAnalyticsEnabled, track } from "./posthog";
import { AnalyticsEvents } from "./events";
/**
* Initializes PostHog on mount and tracks page views on route changes.
* When NEXT_PUBLIC_POSTHOG_KEY is not set, this component is effectively a no-op.
*/
export function AnalyticsProvider({ children }: { children: ReactNode }) {
const pathname = usePathname();
const prevPathRef = useRef<string | null>(null);
// Initialize PostHog once
useEffect(() => {
initAnalytics();
}, []);
// Track page views on route changes
useEffect(() => {
if (!isAnalyticsEnabled) return;
const previousPath = prevPathRef.current;
prevPathRef.current = pathname;
// Skip initial mount (no previous path) — or track it if desired
if (previousPath === pathname) return;
track(AnalyticsEvents.PAGE_VIEWED, {
path: pathname,
previous_path: previousPath,
});
}, [pathname]);
return <>{children}</>;
}

View File

@@ -5,6 +5,7 @@ import { useAuthStore } from "./store";
import { useWorkspaceStore } from "@/features/workspace";
import { api } from "@/shared/api";
import { createLogger } from "@/shared/logger";
import { identifyUser, resetUser } from "@/features/analytics";
import { setLoggedInCookie, clearLoggedInCookie } from "./auth-cookie";
const logger = createLogger("auth");
@@ -32,6 +33,11 @@ export function AuthInitializer({ children }: { children: ReactNode }) {
Promise.all([mePromise, wsPromise])
.then(([user, wsList]) => {
setLoggedInCookie();
identifyUser(user.id, {
name: user.name,
email: user.email,
created_at: user.created_at,
});
useAuthStore.setState({ user, isLoading: false });
useWorkspaceStore.getState().hydrateWorkspace(wsList, wsId);
})
@@ -42,6 +48,7 @@ export function AuthInitializer({ children }: { children: ReactNode }) {
localStorage.removeItem("multica_token");
localStorage.removeItem("multica_workspace_id");
clearLoggedInCookie();
resetUser();
useAuthStore.setState({ user: null, isLoading: false });
});
}, []);

View File

@@ -3,6 +3,7 @@
import { create } from "zustand";
import type { User } from "@/shared/types";
import { api } from "@/shared/api";
import { track, identifyUser, resetUser, AnalyticsEvents } from "@/features/analytics";
import { setLoggedInCookie, clearLoggedInCookie } from "./auth-cookie";
interface AuthState {
@@ -50,16 +51,26 @@ export const useAuthStore = create<AuthState>((set) => ({
localStorage.setItem("multica_token", token);
api.setToken(token);
setLoggedInCookie();
identifyUser(user.id, {
name: user.name,
email: user.email,
created_at: user.created_at,
});
track(AnalyticsEvents.LOGIN_OTP_VERIFIED, {
email_domain: email.split("@")[1],
});
set({ user });
return user;
},
logout: () => {
track(AnalyticsEvents.LOGOUT);
localStorage.removeItem("multica_token");
localStorage.removeItem("multica_workspace_id");
api.setToken(null);
api.setWorkspaceId(null);
clearLoggedInCookie();
resetUser();
set({ user: null });
},

View File

@@ -24,6 +24,7 @@ import { ALL_STATUSES, STATUS_CONFIG, PRIORITY_ORDER, PRIORITY_CONFIG } from "@/
import { useIssueStore } from "@/features/issues/store";
import { useIssueSelectionStore } from "@/features/issues/stores/selection-store";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
import { StatusIcon } from "./status-icon";
import { PriorityIcon } from "./priority-icon";
import { AssigneePicker } from "./pickers";
@@ -50,6 +51,7 @@ export function BatchActionToolbar() {
for (const id of ids) {
useIssueStore.getState().updateIssue(id, updates);
}
track(AnalyticsEvents.ISSUE_BATCH_ACTION, { action_type: "update", issue_count: count, ...updates });
toast.success(`Updated ${count} issue${count > 1 ? "s" : ""}`);
} catch {
toast.error("Failed to update issues");
@@ -68,6 +70,7 @@ export function BatchActionToolbar() {
for (const id of ids) {
useIssueStore.getState().removeIssue(id);
}
track(AnalyticsEvents.ISSUE_BATCH_ACTION, { action_type: "delete", issue_count: count });
clear();
toast.success(`Deleted ${count} issue${count > 1 ? "s" : ""}`);
} catch {

View File

@@ -4,6 +4,7 @@ import { useRef, useState } from "react";
import { ArrowUp, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ContentEditor, type ContentEditorRef } from "@/features/editor";
import { track, AnalyticsEvents } from "@/features/analytics";
import { FileUploadButton } from "@/components/common/file-upload-button";
import { useFileUpload } from "@/shared/hooks/use-file-upload";
@@ -28,6 +29,7 @@ function CommentInput({ issueId, onSubmit }: CommentInputProps) {
setSubmitting(true);
try {
await onSubmit(content);
track(AnalyticsEvents.ISSUE_COMMENT_ADDED, { content_length: content.length });
editorRef.current?.clearContent();
setIsEmpty(true);
} finally {

View File

@@ -71,6 +71,7 @@ import { useIssueTimeline } from "@/features/issues/hooks/use-issue-timeline";
import { useIssueReactions } from "@/features/issues/hooks/use-issue-reactions";
import { useIssueSubscribers } from "@/features/issues/hooks/use-issue-subscribers";
import { ReactionBar } from "@/components/common/reaction-bar";
import { track, AnalyticsEvents } from "@/features/analytics";
import { useFileUpload } from "@/shared/hooks/use-file-upload";
import { timeAgo } from "@/shared/utils";
@@ -298,6 +299,7 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
try {
await api.deleteIssue(issue!.id);
useIssueStore.getState().removeIssue(issue!.id);
track(AnalyticsEvents.ISSUE_DELETED);
toast.success("Issue deleted");
if (onDelete) onDelete();
else router.push("/issues");

View File

@@ -6,6 +6,7 @@ import type { Agent, IssueAssigneeType, UpdateIssueRequest } from "@/shared/type
import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { ActorAvatar } from "@/components/common/actor-avatar";
import { track, AnalyticsEvents } from "@/features/analytics";
import {
PropertyPicker,
PickerItem,
@@ -95,6 +96,7 @@ export function AssigneePicker({
<PickerItem
selected={!assigneeType && !assigneeId}
onClick={() => {
track(AnalyticsEvents.ISSUE_ASSIGNEE_CHANGED, { assignee_type: null });
onUpdate({ assignee_type: null, assignee_id: null });
setOpen(false);
}}
@@ -111,6 +113,7 @@ export function AssigneePicker({
key={m.user_id}
selected={isSelected("member", m.user_id)}
onClick={() => {
track(AnalyticsEvents.ISSUE_ASSIGNEE_CHANGED, { assignee_type: "member" });
onUpdate({
assignee_type: "member",
assignee_id: m.user_id,
@@ -137,6 +140,7 @@ export function AssigneePicker({
disabled={!allowed}
onClick={() => {
if (!allowed) return;
track(AnalyticsEvents.ISSUE_ASSIGNEE_CHANGED, { assignee_type: "agent", is_agent: true });
onUpdate({
assignee_type: "agent",
assignee_id: a.id,

View File

@@ -3,6 +3,7 @@
import { useState } from "react";
import { CalendarDays } from "lucide-react";
import type { UpdateIssueRequest } from "@/shared/types";
import { track, AnalyticsEvents } from "@/features/analytics";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
@@ -45,6 +46,7 @@ export function DueDatePicker({
mode="single"
selected={date}
onSelect={(d: Date | undefined) => {
track(AnalyticsEvents.ISSUE_DUE_DATE_CHANGED, { has_due_date: Boolean(d) });
onUpdate({ due_date: d ? d.toISOString() : null });
setOpen(false);
}}

View File

@@ -3,6 +3,7 @@
import { useState } from "react";
import type { IssuePriority, UpdateIssueRequest } from "@/shared/types";
import { PRIORITY_ORDER, PRIORITY_CONFIG } from "@/features/issues/config";
import { track, AnalyticsEvents } from "@/features/analytics";
import { PriorityIcon } from "../priority-icon";
import { PropertyPicker, PickerItem } from "./property-picker";
@@ -39,6 +40,7 @@ export function PriorityPicker({
key={p}
selected={p === priority}
onClick={() => {
track(AnalyticsEvents.ISSUE_PRIORITY_CHANGED, { from: priority, to: p });
onUpdate({ priority: p });
setOpen(false);
}}

View File

@@ -3,6 +3,7 @@
import { useState } from "react";
import type { IssueStatus, UpdateIssueRequest } from "@/shared/types";
import { ALL_STATUSES, STATUS_CONFIG } from "@/features/issues/config";
import { track, AnalyticsEvents } from "@/features/analytics";
import { StatusIcon } from "../status-icon";
import { PropertyPicker, PickerItem } from "./property-picker";
@@ -36,6 +37,7 @@ export function StatusPicker({
selected={s === status}
hoverClassName={c.hoverBg}
onClick={() => {
track(AnalyticsEvents.ISSUE_STATUS_CHANGED, { from_status: status, to_status: s });
onUpdate({ status: s });
setOpen(false);
}}

View File

@@ -7,6 +7,7 @@ import type {
IssueReactionRemovedPayload,
} from "@/shared/types";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
import { toast } from "sonner";
import { useWSEvent, useWSReconnect } from "@/features/realtime";
@@ -98,6 +99,7 @@ export function useIssueReactions(issueId: string, userId?: string) {
setReactions((prev) => [...prev, temp]);
try {
const reaction = await api.addIssueReaction(issueId, emoji);
track(AnalyticsEvents.ISSUE_REACTION_ADDED, { emoji });
setReactions((prev) => prev.map((r) => (r.id === temp.id ? reaction : r)));
} catch {
setReactions((prev) => prev.filter((r) => r.id !== temp.id));

View File

@@ -5,6 +5,7 @@ import { MulticaIcon } from "@/components/multica-icon";
import { cn } from "@/lib/utils";
import { useAuthStore } from "@/features/auth";
import { useLocale } from "../i18n";
import { track, AnalyticsEvents } from "@/features/analytics";
import { GitHubMark, githubUrl, headerButtonClassName } from "./shared";
export function LandingHeader({
@@ -49,6 +50,7 @@ export function LandingHeader({
target="_blank"
rel="noreferrer"
className={headerButtonClassName("ghost", variant)}
onClick={() => track(AnalyticsEvents.CTA_CLICKED, { button_name: "github", section: "header" })}
>
<GitHubMark className="size-3.5" />
{t.header.github}
@@ -56,6 +58,7 @@ export function LandingHeader({
<Link
href={user ? "/issues" : "/login"}
className={headerButtonClassName("solid", variant)}
onClick={() => track(AnalyticsEvents.CTA_CLICKED, { button_name: user ? "dashboard" : "login", section: "header" })}
>
{user ? t.header.dashboard : t.header.login}
</Link>

View File

@@ -4,6 +4,7 @@ import Image from "next/image";
import Link from "next/link";
import { useAuthStore } from "@/features/auth";
import { useLocale } from "../i18n";
import { track, AnalyticsEvents } from "@/features/analytics";
import {
ClaudeCodeLogo,
CodexLogo,
@@ -37,7 +38,7 @@ export function LandingHero() {
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link href={user ? "/issues" : "/login"} className={heroButtonClassName("solid")}>
<Link href={user ? "/issues" : "/login"} className={heroButtonClassName("solid")} onClick={() => track(AnalyticsEvents.CTA_CLICKED, { button_name: user ? "dashboard" : "get_started", section: "hero" })}>
{user ? t.header.dashboard : t.hero.cta}
</Link>
<Link
@@ -45,6 +46,7 @@ export function LandingHero() {
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
onClick={() => track(AnalyticsEvents.CTA_CLICKED, { button_name: "github", section: "hero" })}
>
<GitHubMark className="size-4" />
GitHub

View File

@@ -33,6 +33,7 @@ import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { useIssueStore } from "@/features/issues";
import { useIssueDraftStore } from "@/features/issues/stores/draft-store";
import { api } from "@/shared/api";
import { track, AnalyticsEvents } from "@/features/analytics";
import { useFileUpload } from "@/shared/hooks/use-file-upload";
import { FileUploadButton } from "@/components/common/file-upload-button";
import { ActorAvatar } from "@/components/common/actor-avatar";
@@ -132,6 +133,13 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data?
due_date: dueDate || undefined,
});
useIssueStore.getState().addIssue(issue);
track(AnalyticsEvents.ISSUE_CREATED, {
has_description: Boolean(descEditorRef.current?.getMarkdown()?.trim()),
priority,
has_assignee: Boolean(assigneeType && assigneeId),
assignee_type: assigneeType ?? null,
has_due_date: Boolean(dueDate),
});
clearDraft();
onClose();
toast.custom((t) => (

View File

@@ -7,6 +7,7 @@ import { useInboxStore } from "@/features/inbox";
import { useRuntimeStore } from "@/features/runtimes";
import { toast } from "sonner";
import { api } from "@/shared/api";
import { track, registerSuperProperties, AnalyticsEvents } from "@/features/analytics";
import { createLogger } from "@/shared/logger";
const logger = createLogger("workspace-store");
@@ -94,11 +95,17 @@ export const useWorkspaceStore = create<WorkspaceStore>((set, get) => ({
logger.info("hydrate complete", "members:", nextMembers.length, "agents:", nextAgents.length);
set({ members: nextMembers, agents: nextAgents, skills: nextSkills });
registerSuperProperties({
workspace_id: nextWorkspace.id,
workspace_name: nextWorkspace.name,
});
return nextWorkspace;
},
switchWorkspace: async (workspaceId) => {
logger.info("switching to", workspaceId);
track(AnalyticsEvents.WORKSPACE_SWITCHED);
const { workspaces, hydrateWorkspace } = get();
const ws = workspaces.find((item) => item.id === workspaceId);
if (!ws) return;
@@ -201,6 +208,7 @@ export const useWorkspaceStore = create<WorkspaceStore>((set, get) => ({
createWorkspace: async (data) => {
const ws = await api.createWorkspace(data);
set((state) => ({ workspaces: [...state.workspaces, ws] }));
track(AnalyticsEvents.WORKSPACE_CREATED);
return ws;
},

View File

@@ -22,7 +22,6 @@
"@tiptap/extension-image": "^3.22.1",
"@tiptap/extension-link": "^3.22.1",
"@tiptap/extension-mention": "^3.22.1",
"@tiptap/suggestion": "^3.22.1",
"@tiptap/extension-placeholder": "^3.22.1",
"@tiptap/extension-table": "^3.22.1",
"@tiptap/extension-table-cell": "^3.22.1",
@@ -33,6 +32,7 @@
"@tiptap/pm": "^3.22.1",
"@tiptap/react": "^3.22.1",
"@tiptap/starter-kit": "^3.22.1",
"@tiptap/suggestion": "^3.22.1",
"@types/linkify-it": "^5.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -46,6 +46,7 @@
"lucide-react": "catalog:",
"next": "^16.1.6",
"next-themes": "^0.4.6",
"posthog-js": "^1.365.5",
"react": "catalog:",
"react-day-picker": "^9.14.0",
"react-dom": "catalog:",

305
pnpm-lock.yaml generated
View File

@@ -155,10 +155,13 @@ importers:
version: 1.0.1(react@19.2.3)
next:
specifier: ^16.1.6
version: 16.2.0(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
version: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
posthog-js:
specifier: ^1.365.5
version: 1.365.5
react:
specifier: 'catalog:'
version: 19.2.3
@@ -237,7 +240,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^4.1.0
version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@1.8.0))(msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.1(@types/node@25.5.0)(jiti@2.6.1))
version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@1.8.0))(msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.1(@types/node@25.5.0)(jiti@2.6.1))
packages:
@@ -839,6 +842,78 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@opentelemetry/api-logs@0.208.0':
resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==}
engines: {node: '>=8.0.0'}
'@opentelemetry/api@1.9.1':
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
engines: {node: '>=8.0.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.6.1':
resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.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/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-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/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.6.1':
resolution: {integrity: sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==}
engines: {node: ^18.19.0 || >=20.6.0}
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-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-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/semantic-conventions@1.40.0':
resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==}
engines: {node: '>=14'}
'@oxc-project/types@0.120.0':
resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==}
@@ -847,6 +922,42 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@posthog/core@1.25.2':
resolution: {integrity: sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg==}
'@posthog/types@1.365.5':
resolution: {integrity: sha512-BCdDP8AC9ruAelYWSjXFRAySY1KamLCE2KEMhdsgIjcExHzVPqwg+K5xyGR6KqBvAbXhKqtkS46pKGS8lJXZCg==}
'@protobufjs/aspromise@1.1.2':
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
'@protobufjs/base64@1.1.2':
resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
'@protobufjs/codegen@2.0.4':
resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
'@protobufjs/eventemitter@1.1.0':
resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
'@protobufjs/fetch@1.1.0':
resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
'@protobufjs/float@1.0.2':
resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
'@protobufjs/inquire@1.1.0':
resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
'@protobufjs/path@1.1.2':
resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
'@protobufjs/pool@1.1.0':
resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
'@protobufjs/utf8@1.1.0':
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
'@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
@@ -1616,6 +1727,9 @@ packages:
'@types/statuses@2.0.6':
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -1884,6 +1998,9 @@ packages:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
core-js@3.49.0:
resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==}
cors@2.8.6:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
@@ -2047,6 +2164,9 @@ packages:
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
dompurify@3.3.3:
resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==}
dotenv@17.3.1:
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
engines: {node: '>=12'}
@@ -2224,6 +2344,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==}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
@@ -2671,6 +2794,9 @@ packages:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -3148,10 +3274,16 @@ packages:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
posthog-js@1.365.5:
resolution: {integrity: sha512-BJw3cmBykQHY/TK82s8UsUyQPjjMPVsI7Wy2eZRXf+GxhLnHmXZj50bQAfAKlTLHv3qwDpld17bGKXC9zLwVhg==}
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
preact@10.29.1:
resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==}
pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -3225,6 +3357,10 @@ packages:
prosemirror-view@1.41.7:
resolution: {integrity: sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==}
protobufjs@7.5.4:
resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
engines: {node: '>=12.0.0'}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -3241,6 +3377,9 @@ packages:
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
engines: {node: '>=0.6'}
query-selector-shadow-dom@1.0.1:
resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -3879,6 +4018,9 @@ packages:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
web-vitals@5.2.0:
resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==}
webidl-conversions@8.0.1:
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
engines: {node: '>=20'}
@@ -4571,12 +4713,115 @@ snapshots:
'@open-draft/until@2.1.0': {}
'@opentelemetry/api-logs@0.208.0':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api@1.9.1': {}
'@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.1)
protobufjs: 7.5.4
'@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/semantic-conventions@1.40.0': {}
'@oxc-project/types@0.120.0': {}
'@playwright/test@1.58.2':
dependencies:
playwright: 1.58.2
'@posthog/core@1.25.2': {}
'@posthog/types@1.365.5': {}
'@protobufjs/aspromise@1.1.2': {}
'@protobufjs/base64@1.1.2': {}
'@protobufjs/codegen@2.0.4': {}
'@protobufjs/eventemitter@1.1.0': {}
'@protobufjs/fetch@1.1.0':
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/inquire': 1.1.0
'@protobufjs/float@1.0.2': {}
'@protobufjs/inquire@1.1.0': {}
'@protobufjs/path@1.1.2': {}
'@protobufjs/pool@1.1.0': {}
'@protobufjs/utf8@1.1.0': {}
'@radix-ui/primitive@1.1.3': {}
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.3)':
@@ -5275,6 +5520,9 @@ snapshots:
'@types/statuses@2.0.6': {}
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.11': {}
'@types/unist@3.0.3': {}
@@ -5512,6 +5760,8 @@ snapshots:
cookie@1.1.1: {}
core-js@3.49.0: {}
cors@2.8.6:
dependencies:
object-assign: 4.1.1
@@ -5639,6 +5889,10 @@ snapshots:
dom-accessibility-api@0.6.3: {}
dompurify@3.3.3:
optionalDependencies:
'@types/trusted-types': 2.0.7
dotenv@17.3.1: {}
dunder-proto@1.0.1:
@@ -5827,6 +6081,8 @@ snapshots:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
fflate@0.4.8: {}
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
@@ -6254,6 +6510,8 @@ snapshots:
chalk: 5.6.2
is-unicode-supported: 1.3.0
long@5.3.2: {}
longest-streak@3.1.0: {}
lowlight@3.3.0:
@@ -6710,7 +6968,7 @@ snapshots:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
next@16.2.0(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
'@next/env': 16.2.0
'@swc/helpers': 0.5.15
@@ -6729,6 +6987,7 @@ snapshots:
'@next/swc-linux-x64-musl': 16.2.0
'@next/swc-win32-arm64-msvc': 16.2.0
'@next/swc-win32-x64-msvc': 16.2.0
'@opentelemetry/api': 1.9.1
'@playwright/test': 1.58.2
sharp: 0.34.5
transitivePeerDependencies:
@@ -6934,8 +7193,26 @@ snapshots:
dependencies:
xtend: 4.0.2
posthog-js@1.365.5:
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@posthog/core': 1.25.2
'@posthog/types': 1.365.5
core-js: 3.49.0
dompurify: 3.3.3
fflate: 0.4.8
preact: 10.29.1
query-selector-shadow-dom: 1.0.1
web-vitals: 5.2.0
powershell-utils@0.1.0: {}
preact@10.29.1: {}
pretty-format@27.5.1:
dependencies:
ansi-regex: 5.0.1
@@ -7056,6 +7333,21 @@ snapshots:
prosemirror-state: 1.4.4
prosemirror-transform: 1.11.0
protobufjs@7.5.4:
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/base64': 1.1.2
'@protobufjs/codegen': 2.0.4
'@protobufjs/eventemitter': 1.1.0
'@protobufjs/fetch': 1.1.0
'@protobufjs/float': 1.0.2
'@protobufjs/inquire': 1.1.0
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0
'@types/node': 25.5.0
long: 5.3.2
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -7069,6 +7361,8 @@ snapshots:
dependencies:
side-channel: 1.1.0
query-selector-shadow-dom@1.0.1: {}
queue-microtask@1.2.3: {}
range-parser@1.2.1: {}
@@ -7755,7 +8049,7 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@1.8.0))(msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.1(@types/node@25.5.0)(jiti@2.6.1)):
vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@1.8.0))(msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.1(@types/node@25.5.0)(jiti@2.6.1)):
dependencies:
'@vitest/expect': 4.1.0
'@vitest/mocker': 4.1.0(msw@2.12.14(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.1(@types/node@25.5.0)(jiti@2.6.1))
@@ -7778,6 +8072,7 @@ snapshots:
vite: 8.0.1(@types/node@25.5.0)(jiti@2.6.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 25.5.0
jsdom: 29.0.1(@noble/hashes@1.8.0)
transitivePeerDependencies:
@@ -7793,6 +8088,8 @@ snapshots:
web-streams-polyfill@3.3.3: {}
web-vitals@5.2.0: {}
webidl-conversions@8.0.1: {}
whatwg-mimetype@5.0.0: {}