Skip to content

Conversation

@leoisadev8
Copy link
Member

Summary

  • Introduces comprehensive PostHog telemetry for server and web, including user/workspace identification, property registration, sanitized event payloads, and centralized bootstrap wiring. Adds event hooks across UI flows (chat, sidebar, OpenRouter) and server routes to track usage, errors, and feature flags.

Changes

Core Telemetry

  • Initialize a centralized PostHog client with BASE_SUPER_PROPERTIES (app, app_version, deployment, environment, deployment_region) on both server and web sides.
  • Sanitize event payloads by dropping undefined values before sending to PostHog.
  • Extend identifyClient to support a workspaceId and attach a workspace group plus register workspace-related properties.
  • Introduce a PostHog bootstrap component to wire identity, environment, and feature flags at app startup and to publish initial pageview data.
  • Provide generic helpers: captureClientEvent and registerClientProperties for consistent telemetry usage across the codebase.

Backend Telemetry

  • books/apps/server/src/lib/posthog.ts: added environment-aware APP_VERSION, DEPLOYMENT, ENVIRONMENT, and DEPLOYMENT_REGION, and registered BASE_SUPER_PROPERTIES on the client. Ensure sanitized properties on capture.
  • books/apps/web/src/lib/posthog-server.ts: parallel server-side telemetry setup for web context with similar properties.

Client Telemetry (UI)

  • account-settings-modal.tsx: log OpenRouter key save/remove events and update client properties to reflect key presence.
  • app-sidebar.tsx: identify user on mount, log dashboard entry, log chat creation (including storage backend), and register model/workspace properties.
  • chat-composer.tsx: log attachment events (rejected/accepted) and associate with chatId when available.
  • chat-room.tsx: identify user on room load; log OpenRouter model fetch failures; log key saves; log chat streaming lifecycle, rate limits, and workspace tagging; pass chatId to child components for richer telemetry.
  • hero-section.tsx: track CTA clicks and marketing visit data (landing visits, referrer, UTM, entry path).
  • model-selector.tsx: track model_id selections and update client properties accordingly.
  • openrouter-link-modal.tsx: track key prompt shown events (missing/error) and hasApiKey state.
  • posthog-bootstrap.tsx (new): central bootstrap hook to initialize identity, workspace grouping, theme, and presence of an OpenRouter key.
  • providers.tsx: render PostHogBootstrap and emit an initial pageview with referrer, entry path, and related metadata.

OpenRouter / Key Storage Telemetry

  • Track key prompt visibility and key save/remove events; log key presence consistently across flows.

Misc / Utilities

  • web/src/lib/posthog.ts: enhanced typings (IdentifyOptions) and helpers (registerClientProperties, identifyClient with workspaceId) to support richer client profiling.
  • web/src/app/api/chat/chat-handler.ts: direction for rate limit telemetry and sanitized event payloads during chat streams.
  • Added new posthog bootstrap component and wired it into Providers for automatic telemetry wiring on app load.

Observability & Data Quality

  • All telemetry payloads sanitize undefined values to avoid noisy data.
  • Workspace-scoped properties (workspace_id, auth_state) are consistently attached to events where applicable.

Verification / Test Plan

  • Trigger rate-limited paths (429) and verify chat.rate_limited events contain limit and window values.
  • Create new chats via server and confirm events chat.created and storage_backend are emitted, including fallback paths when PostHog backend is unavailable.
  • Attach files in ChatComposer and verify chat.attachment_event events for both accepted and rejected files with relevant metadata.
  • Open a chat and validate openrouter.models_fetch_failed events when model loading fails, including provider_host and api_key state.
  • Ensure initial pageview is captured on load with referrer data (direct/landing flow) via $pageview, populated by Providers bootstrap.
  • Verify identify calls attach workspace_id and auth_state, and that registerClientProperties reflects these attributes.
  • Validate UI interactions (CTA clicks, model selections) propagate telemetry without errors.

If any telemetry appears missing in dev, confirm that POSTHOG_API_KEY and host are configured, and that the new bootstrap and client helpers are loaded on the respective pages.

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/f9f3d737-4316-4784-b610-0c0cd5c6b386

- Add base super properties and sanitize event properties for client and server PostHog usage
- Track fallback storage usage in server router for chats and messages
- Add detailed client event tracking in web chat handler and components
- Introduce PosthogBootstrap component for consistent client identification and property registration
- Enhance event tracking with user workspace IDs, auth states, theme info, and OpenRouter API key status
- Track marketing CTA clicks and landing visits with detailed metadata
- Improve rate limit and error event captures for chat and OpenRouter APIs
- Add client property registration on model selection and OpenRouter key management
- Add sync connection state events to PostHog

This update improves analytics granularity and consistency across client and server, enabling better user behavior insights and troubleshooting.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leoisadev8
Copy link
Member Author

@greptile what custom events were added?

@leoisadev8 leoisadev8 marked this pull request as ready for review October 16, 2025 01:33
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

This PR implements comprehensive PostHog telemetry across the OpenChat application, adding observability to both server and web components. The implementation introduces centralized telemetry infrastructure with environment-aware super properties, property sanitization, and workspace-scoped tracking. Key additions include a PostHogBootstrap component for initialization, enhanced user identification with workspace grouping, and detailed event tracking across UI flows (chat interactions, model selection, file attachments) and server operations (chat creation, rate limiting, storage fallback scenarios). The telemetry system captures user behavior patterns, system performance metrics, and error conditions while maintaining data quality through undefined value sanitization and privacy through IP hashing.

Important Files Changed

Changed Files
Filename Score Overview
apps/web/src/lib/sync.ts 4/5 Added telemetry for WebSocket connection state tracking with retry patterns
apps/server/src/lib/posthog.ts 4/5 Implemented comprehensive server-side PostHog infrastructure with sanitization
apps/web/src/components/openrouter-link-modal.tsx 5/5 Added telemetry for API key prompt events with deduplication logic
apps/web/src/lib/posthog.ts 4/5 Enhanced client telemetry with workspace grouping and property utilities
apps/web/src/components/posthog-bootstrap.tsx 4/5 New centralized bootstrap component for telemetry initialization
apps/server/src/routers/index.ts 4/5 Added comprehensive telemetry across chat operations and fallback scenarios
apps/web/src/lib/posthog-server.ts 4/5 Server-side telemetry setup with potential naming inconsistency
apps/web/src/components/chat-composer.tsx 4/5 Added file attachment event tracking with metadata
apps/web/src/components/account-settings-modal.tsx 5/5 Implemented telemetry for OpenRouter key management events
apps/web/src/app/api/chat/chat-handler.ts 4/5 Added comprehensive chat API telemetry with IP hashing for privacy
apps/web/src/components/app-sidebar.tsx 4/5 Enhanced with user identification and dashboard engagement tracking
apps/web/src/components/providers.tsx 4/5 Improved pageview tracking with referrer analysis and bootstrap integration
apps/web/src/components/model-selector.tsx 4/5 Added model selection tracking for user preference analytics
apps/web/src/components/chat-room.tsx 4/5 Comprehensive telemetry for chat interactions and error conditions
apps/web/src/components/hero-section.tsx 4/5 Converted to client component with marketing analytics tracking

Confidence score: 4/5

  • This PR is safe to merge with minimal risk as it primarily adds observability features without changing core application logic
  • Score reflects comprehensive telemetry implementation with good error handling patterns, though some files have minor concerns like potential naming inconsistencies and the conversion of server components to client components
  • Pay close attention to apps/web/src/lib/posthog-server.ts for the "openchat-server" naming in web context and apps/web/src/components/hero-section.tsx for the server-to-client component conversion impact

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant PosthogBootstrap
    participant WebApp
    participant ServerAPI
    participant PosthogCloud
    
    User->>Browser: "Visit OpenChat"
    Browser->>PosthogBootstrap: "Initialize on page load"
    PosthogBootstrap->>PosthogCloud: "capture('$pageview')"
    PosthogBootstrap->>PosthogBootstrap: "identifyClient(userId, workspaceId)"
    PosthogBootstrap->>PosthogBootstrap: "registerClientProperties(auth_state, theme)"
    
    User->>WebApp: "Click 'New Chat'"
    WebApp->>ServerAPI: "client.chats.create()"
    ServerAPI->>PosthogCloud: "capturePosthogEvent('chat.created')"
    WebApp->>PosthogCloud: "captureClientEvent('chat.created')"
    
    User->>WebApp: "Send chat message"
    WebApp->>ServerAPI: "POST /api/chat"
    ServerAPI->>ServerAPI: "isRateLimited(request)"
    alt Rate limited
        ServerAPI->>PosthogCloud: "captureServerEvent('chat.rate_limited')"
        ServerAPI->>WebApp: "429 Too Many Requests"
    else Not rate limited
        ServerAPI->>ServerAPI: "streamText(model, messages)"
        ServerAPI->>PosthogCloud: "captureServerEvent('chat_message_stream')"
    end
    
    User->>WebApp: "Save OpenRouter key"
    WebApp->>WebApp: "saveOpenRouterKey(key)"
    WebApp->>PosthogCloud: "captureClientEvent('openrouter.key_saved')"
    WebApp->>PosthogCloud: "registerClientProperties({has_openrouter_key: true})"
    
    User->>WebApp: "Attach file to chat"
    alt File too large
        WebApp->>PosthogCloud: "captureClientEvent('chat.attachment_event', {result: 'rejected'})"
    else File accepted
        WebApp->>PosthogCloud: "captureClientEvent('chat.attachment_event', {result: 'accepted'})"
    end
    
    User->>WebApp: "Select model"
    WebApp->>PosthogCloud: "registerClientProperties({model_id: selectedModel})"
    
    alt OpenRouter models fetch fails
        WebApp->>PosthogCloud: "captureClientEvent('openrouter.models_fetch_failed')"
    end
    
    User->>WebApp: "Click marketing CTA"
    WebApp->>PosthogCloud: "captureClientEvent('marketing.cta_clicked')"
