From 76921b8ddb6d9d8de284e8b5df095660b67b2bf5 Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Sun, 30 Aug 2026 20:43:34 +0530 Subject: [PATCH] feat: wire PostHog analytics - Add posthog-js to package.json dependencies - Add VITE_POSTHOG_KEY + VITE_POSTHOG_HOST to .env.production - Create src/lib/posthog.ts: init, identifyUser, resetIdentity helpers - Call initPostHog() at app startup in main.tsx - Identify user on auth state change, reset on logout in useAuth.ts --- .devin/skills/noterm/SKILL.md | 21 ++++++++++++++++++++ .env.example | 6 ++++++ .env.production | 4 ++++ .windsurf/workflows/noterm.md | 19 ++++++++++++++++++ package.json | 1 + src/auth/useAuth.ts | 8 ++++++++ src/lib/posthog.ts | 37 +++++++++++++++++++++++++++++++++++ src/main.tsx | 3 +++ 8 files changed, 99 insertions(+) create mode 100644 .devin/skills/noterm/SKILL.md create mode 100644 .windsurf/workflows/noterm.md create mode 100644 src/lib/posthog.ts diff --git a/.devin/skills/noterm/SKILL.md b/.devin/skills/noterm/SKILL.md new file mode 100644 index 0000000..953aa31 --- /dev/null +++ b/.devin/skills/noterm/SKILL.md @@ -0,0 +1,21 @@ +# /noterm — Prevent Session Termination + +**Trigger**: User types `/noterm` in any message. + +## Behavior + +When this skill is invoked, reinforce the following rule for the remainder of the session: + +1. **NEVER** terminate or end the session on your own. +2. After completing any task, use the `ask_user_question` tool to confirm whether the user considers the work done. +3. Do **not** produce a final summary and stop — always ask for next steps. +4. If you are unsure whether the user wants to continue, ask. +5. Treat every completed task as potentially having follow-up work. + +## Response + +After invoking this skill, acknowledge briefly: + +> Session termination guard is active. I will not end this session until you explicitly confirm we're done. + +Then continue with whatever the user asked in the same message (if anything). diff --git a/.env.example b/.env.example index 094c788..821fdc6 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,9 @@ VITE_IAM_DESKTOP_REDIRECT_URI=app://bundle/callback # Google Identity Services (optional — enables native one-tap sign-in) VITE_GOOGLE_CLIENT_ID=your-google-oauth2-client-id.apps.googleusercontent.com + +# ── PostHog Analytics ────────────────────────────────────────────────────────── +# Public project token (safe to commit) — from analytics.armco.dev +VITE_POSTHOG_KEY=phc_your_project_token +# Self-hosted PostHog host +VITE_POSTHOG_HOST=https://analytics.armco.dev diff --git a/.env.production b/.env.production index c22f0a5..246f0d6 100644 --- a/.env.production +++ b/.env.production @@ -5,3 +5,7 @@ VITE_IAM_ISSUER=https://iam.armco.dev VITE_IAM_AUDIENCE=stuffle-api VITE_IAM_DESKTOP_CLIENT_ID=client_6a243d0c127843ff8f2f2a28a486638f VITE_IAM_DESKTOP_REDIRECT_URI=app://bundle/callback + +# ── PostHog Analytics ────────────────────────────────────────────────────────── +VITE_POSTHOG_KEY=phc_xNzMtXgsxmu74yTncZZuEJMACvdAjSfV3jMKaWAGvj5v +VITE_POSTHOG_HOST=https://analytics.armco.dev diff --git a/.windsurf/workflows/noterm.md b/.windsurf/workflows/noterm.md new file mode 100644 index 0000000..5425fc8 --- /dev/null +++ b/.windsurf/workflows/noterm.md @@ -0,0 +1,19 @@ +--- +description: Prevent session termination - keep working until user confirms done +--- + +## /noterm — Session Termination Guard + +When invoked, enforce the following for the remainder of the session: + +1. NEVER terminate or end the session on your own. +2. After completing any task, ask the user whether they consider the work done. +3. Do not produce a final summary and stop — always ask for next steps. +4. If unsure whether the user wants to continue, ask. +5. Treat every completed task as potentially having follow-up work. + +Acknowledge briefly: + +> Session termination guard is active. I will not end this session until you explicitly confirm we're done. + +Then continue with whatever the user asked in the same message (if anything). diff --git a/package.json b/package.json index 48c66b3..646d5a9 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "lowlight": "^3.3.0", "lucide-react": "^0.577.0", "marked": "^17.0.6", + "posthog-js": "^1.250.0", "prosemirror-search": "^1.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", diff --git a/src/auth/useAuth.ts b/src/auth/useAuth.ts index ee16e78..1eb3671 100644 --- a/src/auth/useAuth.ts +++ b/src/auth/useAuth.ts @@ -16,6 +16,7 @@ import { createContext, createElement, useContext, useEffect, useMemo, useState, import type { User } from '@armco/iam-client' import { iam } from './iam' import { shutdown as wsShutdown } from '@/services/ws.service' +import { identifyUser, resetIdentity } from '@/lib/posthog' import { DEV_BYPASS_ACTIVE } from './dev-bypass' // re-exported below for consumers import { installAuthDebug } from './authDebug' @@ -138,6 +139,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { const nextState = await resolveAuthState(authState.isAuthenticated) if (cancelled) return setState(nextState) + if (nextState.user) { + identifyUser(nextState.user.sub, { + email: nextState.user.email, + name: nextState.user.name, + }) + } })() }) @@ -173,6 +180,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { ...state, logout: () => { if (DEV_BYPASS_ACTIVE) return + resetIdentity() wsShutdown() iam.logout({ returnTo: window.location.origin }) }, diff --git a/src/lib/posthog.ts b/src/lib/posthog.ts new file mode 100644 index 0000000..e27354b --- /dev/null +++ b/src/lib/posthog.ts @@ -0,0 +1,37 @@ +import posthog from 'posthog-js' + +const POSTHOG_KEY = import.meta.env.VITE_POSTHOG_KEY as string | undefined +const POSTHOG_HOST = import.meta.env.VITE_POSTHOG_HOST as string | undefined + +let _initialised = false + +export function initPostHog(): void { + if (_initialised || !POSTHOG_KEY || !POSTHOG_HOST) return + _initialised = true + posthog.init(POSTHOG_KEY, { + api_host: POSTHOG_HOST, + ui_host: POSTHOG_HOST, + person_profiles: 'identified_only', + capture_pageview: true, + capture_pageleave: true, + session_recording: { + maskAllInputs: false, + maskInputOptions: { password: true }, + }, + loaded: (ph) => { + if (import.meta.env.DEV) ph.debug() + }, + }) +} + +export function identifyUser(userId: string, traits?: Record): void { + if (!_initialised) return + posthog.identify(userId, traits) +} + +export function resetIdentity(): void { + if (!_initialised) return + posthog.reset() +} + +export { posthog } diff --git a/src/main.tsx b/src/main.tsx index 051dc7f..9742139 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,9 +4,12 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ThemeProvider } from '@/theme' import { ShortlistProvider } from '@/context/ShortlistContext' import { AuthProvider } from '@/auth/useAuth' +import { initPostHog } from '@/lib/posthog' import './index.css' import App from './App' +initPostHog() + /* ── Global scroll-reveal scrollbar ──────────────────────────────────────── Adds `.ag-scrolling` to any element while it scrolls, removes after 2 s of idle. The CSS in index.css transitions the scrollbar thumb via this