mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-26 17:09:14 +02:00
Compare commits
3 Commits
agent/lamb
...
agent/j/94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9bf076eac | ||
|
|
a47113173f | ||
|
|
fde3afe289 |
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
61
apps/web/features/analytics/events.ts
Normal file
61
apps/web/features/analytics/events.ts
Normal 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;
|
||||
9
apps/web/features/analytics/index.ts
Normal file
9
apps/web/features/analytics/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { AnalyticsProvider } from "./provider";
|
||||
export { AnalyticsEvents } from "./events";
|
||||
export {
|
||||
track,
|
||||
identifyUser,
|
||||
resetUser,
|
||||
registerSuperProperties,
|
||||
isAnalyticsEnabled,
|
||||
} from "./posthog";
|
||||
64
apps/web/features/analytics/posthog.ts
Normal file
64
apps/web/features/analytics/posthog.ts
Normal 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);
|
||||
}
|
||||
38
apps/web/features/analytics/provider.tsx
Normal file
38
apps/web/features/analytics/provider.tsx
Normal 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}</>;
|
||||
}
|
||||
@@ -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 });
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
|
||||
@@ -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
305
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
Reference in New Issue
Block a user