Loading

Context used:

  • Context from dashboard - AGENTS.md (source)

15 files reviewed, 10 comments

Edit Code Review Agent Settings | Greptile


useEffect(() => {
if (!resolvedWorkspaceId) return;
if (identifyRef.current === resolvedWorkspaceId && session?.user) return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The deduplication logic may miss re-identification when session state changes from guest to member with the same workspace ID

process.env.POSTHOG_DEPLOYMENT_REGION ?? process.env.VERCEL_REGION ?? "local";

const BASE_SUPER_PROPERTIES = Object.freeze({
app: "openchat-server",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The app property is set to 'openchat-server' but this file is in apps/web. Consider using 'openchat-web' to match the web context.

Comment on lines +121 to +129
identifyClient(currentUserId, {
workspaceId: currentUserId,
properties: { auth_state: session?.user ? "member" : "guest" },
});
registerClientProperties({
auth_state: session?.user ? "member" : "guest",
workspace_id: currentUserId,
});
}, [currentUserId, session?.user]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: User identification runs on every session change, potentially causing redundant PostHog calls. Consider adding a check to only identify when user ID or auth state actually changes.

try {
const now = new Date();
const { id } = await client.chats.create({ title: "New Chat" });
const { id, storageBackend = "postgres" } = await client.chats.create({ title: "New Chat" });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The storageBackend destructuring assumes the server response includes this field, but there's no type safety or validation. If the server doesn't return storageBackend, it will default to 'postgres' which may not be accurate.

Comment on lines +273 to +295
const dashboardTrackedRef = useRef(false);
useEffect(() => {
if (dashboardTrackedRef.current) return;
if (isLoading) return;
dashboardTrackedRef.current = true;
void (async () => {
let hasKey = false;
try {
const key = await loadOpenRouterKey();
hasKey = Boolean(key);
registerClientProperties({ has_openrouter_key: hasKey });
} catch {
hasKey = false;
}
const entryPath = typeof window !== "undefined" ? window.location.pathname || "/dashboard" : "/dashboard";
captureClientEvent("dashboard.entered", {
chat_total: baseChats.length,
has_api_key: hasKey,
entry_path: entryPath,
brand_theme: brandTheme,
});
})();
}, [baseChats.length, brandTheme, isLoading]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Dashboard tracking logic is complex and could be extracted into a custom hook for better testability and reusability across components.

if (client) {
setPosthogClient(client);
client.capture("$pageview");
const referrerUrl = document.referrer && document.referrer.length > 0 ? document.referrer : "direct";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using optional chaining: document.referrer?.length > 0 for cleaner syntax

Comment on lines +94 to +97
React.useEffect(() => {
if (!selectedValue) return
registerClientProperties({ model_id: selectedValue })
}, [selectedValue])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: registering client properties on every selectedValue change, including initial load, could create noise in analytics if models are frequently switched or reset programmatically

setInternalValue(currentValue)
}
onChange?.(currentValue)
registerClientProperties({ model_id: currentValue })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: redundant registerClientProperties call since the useEffect on line 96 will trigger when selectedValue changes after this selection

Comment on lines +189 to +194
registerClientProperties({ has_openrouter_key: true });
captureClientEvent("openrouter.key_saved", {
source: "modal",
masked_tail: key.slice(-4),
scope: "workspace",
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Logging masked API key tail could be a privacy risk - consider if this level of detail is necessary for analytics


useEffect(() => {
if (visitTrackedRef.current) return
if (typeof session === 'undefined') return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Session loading state check may cause telemetry to miss some visits if session loads after component mount

@leoisadev8
Copy link
Member Author

Superseded by #57 (feat: telemetry and security hardening) now on main.

@leoisadev8 leoisadev8 closed this Oct 19, 2025
@leoisadev8 leoisadev8 deleted the codex/implement-posthog-features-qkjs37 branch November 2, 2025 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants