Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion public/mixpanel-skill/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,73 @@ These phases apply to Full Implementation mode only. Quick Start uses Live View
- **SDK code may lag behind releases.** Mixpanel ships SDK updates independently of this skill. Always check the current SDK changelog when writing production initialization code.
- **Not legal advice.** The compliance and privacy guardrails in `SKILL.md` are implementation defaults, not legal guidance. Customer policy and counsel are the authoritative source for consent and data residency requirements.
- **No enforcement mechanism.** The skill guides the agent to gate phases and reject shortcuts, but a customer who overrides the agent can bypass any guardrail. The skill documents the risk, not the enforcement.
- **Developer Handoff Spec is unverified.** When no codebase access is available, the generated specification cannot be tested for correctness. The agent fills the template with session context, but cannot verify the code compiles, runs, or produces events in Live View. The developer receiving the spec must validate it.
- **Developer Handoff Spec is unverified.** When no codebase access is available, the generated specification cannot be tested for correctness. The agent fills the template with session context, but cannot verify the code compiles, runs, or produces events in Live View. The developer receiving the spec must validate it.

---

## Testing
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How to test/evaluate.


The skill includes a test harness in `tests/` to validate that an AI agent can correctly implement Mixpanel tracking when given the skill.

### Test App

`test-app/` is a minimal React + Express e-commerce demo (Cinder & Bloom Coffee) with:
- Product catalog, cart, checkout flow
- Login/signup authentication flow
- No Mixpanel pre-installed

This provides a realistic codebase for the agent to implement tracking against.

### Test Files

| File | Purpose |
|---|---|
| `tests/01-implement.txt` | Prompt that instructs an agent to implement Mixpanel using the skill |
| `tests/02-evaluate.txt` | Evaluation criteria for grading the agent's implementation |

### Running a Test

1. **Start a fresh agent session** with access to this repo
2. **Feed it `01-implement.txt`** as the initial prompt
3. **Let the agent complete** its implementation (it should follow the Quick Start flow)
4. **Feed it `02-evaluate.txt`** to have the agent self-evaluate its work
5. **Review the JSON output** — `pass: true` means all criteria passed

### Evaluation Criteria

The evaluation checks 14 criteria across 5 categories:

**SDK Setup (1-3)**
- Mixpanel initialized (SDK loaded + init() called)
- At least one event tracked
- Real project token used (not placeholder)

**Identity Management (4-7)**
- `identify()` called on login/signup with stable user ID
- `reset()` called on logout
- `identify()` called BEFORE tracking signup event
- Email NOT used as user ID

**Naming Conventions (8-9)**
- Event names use `snake_case`
- Property names use `snake_case`

**Data Quality (10-12)**
- Numeric values sent as numbers, not strings
- No `null` or empty strings sent (should omit instead)
- No `$` or `mp_` prefixes on custom properties

**Critical Prohibitions (13-14)**
- No `alias()` calls
- No dynamic event/property name construction

### After Evaluation

The evaluation prompt instructs the agent to revert all changes to `test-app/` so subsequent test runs start clean.

### Adding New Tests

To test additional scenarios:
1. Create `tests/0X-scenario.txt` with the agent prompt
2. Create corresponding evaluation criteria or reuse `02-evaluate.txt`
3. Document what the test validates in this section
73 changes: 73 additions & 0 deletions public/mixpanel-skill/test-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Coffee Beans E-commerce (React + Node/Express)

> **Note:** This app was generated using AI and is used only for testing purposes for mixpanel-skill to prevent prompt drift.

A small, fully functioning demo e-commerce app for selling coffee beans.

## Features
- Product list + product detail pages
- Cart (add/remove/update quantities)
- Checkout flow (shipping + customer info) with order confirmation
- Contact Us form
- About + FAQ pages
- Backend REST API (products, orders, contact)
- Simple in-memory data store (swap to DB later)

## Tech
- Frontend: React + Vite + React Router
- Backend: Node.js + Express + CORS
- Dev experience: two terminals (client + server) or use the root `dev` script

---

## Quick Start

### 1) Install dependencies
From the project root:

```bash
npm install
```

### 2) Run in development (two ways)

**Option A: one command (recommended)**
```bash
npm run dev
```
This starts:
- API server at http://localhost:5000
- React dev server at http://localhost:5173 (proxy to API)

**Option B: two terminals**
Terminal 1:
```bash
npm run dev:server
```
Terminal 2:
```bash
npm run dev:client
```

### 3) Build for production
```bash
npm run build
npm start
```
This will:
- build the client
- serve the built client from the Express server at http://localhost:5000

---

## Folder Structure
- `client/` React app (Vite)
- `server/` Express API server

---

## Notes / Next Up Ideas
- Add Stripe/Shopify payments
- Add admin dashboard (CRUD products, orders)
- Add persistent storage (PostgreSQL/MongoDB)
- Add auth + saved addresses
12 changes: 12 additions & 0 deletions public/mixpanel-skill/test-app/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Coffee Beans Shop</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
22 changes: 22 additions & 0 deletions public/mixpanel-skill/test-app/client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "coffee-commerce-client",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"vite": "^5.4.2"
}
}
37 changes: 37 additions & 0 deletions public/mixpanel-skill/test-app/client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { Routes, Route } from "react-router-dom";
import Layout from "./components/Layout.jsx";

import Home from "./pages/Home.jsx";
import Shop from "./pages/Shop.jsx";
import ProductDetail from "./pages/ProductDetail.jsx";
import Cart from "./pages/Cart.jsx";
import Checkout from "./pages/Checkout.jsx";
import OrderConfirmation from "./pages/OrderConfirmation.jsx";
import Contact from "./pages/Contact.jsx";
import About from "./pages/About.jsx";
import FAQ from "./pages/FAQ.jsx";
import Login from "./pages/Login.jsx";
import Signup from "./pages/Signup.jsx";
import NotFound from "./pages/NotFound.jsx";

export default function App() {
return (
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/shop" element={<Shop />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/order/:id" element={<OrderConfirmation />} />
<Route path="/contact" element={<Contact />} />
<Route path="/about" element={<About />} />
<Route path="/faq" element={<FAQ />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Layout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

export default function ErrorBox({ error }) {
if (!error) return null;
return (
<div className="card" style={{ padding: 16, borderColor: "rgba(239,68,68,0.45)" }}>
<div style={{ fontWeight: 800, marginBottom: 6 }}>Something went wrong</div>
<div className="small">{String(error.message || error)}</div>
</div>
);
}
73 changes: 73 additions & 0 deletions public/mixpanel-skill/test-app/client/src/components/Layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { Link, NavLink, useNavigate } from "react-router-dom";
import { useCart } from "../state/cart.jsx";
import { useAuth } from "../state/auth.jsx";

function CartCount() {
const { items } = useCart();
const count = items.reduce((s, it) => s + it.qty, 0);
return <span className="badge">{count} item{count === 1 ? "" : "s"}</span>;
}

function AuthNav() {
const { user, logout } = useAuth();
const nav = useNavigate();

function handleLogout() {
logout();
nav("/");
}

if (user) {
return (
<>
<span className="small" style={{ opacity: 0.7 }}>{user.name}</span>
<button className="btn" onClick={handleLogout} style={{ marginLeft: 8 }}>
Log Out
</button>
</>
);
}

return (
<>
<NavLink to="/login">Log In</NavLink>
<NavLink to="/signup" className="btn">Sign Up</NavLink>
</>
);
}

export default function Layout({ children }) {
return (
<div>
<header className="nav">
<div className="nav-inner">
<Link to="/" className="brand">
<span style={{ fontSize: 18 }}>☕</span>
<span>Cinder & Bloom</span>
<span className="badge">Coffee Beans</span>
</Link>

<nav className="navlinks">
<NavLink to="/shop">Shop</NavLink>
<NavLink to="/cart">Cart</NavLink>
<NavLink to="/contact">Contact</NavLink>
<NavLink to="/faq">FAQ</NavLink>
<NavLink to="/about">About</NavLink>
<CartCount />
<AuthNav />
</nav>
</div>
</header>

<main className="container">{children}</main>

<footer className="footer">
<div>© {new Date().getFullYear()} Cinder & Bloom Coffee</div>
<div className="small" style={{ marginTop: 8 }}>
Demo store • Replace branding, products, and policies before launching.
</div>
</footer>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

export default function Loading({ label = "Loading..." }) {
return (
<div className="card" style={{ padding: 16 }}>
<div className="small">{label}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

export function formatUsdFromCents(cents) {
return `$${(Number(cents || 0) / 100).toFixed(2)}`;
}

export default function Price({ cents }) {
return <span>{formatUsdFromCents(cents)}</span>;
}
22 changes: 22 additions & 0 deletions public/mixpanel-skill/test-app/client/src/components/Toast.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

export default function Toast({ message, onClose }) {
if (!message) return null;
return (
<div style={{
position: "fixed",
right: 16,
bottom: 16,
maxWidth: 420,
zIndex: 50
}}>
<div className="card" style={{ padding: 14, display: "flex", gap: 10, alignItems: "center" }}>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 700 }}>Added to cart</div>
<div className="small">{message}</div>
</div>
<button className="btn" onClick={onClose}>OK</button>
</div>
</div>
);
}
24 changes: 24 additions & 0 deletions public/mixpanel-skill/test-app/client/src/lib/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export async function apiGet(path) {
const res = await fetch(path, { headers: { "Accept": "application/json" }});
if (!res.ok) throw new Error(await safeErr(res));
return res.json();
}

export async function apiPost(path, body) {
const res = await fetch(path, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(await safeErr(res));
return res.json();
}

async function safeErr(res) {
try {
const data = await res.json();
return data?.error || res.statusText;
} catch {
return res.statusText;
}
}
19 changes: 19 additions & 0 deletions public/mixpanel-skill/test-app/client/src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
import { CartProvider } from "./state/cart.jsx";
import { AuthProvider } from "./state/auth.jsx";
import "./styles.css";

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<CartProvider>
<App />
</CartProvider>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);
Loading
Loading