Skip to content

chore(main): release 2.38.0#785

Open
releaser-wizard[bot] wants to merge 1 commit into
mainfrom
release-please--branches--main--components--wizard
Open

chore(main): release 2.38.0#785
releaser-wizard[bot] wants to merge 1 commit into
mainfrom
release-please--branches--main--components--wizard

Conversation

@releaser-wizard

@releaser-wizard releaser-wizard Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🤖 I have created a release beep boop

2.38.0 (2026-07-03)

Features

  • pi: real PostHog MCP dashboard, env lockdown, perf parity (#701) (9730dfd)
  • tui: Hidden Ctrl+T HUD showing running/final LLM token cost (#783) (b7ef4ba)

Bug Fixes

  • tui: disable crashing Visualizer tab (#787) (18bd38e)

This PR was generated with Release Please. See documentation.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci basic-integration
  • /wizard-ci error-tracking-upload-source-maps
  • /wizard-ci mcp-analytics
  • /wizard-ci misc
  • /wizard-ci revenue

Test an individual app:

  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
Show more apps
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci error-tracking-upload-source-maps/android
  • /wizard-ci error-tracking-upload-source-maps/cicd-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-nested-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-single-stage-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-gitlab-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-monorepo-pnpm-node-react
  • /wizard-ci error-tracking-upload-source-maps/cicd-monorepo-raw-node-react
  • /wizard-ci error-tracking-upload-source-maps/cicd-ssh-vps-node-raw
  • /wizard-ci error-tracking-upload-source-maps/flutter
  • /wizard-ci error-tracking-upload-source-maps/ios
  • /wizard-ci error-tracking-upload-source-maps/next
  • /wizard-ci error-tracking-upload-source-maps/next-no-posthog
  • /wizard-ci error-tracking-upload-source-maps/node-raw
  • /wizard-ci error-tracking-upload-source-maps/node-rollup
  • /wizard-ci error-tracking-upload-source-maps/node-rollup-typescript-plugin
  • /wizard-ci error-tracking-upload-source-maps/node-webpack
  • /wizard-ci error-tracking-upload-source-maps/nuxt-3-6
  • /wizard-ci error-tracking-upload-source-maps/nuxt-4-3
  • /wizard-ci error-tracking-upload-source-maps/react-native
  • /wizard-ci error-tracking-upload-source-maps/react-vite
  • /wizard-ci error-tracking-upload-source-maps/rust
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci misc/quack-quack
  • /wizard-ci revenue/stripe

Results will be posted here when complete.

@releaser-wizard releaser-wizard Bot force-pushed the release-please--branches--main--components--wizard branch from b67b152 to cb9b769 Compare July 2, 2026 03:45
@releaser-wizard releaser-wizard Bot force-pushed the release-please--branches--main--components--wizard branch from cb9b769 to 7188a5b Compare July 3, 2026 00:03
@gewenyu99

Copy link
Copy Markdown
Collaborator

/wizard-ci all

@wizard-ci-bot

wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 8dccaac
Workflow: View run

App Confidence PR YARA
audit/DemoHog3001 N/A Failed (logs)
audit/cal-diy N/A Failed (logs)
audit/dub N/A Failed (logs)
audit/posthog-demo-3000 N/A Failed (logs)
basic-integration/android/Jetchat 3/5 #2343 (logs)
basic-integration/angular/angular-saas 4/5 #2344 (logs)
basic-integration/astro/astro-hybrid-marketing 4/5 #2339 (logs)
basic-integration/astro/astro-ssr-docs 4/5 #2340 (logs)
basic-integration/astro/astro-static-marketing 5/5 #2341 (logs)
basic-integration/astro/astro-view-transitions-marketing 4/5 #2342 (logs)
basic-integration/django/django3-saas 4/5 #2345 (logs)
basic-integration/fastapi/fastapi3-ai-saas 5/5 #2354 (logs)
basic-integration/flask/flask3-social-media 4/5 #2352 (logs)
basic-integration/javascript-node/express-todo 4/5 #2347 (logs)
basic-integration/javascript-node/fastify-blog 4/5 #2346 (logs)
basic-integration/javascript-node/hono-links 4/5 #2349 (logs)
basic-integration/javascript-node/koa-notes 4/5 #2348 (logs)
basic-integration/javascript-node/native-http-contacts 4/5 #2350 (logs)
basic-integration/javascript-web/saas-dashboard 5/5 #2351 (logs)
basic-integration/laravel/laravel12-saas 3/5 #2353 (logs)
basic-integration/next-js/15-app-router-saas 4/5 #2356 (logs)
basic-integration/next-js/15-app-router-todo 4/5 #2355 (logs)
basic-integration/next-js/15-pages-router-saas 4/5 #2359 (logs)
basic-integration/next-js/15-pages-router-todo 4/5 #2357 (logs)
basic-integration/nuxt/movies-nuxt-3-6 4/5 #2361 (logs)
basic-integration/nuxt/movies-nuxt-4 4/5 #2358 (logs)
basic-integration/python/meeting-summarizer 4/5 #2360 (logs)
basic-integration/rails/fizzy 4/5 #2366 (logs) ⚠️
basic-integration/react-native/expo-react-native-hacker-news 4/5 #2363 (logs)
basic-integration/react-native/react-native-saas 4/5 #2362 (logs) ⚠️
basic-integration/react-router/react-router-v7-project 4/5 #2364 (logs) ⚠️
basic-integration/react-router/rrv7-starter 4/5 #2365 (logs)
basic-integration/react-router/saas-template 4/5 #2373 (logs)
basic-integration/react-router/shopper 4/5 #2367 (logs)
basic-integration/sveltekit/CMSaasStarter 4/5 #2370 (logs) ⚠️
basic-integration/swift/hackers-ios 5/5 #2375 (logs)
basic-integration/tanstack-router/tanstack-router-code-based-saas 4/5 #2369 (logs)
basic-integration/tanstack-router/tanstack-router-file-based-saas 4/5 #2372 (logs)
basic-integration/tanstack-start/tanstack-start-saas 4/5 #2377 (logs)
basic-integration/vue/movies 4/5 #2371 (logs)
mcp-analytics/custom-dispatcher/hono-server 5/5 #2368 (logs)
mcp-analytics/typescript-sdk/stdio-server 5/5 #2374 (logs)
migrate/triplex N/A Failed (logs)
revenue/stripe/stripe-next-js-saas-starter 3/5 #2376 (logs)
revenue/stripe/stripe-saas-demo 4/5 #2378 (logs)
self-driving/turbo-monorepo N/A Failed (logs)

Configuration

Setting Value
Wizard ref release-please--branches--main--components--wizard
Context Mill ref main
PostHog ref master

Search for trigger ID 8dccaac in wizard-workbench PRs.

⚠️ YARA Scanner — basic-integration/react-native/react-native-saas
45 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Write
⚠️ YARA Scanner — basic-integration/react-router/react-router-v7-project
65 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit
⚠️ YARA Scanner — basic-integration/rails/fizzy
95 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit
⚠️ YARA Scanner — basic-integration/sveltekit/CMSaasStarter
80 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit

@gewenyu99

gewenyu99 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: umami

umami — production web-analytics product, Next.js App Router. Ran the built 2.38.0 wizard (this release-please branch) locally, headless, snapshotting every screen. Evaluated the diff myself against the workbench rubric.

Run: 28 frames · runPhase: completed · intro → auth → run → outro → mcp → slack → keep-skills · diff 17 files, +122 / −3

Evaluation (workbench rubric)

Dimension Verdict
File analysis ✅ correct files & locations, clean additive code
PostHog implementation ⚠️ client+server init, 3-rule reverse proxy, identify (client and server), capture_exceptions all correct — but the SDK was never added to package.json
Event quality ✅ 12 real product events (website_created, team_joined, funnel_report_saved, server_login, …), enriched props (username, role), no PII dumps
App sanity won't build — imports posthog-js/posthog-node in 14 files, but neither dependency is in package.json

Confidence: 3/5. Thorough on a real codebase — identify on both sides, a correct 3-rule /ingest reverse proxy, 12 meaningful events, a written posthog-setup-report.md. Undercut by one build-breaking miss: imports the SDKs but never declares them, so pnpm install && next build fails. A 4/5 with the deps added.

📸 Snapshots — all 28 frames

01-intro

01-intro

02-auth

02-auth

03-run

03-run

04-run

04-run

05-run

05-run

06-run

06-run

07-run

07-run

08-run

08-run

09-run

09-run

10-run

10-run

11-run

11-run

12-run

12-run

13-run

13-run

14-run

14-run

15-run

15-run

16-run

16-run

17-run

17-run

18-run

18-run

19-run

19-run

20-run

20-run

21-run

21-run

22-run

22-run

23-run

23-run

24-run

24-run

25-outro

25-outro

26-mcp

26-mcp

27-slack-connect

27-slack-connect

28-keep-skills

28-keep-skills

🔧 Full diff (17 files, +122/−3)
diff --git a/.gitignore b/.gitignore
index 7ac75d6..c6713b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,4 @@ yarn-error.log*
 *.env.*
 
 *.dev.yml
+.env.local
diff --git a/instrumentation-client.ts b/instrumentation-client.ts
new file mode 100644
index 0000000..b8ebf26
--- /dev/null
+++ b/instrumentation-client.ts
@@ -0,0 +1,9 @@
+import posthog from 'posthog-js';
+
+posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
+  api_host: '/ingest',
+  ui_host: 'https://us.posthog.com',
+  defaults: '2026-01-30',
+  capture_exceptions: true,
+  debug: process.env.NODE_ENV === 'development',
+});
diff --git a/next.config.ts b/next.config.ts
index 922d128..0218b68 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -246,6 +246,18 @@ export default withNextIntl({
   async rewrites() {
     return [
       ...rewrites,
+      {
+        source: '/ingest/static/:path*',
+        destination: 'https://us-assets.i.posthog.com/static/:path*',
+      },
+      {
+        source: '/ingest/array/:path*',
+        destination: 'https://us-assets.i.posthog.com/array/:path*',
+      },
+      {
+        source: '/ingest/:path*',
+        destination: 'https://us.i.posthog.com/:path*',
+      },
       {
         source: '/telemetry.js',
         destination: '/api/scripts/telemetry',
@@ -256,6 +268,7 @@ export default withNextIntl({
       },
     ];
   },
+  skipTrailingSlashRedirect: true,
   async redirects() {
     return [...redirects];
   },
diff --git a/posthog-setup-report.md b/posthog-setup-report.md
new file mode 100644
index 0000000..1c5c41a
--- /dev/null
+++ b/posthog-setup-report.md
@@ -0,0 +1,41 @@
+# PostHog post-wizard report
+
+The wizard has completed a deep integration of PostHog analytics into Umami. The setup covers client-side initialization via `instrumentation-client.ts` (Next.js 15.3+ pattern), a shared server-side PostHog client in `src/lib/posthog-server.ts`, a reverse proxy configured in `next.config.ts` to route PostHog traffic through `/ingest`, user identification on login (both client-side and server-side), and 12 instrumented events across key user flows.
+
+| Event | Description | File |
+|---|---|---|
+| `user_logged_in` | Fired when a user successfully logs in to Umami. | `src/app/login/LoginForm.tsx` |
+| `user_logged_out` | Fired when a user logs out of Umami. | `src/app/logout/LogoutPage.tsx` |
+| `website_created` | Fired when a user successfully adds a new website to track. | `src/app/(main)/websites/WebsiteAddForm.tsx` |
+| `website_deleted` | Fired when a user permanently deletes a tracked website. | `src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx` |
+| `website_updated` | Fired when a user saves changes to a website's settings. | `src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx` |
+| `website_reset` | Fired when a user resets all analytics data for a website. | `src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx` |
+| `team_created` | Fired when a user creates a new team. | `src/app/(main)/teams/TeamAddForm.tsx` |
+| `team_joined` | Fired when a user joins an existing team using an access code. | `src/app/(main)/teams/TeamJoinForm.tsx` |
+| `board_created` | Fired when a user creates a new custom dashboard board. | `src/app/(main)/boards/BoardEditForm.tsx` |
+| `password_changed` | Fired when a user successfully changes their account password. | `src/app/(main)/settings/profile/PasswordEditForm.tsx` |
+| `funnel_report_saved` | Fired when a user saves a funnel report configuration. | `src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx` |
+| `server_login` | Server-side event capturing each login with user identity on the authentication API. | `src/app/api/auth/login/route.ts` |
+
+## Next steps
+
+We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented:
+
+- [Analytics basics (wizard) dashboard](https://us.posthog.com/project/228144/dashboard/1793612)
+- [Daily logins](https://us.posthog.com/project/228144/insights/XnyFVrzP)
+- [Websites created vs deleted](https://us.posthog.com/project/228144/insights/wHP9LAcM)
+- [Team activity — created & joined](https://us.posthog.com/project/228144/insights/GPcKhHwU)
+- [Feature adoption — boards & funnel reports](https://us.posthog.com/project/228144/insights/JLqLxknE)
+- [Website churn rate (deleted / created)](https://us.posthog.com/project/228144/insights/2RqYjMyh)
+
+## Verify before merging
+
+- [ ] Run a full production build (the wizard only verified the files it touched) and fix any lint or type errors introduced by the generated code.
+- [ ] Run the test suite — call sites that were rewritten or instrumented may need updated mocks or fixtures.
+- [ ] Add `NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN` and `NEXT_PUBLIC_POSTHOG_HOST` to `.env.example` and any monorepo/bootstrap scripts so collaborators know what to set.
+- [ ] Wire source-map upload (`posthog-cli sourcemap` or your bundler's upload step) into CI so production stack traces de-minify.
+- [ ] Confirm the returning-visitor path also calls `identify` — `LoginForm.tsx` identifies on fresh login, but returning sessions that skip login (token still valid) will remain on anonymous distinct IDs until the next explicit login.
+
+### Agent skill
+
+We've left an agent skill folder in your project. You can use this context for further agent development when using Claude Code. This will help ensure the model provides the most up-to-date approaches for integrating PostHog.
diff --git a/src/app/(main)/boards/BoardEditForm.tsx b/src/app/(main)/boards/BoardEditForm.tsx
index ec405ff..bba62aa 100644
--- a/src/app/(main)/boards/BoardEditForm.tsx
+++ b/src/app/(main)/boards/BoardEditForm.tsx
@@ -10,6 +10,7 @@ import {
   Select,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useBoardQuery, useMessages, useNavigation, useUpdateQuery } from '@/components/hooks';
 import { LinkSelect } from '@/components/input/LinkSelect';
 import { PixelSelect } from '@/components/input/PixelSelect';
@@ -75,6 +76,9 @@ export function BoardEditForm({
       parameters: setBoardEntity(board?.parameters, data.type, data.entityId || undefined),
     });
 
+    if (!boardId) {
+      posthog.capture('board_created', { board_id: result.id, board_name: data.name, board_type: data.type });
+    }
     toast(t(messages.saved));
     touch('boards');
     touch(`board:${result.id}`);
diff --git a/src/app/(main)/settings/profile/PasswordEditForm.tsx b/src/app/(main)/settings/profile/PasswordEditForm.tsx
index 8a1615a..2acf070 100644
--- a/src/app/(main)/settings/profile/PasswordEditForm.tsx
+++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   PasswordField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
 export function PasswordEditForm({ onSave, onClose }) {
@@ -15,6 +16,7 @@ export function PasswordEditForm({ onSave, onClose }) {
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
       onSuccess: async () => {
+        posthog.capture('password_changed');
         onSave();
         onClose();
       },
diff --git a/src/app/(main)/teams/TeamAddForm.tsx b/src/app/(main)/teams/TeamAddForm.tsx
index 2599baa..9b904c1 100644
--- a/src/app/(main)/teams/TeamAddForm.tsx
+++ b/src/app/(main)/teams/TeamAddForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { UserSelect } from '@/components/input/UserSelect';
 
@@ -23,7 +24,8 @@ export function TeamAddForm({
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (team: any) => {
+        posthog.capture('team_created', { team_name: data.name, team_id: team?.id });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/(main)/teams/TeamJoinForm.tsx b/src/app/(main)/teams/TeamJoinForm.tsx
index 3aa8653..a0487c4 100644
--- a/src/app/(main)/teams/TeamJoinForm.tsx
+++ b/src/app/(main)/teams/TeamJoinForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
 export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
@@ -14,7 +15,8 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose:
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (team: any) => {
+        posthog.capture('team_joined', { team_id: team?.id, team_name: team?.name });
         touch('teams:members');
         onSave?.();
         onClose?.();
diff --git a/src/app/(main)/websites/WebsiteAddForm.tsx b/src/app/(main)/websites/WebsiteAddForm.tsx
index 7f28651..0b23c1f 100644
--- a/src/app/(main)/websites/WebsiteAddForm.tsx
+++ b/src/app/(main)/websites/WebsiteAddForm.tsx
@@ -1,4 +1,5 @@
 import { Button, Form, FormField, FormSubmitButton, Row, TextField } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { DOMAIN_REGEX } from '@/lib/constants';
 
@@ -16,7 +17,13 @@ export function WebsiteAddForm({
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (website: any) => {
+        posthog.capture('website_created', {
+          website_name: data.name,
+          website_domain: data.domain,
+          website_id: website?.id,
+          team_id: teamId,
+        });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
index 99cc52b..6fc15be 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
@@ -15,6 +15,7 @@ import {
   TextField,
 } from '@umami/react-zen';
 import { Fragment, useState } from 'react';
+import posthog from 'posthog-js';
 import { useApi, useMessages, useMobile, useReportQuery, useUpdateQuery } from '@/components/hooks';
 import { Plus, X } from '@/components/icons';
 import { ActionSelect } from '@/components/input/ActionSelect';
@@ -173,6 +174,12 @@ export function FunnelEditForm({
       { ...data, id, name, type: 'funnel', websiteId, parameters },
       {
         onSuccess: async () => {
+          posthog.capture('funnel_report_saved', {
+            report_name: name,
+            website_id: websiteId,
+            is_new: !id,
+            step_count: (parameters as any)?.steps?.length,
+          });
           touch('reports:funnel');
           touch(`report:${id}`);
           onSave?.();
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
index fb450f4..6848d89 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
@@ -1,3 +1,4 @@
+import posthog from 'posthog-js';
 import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
 import { useDeleteQuery, useMessages } from '@/components/hooks';
 
@@ -18,6 +19,7 @@ export function WebsiteDeleteForm({
   const handleConfirm = async () => {
     await mutateAsync(null, {
       onSuccess: async () => {
+        posthog.capture('website_deleted', { website_id: websiteId });
         touch('websites');
         touch(`websites:${websiteId}`);
         onSave?.();
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
index 63d3703..0231bb4 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
@@ -1,4 +1,5 @@
 import { Form, FormButtons, FormField, FormSubmitButton, TextField } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery, useWebsite } from '@/components/hooks';
 import { DOMAIN_REGEX } from '@/lib/constants';
 
@@ -11,6 +12,11 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa
     const { shareId, ...updateData } = data;
     await mutateAsync(updateData, {
       onSuccess: async () => {
+        posthog.capture('website_updated', {
+          website_id: websiteId,
+          website_name: updateData.name,
+          website_domain: updateData.domain,
+        });
         toast(t(messages.saved));
         touch('websites');
         touch(`website:${website.id}`);
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
index 72a308e..1c915af 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
@@ -1,3 +1,4 @@
+import posthog from 'posthog-js';
 import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
@@ -18,6 +19,7 @@ export function WebsiteResetForm({
   const handleConfirm = async () => {
     await mutateAsync(null, {
       onSuccess: async () => {
+        posthog.capture('website_reset', { website_id: websiteId });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts
index 9b2ad94..03c4326 100644
--- a/src/app/api/auth/login/route.ts
+++ b/src/app/api/auth/login/route.ts
@@ -7,6 +7,7 @@ import { checkPassword } from '@/lib/password';
 import redis from '@/lib/redis';
 import { parseRequest } from '@/lib/request';
 import { json, unauthorized } from '@/lib/response';
+import { getPostHogClient } from '@/lib/posthog-server';
 import { getAllUserTeams, getUserByUsername } from '@/queries/prisma';
 
 export async function POST(request: Request) {
@@ -44,6 +45,11 @@ export async function POST(request: Request) {
 
   const teams = await getAllUserTeams(id);
 
+  const posthog = getPostHogClient();
+  posthog.identify({ distinctId: id, properties: { username, role } });
+  posthog.capture({ distinctId: id, event: 'server_login', properties: { username, role } });
+  await posthog.shutdown();
+
   return json({
     token,
     user: { id, username, role, createdAt, isAdmin: role === ROLES.admin, teams },
diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx
index 23101b9..279bb7d 100644
--- a/src/app/login/LoginForm.tsx
+++ b/src/app/login/LoginForm.tsx
@@ -10,6 +10,7 @@ import {
   TextField,
 } from '@umami/react-zen';
 import { useRouter } from 'next/navigation';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { Logo } from '@/components/svg';
 import { setClientAuthToken } from '@/lib/client';
@@ -25,6 +26,8 @@ export function LoginForm() {
       onSuccess: async ({ token, user }) => {
         setClientAuthToken(token);
         setUser(user);
+        posthog.identify(user.id, { username: user.username, role: user.role });
+        posthog.capture('user_logged_in', { username: user.username, role: user.role });
         router.push('/');
       },
     });
diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx
index 33e1615..3604b32 100644
--- a/src/app/logout/LogoutPage.tsx
+++ b/src/app/logout/LogoutPage.tsx
@@ -1,6 +1,7 @@
 'use client';
 import { useRouter } from 'next/navigation';
 import { useEffect } from 'react';
+import posthog from 'posthog-js';
 import { useApi } from '@/components/hooks';
 import { removeClientAuthToken } from '@/lib/client';
 import { setUser } from '@/store/app';
@@ -18,6 +19,8 @@ export function LogoutPage() {
 
     removeClientAuthToken();
     setUser(null);
+    posthog.capture('user_logged_out');
+    posthog.reset();
     logout();
   }, [router, post]);
 
diff --git a/src/lib/posthog-server.ts b/src/lib/posthog-server.ts
new file mode 100644
index 0000000..d93e269
--- /dev/null
+++ b/src/lib/posthog-server.ts
@@ -0,0 +1,9 @@
+import { PostHog } from 'posthog-node';
+
+export function getPostHogClient() {
+  return new PostHog(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
+    host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
+    flushAt: 1,
+    flushInterval: 0,
+  });
+}

@gewenyu99

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: dub

dub — production link-management platform, Next.js monorepo (apps/web). Built 2.38.0 wizard, headless, snapshotting every screen.

Run: 29 frames · runPhase: completed · diff 24 files, +3,229 / −18 (of which ~2,900 are committed skill reference .md)

Note: the result probe reported hasPosthogDep: false, but that's a monorepo false-negative — the dep landed correctly in apps/web/package.json, not the repo root. Confirmed in the diff below.

Evaluation (workbench rubric)

Dimension Verdict
File analysis ✅ correct sub-app (apps/web), clean client lib/posthog.ts, additive route edits
PostHog implementation ⚠️ dep added (posthog-node ^5.39.4 + lockfile), server client, identify in auth, captureExceptionbut server-only: it picked the javascript_node skill for a Next.js app, so no client-side posthog-js (no pageviews/autocapture)
Event quality ✅ meaningful server events across API routes (links, domains, tokens, workspaces, billing, invites — create/update/delete)
App sanity ⚠️ builds (dep present), but it committed ~2,900 lines of skill reference .md into .claude/skills/ — repo noise the user likely doesn't want checked in

Confidence: 3/5. Correct, real server-side instrumentation on a large monorepo with the dependency properly placed in the sub-app. Docked for treating a Next.js app as plain Node (missing all client-side analytics) and for committing the skill corpus into the repo.

📸 Snapshots — all 29 frames

01-intro

01-intro

02-auth

02-auth

03-run

03-run

04-run

04-run

05-run

05-run

06-run

06-run

07-run

07-run

08-run

08-run

09-run

09-run

10-run

10-run

11-run

11-run

12-run

12-run

13-run

13-run

14-run

14-run

15-run

15-run

16-run

16-run

17-run

17-run

18-run

18-run

19-run

19-run

20-run

20-run

21-run

21-run

22-run

22-run

23-run

23-run

24-run

24-run

25-run

25-run

26-outro

26-outro

27-mcp

27-mcp

28-slack-connect

28-slack-connect

29-keep-skills

29-keep-skills

🔧 Key changes (full diff is 128KB — PostHog code shown)

Changed files

 .../references/4-conclude.md                       |   57 +
 .../references/identify-users.md                   |  272 ++++
 .../integration-javascript_node/references/node.md |  891 ++++++++++++
 .../references/posthog-node.md                     | 1532 ++++++++++++++++++++
 apps/web/app/api/domains/route.ts                  |   13 +
 apps/web/app/api/links/[linkId]/route.ts           |   25 +
 apps/web/app/api/links/route.ts                    |   17 +
 apps/web/app/api/tokens/route.ts                   |   14 +
 apps/web/app/api/user/route.ts                     |   10 +
 .../workspaces/[idOrSlug]/billing/cancel/route.ts  |   14 +-
 .../workspaces/[idOrSlug]/billing/upgrade/route.ts |   15 +
 .../workspaces/[idOrSlug]/invites/accept/route.ts  |   13 +
 apps/web/app/api/workspaces/route.ts               |   12 +
 apps/web/lib/api/errors.ts                         |    5 +
 apps/web/lib/auth/options.ts                       |   35 +-
 apps/web/lib/posthog.ts                            |   18 +
 apps/web/package.json                              |    1 +
 pnpm-lock.yaml                                     |   86 +-
 posthog-setup-report.md                            |   45 +
 24 files changed, 3229 insertions(+), 18 deletions(-)
(+ .claude/skills/integration-javascript_node/** — ~2,900 lines of skill reference md, omitted)

apps/web/lib/posthog.ts

import { PostHog } from "posthog-node";

let posthogClient: PostHog | null = null;

export function getPostHogClient(): PostHog {
  if (!posthogClient) {
    posthogClient = new PostHog(
      process.env.NEXT_PUBLIC_POSTHOG_KEY!,
      {
        host: process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
        flushAt: 1,
        flushInterval: 0,
        enableExceptionAutocapture: true,
      },
    );
  }
  return posthogClient;
}

apps/web/package.json (dependency added)

+    "posthog-node": "^5.39.4",

sample: apps/web/app/api/links/route.ts

+++ b/apps/web/app/api/links/route.ts
+import { getPostHogClient } from "@/lib/posthog";
+      if (session?.user?.id) {
+        const posthog = getPostHogClient();
+        posthog.capture({
+          distinctId: session.user.id,
+          event: "link created",
+          properties: {
+            link_id: response.id,
+            domain: response.domain,
+            workspace_id: workspace?.id,
+            workspace_plan: workspace?.plan,
+            has_expiration: !!response.expiresAt,
+            has_password: !!response.password,
+          },
+        });
+      }
+

identify (apps/web/lib/auth/options.ts)

+import { getPostHogClient } from "../posthog";
+      const posthog = getPostHogClient();
+        posthog.capture({
+        posthog.capture({

@gewenyu99

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: uptime-kuma (Vue 3)

uptime-kuma — production self-hosted uptime monitor, Vue 3 + Node. Single-app. Ran built 2.38.0, headless, every screen snapshotted.

Run: 26 frames · runPhase: completed · dep added (hasPosthogDep: true)

Dimension Verdict
File analysis ✅ init in src/main.js, additive edits across 7 flow components
PostHog impl ✅ env-based init, global Vue error handler → captureException, identify on login + admin-create, session reset on logout
Event quality ✅ 7 real events across core flows (monitor/status-page/maintenance edits, setup)
App sanity ✅ dep added, additive

Confidence: 4/5. Clean idiomatic Vue client integration. Only gap: it's frontend-focused — Uptime Kuma's substantial Node server/ backend isn't server-instrumented. (Skill files committed into .claude/skills/, as with all runs.)

📄 Full run log: uptime-kuma.log

📸 Snapshots — all 26 frames

01-intro

02-auth

03-run

04-run

05-run

06-run

07-run

08-run

09-run

10-run

11-run

12-run

13-run

14-run

15-run

16-run

17-run

18-run

19-run

20-run

21-run

22-run

23-outro

24-mcp

25-slack-connect

26-keep-skills

🔧 Changed files (skill refs omitted) + wizard report
 package-lock.json                                  | 112 ++-
 package.json                                       |   1 +
 posthog-setup-report.md                            |  39 +
 src/components/Login.vue                           |   7 +
 src/components/NotificationDialog.vue              |   4 +
 src/main.js                                        |  10 +
 src/mixins/socket.js                               |   2 +
 src/pages/AddStatusPage.vue                        |   3 +
 src/pages/EditMaintenance.vue                      |   4 +
 src/pages/EditMonitor.vue                          |  12 +
 src/pages/Setup.vue                                |   4 +
 20 files changed, 1866 insertions(+), 41 deletions(-)
<wizard-report>
# PostHog post-wizard report

The wizard has completed a deep integration of PostHog analytics into Uptime Kuma. PostHog is now initialized in `src/main.js` with the project token and host read from environment variables. A global Vue error handler forwards uncaught exceptions to PostHog via `captureException`. Users are identified by username on login and on first-time admin account creation. The PostHog session is reset on logout. Seven custom events are captured across the core user flows.

| Event name | Description | File |
|---|---|---|
| `user_logged_in` | Fires when a user successfully logs in with username and password. | `src/components/Login.vue` |
| `account_created` | Fires when the initial admin account is created during first-time setup. | `src/pages/Setup.vue` |
| `monitor_added` | Fires when a new monitor is successfully created. | `src/pages/EditMonitor.vue` |

@gewenyu99

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: healthchecks (Django)

healthchecks — production cron/uptime monitoring, Django. Single-app.

Run: 30 frames · runPhase: completed · hasPosthogDep: false is a false-negative (the probe reads package.json; the dep was added to requirements.txt)

Dimension Verdict
File analysis ✅ idiomatic — init via AccountsConfig.ready() in apps.py, PosthogContextMiddleware added to MIDDLEWARE
PostHog impl ✅ dep in requirements.txt, request-level context middleware, identify_context in every event
Event quality ✅ 12 business events across 4 view files; a dashboard with 5 insights was created
App sanity ✅ dep added, additive

Confidence: 4/5. Strong, idiomatic Django integration (AppConfig.ready + middleware is exactly right). No material defects.

📄 Full run log: healthchecks.log

📸 Snapshots — all 30 frames

01-intro

02-auth

03-run

04-run

05-run

06-run

07-run

08-run

09-run

10-run

11-run

12-run

13-run

14-run

15-run

16-run

17-run

18-run

19-run

20-run

21-run

22-run

23-run

24-run

25-run

26-run

27-outro

28-mcp

29-slack-connect

30-keep-skills

🔧 Changed files (skill refs omitted) + wizard report
 hc/accounts/apps.py                                |   17 +
 hc/accounts/views.py                               |   37 +
 hc/front/views.py                                  |   18 +
 hc/integrations/slack/views.py                     |   15 +
 hc/settings.py                                     |    6 +-
 posthog-setup-report.md                            |   43 +
 requirements.txt                                   |    1 +
 16 files changed, 2059 insertions(+), 1 deletion(-)
<wizard-report>
# PostHog post-wizard report

The wizard has completed a PostHog integration for the healthchecks Django project. PostHog is now initialized via `hc/accounts/apps.py` (`AccountsConfig.ready()`), with `PosthogContextMiddleware` added to `MIDDLEWARE` for automatic request-level context. Twelve business events are captured across four files, and users are identified in every event using `identify_context`. A dashboard with five insights has been created in PostHog.

| Event name | Description | File |
|---|---|---|
| `user_signed_up` | A new user account was created via the signup form | `hc/accounts/views.py` |
| `user_logged_in` | A user successfully authenticated (email, webauthn, or totp path) | `hc/accounts/views.py` |
| `user_logged_out` | A user logged out of their account | `hc/accounts/views.py` |

@gewenyu99

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: cachet (Laravel)

cachet — production status-page system, Laravel/PHP. Single-app.

Run: 27 frames · runPhase: completed · hasPosthogDep: false is a false-negative (probe reads package.json; dep added to composer.json as posthog/posthog-php)

Dimension Verdict
File analysis ✅ idiomatic — PostHogService wrapper, config/posthog.php, AppServiceProvider event listeners, User::getPostHogProperties()
PostHog impl ✅ dep in composer.json, init + auth listeners (login/logout/register)
Event quality ✅ real domain events — component status changes, incident lifecycle, subscriber signups, scheduled maintenance
App sanity ✅ dep added, additive

Confidence: 4/5. Thorough, idiomatic Laravel — service class + config + provider listeners + model helper. The most complete of the batch.

📄 Full run log: cachet.log

📸 Snapshots — all 27 frames

01-intro

02-auth

03-run

04-run

05-run

06-run

07-run

08-run

09-run

10-run

11-run

12-run

13-run

14-run

15-run

16-run

17-run

18-run

19-run

20-run

21-run

22-run

23-run

24-outro

25-mcp

26-slack-connect

27-keep-skills

🔧 Changed files (skill refs omitted) + wizard report
 app/Models/User.php                                |   10 +
 app/Providers/AppServiceProvider.php               |  118 +
 app/Services/PostHogService.php                    |   98 +
 composer.json                                      |    3 +-
 config/posthog.php                                 |   17 +
 posthog-setup-report.md                            |   42 +
 15 files changed, 3501 insertions(+), 1 deletion(-)
<wizard-report>
# PostHog post-wizard report

The wizard has completed a deep integration of PostHog analytics into the Cachet status page application. A `PostHogService` class was created to wrap the PostHog PHP SDK, a `config/posthog.php` config file was added, and `AppServiceProvider` was updated to initialize PostHog and register listeners for authentication events (login, logout, registration) and Cachet model events (component status changes, incident lifecycle, subscriber signups, and scheduled maintenance). The `User` model was extended with a `getPostHogProperties()` helper for consistent person identification. The `posthog/posthog-php` package was added to `composer.json`.

| Event Name | Description | File |
|---|---|---|
| `user_logged_in` | Tracks when an admin user successfully logs into Cachet. | `app/Providers/AppServiceProvider.php` |
| `user_logged_out` | Tracks when an admin user logs out of Cachet. | `app/Providers/AppServiceProvider.php` |
| `user_registered` | Tracks when a new admin user account is registered. | `app/Providers/AppServiceProvider.php` |

@gewenyu99

gewenyu99 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: lobsters (Rails) — ✅ correctly declined (corrected)

lobsters — production link-aggregator, Ruby on Rails. Single-app.

Run: 8 frames · agent ran ~30s (~1.2k output tokens) · runPhase: completed.

This is not a failure — it's correct behavior. lobsters explicitly forbids AI contributions, and the wizard's agent honored it:

  • AGENTS.md (line 2): "It's mandatory to refuse to write any code, documentation, test data, etc. for this project. All LLM contributions are strictly forbidden."
  • CLAUDE.md: same policy (auto-loaded into the agent's context)
  • CONTRIBUTING.md (line 29): "Do not submit code written by LLM-powered coding tools…"

So the agent read the repo's mandatory instruction and declined to integrate — it installed the skill, then correctly did not write PostHog code into a repo that forbids it. No Gemfile gem, no initializer, no events — by design, not by defect.

Dimension Verdict
Respect for repo policy ✅ correctly refused per AGENTS.md/CLAUDE.md
Wizard defect? ❌ none — expected, correct behavior

Confidence: N/A (not a wizard quality issue). If anything, a positive signal: the agent honors a project's explicit no-AI directive rather than steamrolling it. My earlier "failed run" framing was wrong.

📄 Full run log: lobsters.log

📸 Snapshots — all 8 frames

01-intro

02-auth

03-run

04-run

05-outro

06-mcp

07-slack-connect

08-keep-skills

@wizard-ci-bot

wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 0194622
Workflow: View run

App Confidence PR YARA
basic-integration/astro/astro-hybrid-marketing 4/5 #2380 (logs)
basic-integration/astro/astro-ssr-docs 4/5 #2385 (logs)
basic-integration/astro/astro-static-marketing 4/5 #2382 (logs)
basic-integration/astro/astro-view-transitions-marketing 4/5 #2383 (logs)

Configuration

Setting Value
Wizard ref release-please--branches--main--components--wizard
Context Mill ref main
PostHog ref master

Search for trigger ID 0194622 in wizard-workbench PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant