chore(main): release 2.38.0#785
Conversation
🧙 Wizard CIRun 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:
Test all apps in a directory:
Test an individual app:
Show more apps
Results will be posted here when complete. |
b67b152 to
cb9b769
Compare
cb9b769 to
7188a5b
Compare
|
/wizard-ci all |
🧙 Wizard CI ResultsTrigger ID:
Configuration
|
🧪 Release test — 2.38.0 on a production repo:
|
| Dimension | Verdict |
|---|---|
| File analysis | ✅ correct files & locations, clean additive code |
| PostHog implementation | 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
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-outro
26-mcp
27-slack-connect
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,
+ });
+}
🧪 Release test — 2.38.0 on a production repo:
|
| Dimension | Verdict |
|---|---|
| File analysis | ✅ correct sub-app (apps/web), clean client lib/posthog.ts, additive route edits |
| PostHog implementation | posthog-node ^5.39.4 + lockfile), server client, identify in auth, captureException — but 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 | .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
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-outro
27-mcp
28-slack-connect
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({
🧪 Release test — 2.38.0 on a production repo:
|
| 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
🔧 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` |
🧪 Release test — 2.38.0 on a production repo:
|
| 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
🔧 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` |
🧪 Release test — 2.38.0 on a production repo:
|
| 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
🔧 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` |
🧪 Release test — 2.38.0 on a production repo:
|
| 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
🧙 Wizard CI ResultsTrigger ID:
Configuration
|




















































































































































🤖 I have created a release beep boop
2.38.0 (2026-07-03)
Features
Bug Fixes
This PR was generated with Release Please. See documentation.