+}
+```
+
+**Why this matters in React:** Props/state mutations break React's immutability model and cause stale closure bugs.
+
+**Other immutable array methods:** `.toSorted()`, `.toReversed()`, `.toSpliced()`, `.with()`
diff --git a/.agents/skills/workleap-react-best-practices/references/rendering-rules.md b/.agents/skills/workleap-react-best-practices/references/rendering-rules.md
new file mode 100644
index 00000000..e76f5e00
--- /dev/null
+++ b/.agents/skills/workleap-react-best-practices/references/rendering-rules.md
@@ -0,0 +1,267 @@
+# Rendering Performance
+
+**Impact:** MEDIUM
+**Description:** Optimizing the rendering process reduces the work the browser needs to do.
+
+---
+
+## Animate SVG Wrapper Instead of SVG Element
+
+Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `
+ )
+}
+```
+
+This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements, making manual hoisting unnecessary.
+
+---
+
+## Optimize SVG Precision
+
+Reduce SVG coordinate precision to decrease file size.
+
+**Incorrect (excessive precision):**
+
+```svg
+
+```
+
+**Correct (1 decimal place):**
+
+```svg
+
+```
+
+
+---
+
+## Use Activity Component for Show/Hide (Experimental)
+
+**Note:** `` is an experimental React API not yet available in stable releases. Use it only if your project uses React Canary/experimental builds. For stable React, use CSS `display: none` or conditional rendering with key-based state preservation.
+
+**Stable React approach:**
+
+```tsx
+function Dropdown({ isOpen }: Props) {
+ return (
+
+
+
+ )
+}
+```
+
+**With React experimental builds:**
+
+```tsx
+import { Activity } from 'react'
+
+function Dropdown({ isOpen }: Props) {
+ return (
+
+
+
+ )
+}
+```
+
+Both approaches preserve state/DOM and avoid expensive re-renders on toggle.
+
+---
+
+## Use Explicit Conditional Rendering
+
+Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
+
+**Incorrect (renders "0" when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return (
+
+ {count && {count}}
+
+ )
+}
+// When count = 0, renders:
0
+```
+
+**Correct (renders nothing when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return (
+
+ {count > 0 ? {count} : null}
+
+ )
+}
+```
+
+---
+
+## Use useTransition Over Manual Loading States
+
+Use `useTransition` instead of manual `useState` for loading states.
+
+**Incorrect (manual loading state):**
+
+```tsx
+function SearchResults() {
+ const [query, setQuery] = useState('')
+ const [results, setResults] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+
+ const handleSearch = async (value: string) => {
+ setIsLoading(true)
+ setQuery(value)
+ const data = await fetchResults(value)
+ setResults(data)
+ setIsLoading(false)
+ }
+
+ return (
+ <>
+ handleSearch(e.target.value)} />
+ {isLoading && }
+
+ >
+ )
+}
+```
+
+**Correct (useTransition with built-in pending state):**
+
+```tsx
+import { useTransition, useState } from 'react'
+
+function SearchResults() {
+ const [query, setQuery] = useState('')
+ const [results, setResults] = useState([])
+ const [isPending, startTransition] = useTransition()
+
+ const handleSearch = (value: string) => {
+ setQuery(value)
+
+ startTransition(async () => {
+ const data = await fetchResults(value)
+ setResults(data)
+ })
+ }
+
+ return (
+ <>
+ handleSearch(e.target.value)} />
+ {isPending && }
+
+ >
+ )
+}
+```
+
+**Benefits:**
+- Automatic pending state management
+- Error resilience: pending state correctly resets even if the transition throws
+- Better responsiveness: keeps the UI responsive during updates
+- New transitions automatically cancel pending ones
+
+Reference: [useTransition](https://react.dev/reference/react/useTransition)
diff --git a/.agents/skills/workleap-react-best-practices/references/rerender-rules.md b/.agents/skills/workleap-react-best-practices/references/rerender-rules.md
new file mode 100644
index 00000000..8ac1b2e8
--- /dev/null
+++ b/.agents/skills/workleap-react-best-practices/references/rerender-rules.md
@@ -0,0 +1,444 @@
+# Re-render Optimization
+
+**Impact:** MEDIUM
+**Description:** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
+
+---
+
+## Defer State Reads to Usage Point
+
+Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
+
+**Incorrect (subscribes to all searchParams changes):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const searchParams = useSearchParams()
+
+ const handleShare = () => {
+ const ref = searchParams.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return
+}
+```
+
+**Correct (reads on demand, no subscription):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const handleShare = () => {
+ const params = new URLSearchParams(window.location.search)
+ const ref = params.get('ref')
+ shareChat(chatId, { ref })
+ }
+
+ return
+}
+```
+
+---
+
+## Extract to Memoized Components
+
+Extract expensive work into memoized components to enable early returns before computation.
+
+**Incorrect (computes avatar even when loading):**
+
+```tsx
+function Profile({ user, loading }: Props) {
+ const avatar = useMemo(() => {
+ const id = computeAvatarId(user)
+ return
+ }, [user])
+
+ if (loading) return
+ return
{avatar}
+}
+```
+
+**Correct (skips computation when loading):**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
+ const id = useMemo(() => computeAvatarId(user), [user])
+ return
+})
+
+function Profile({ user, loading }: Props) {
+ if (loading) return
+ return (
+
+
+
+ )
+}
+```
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
+
+---
+
+## Extract Default Non-primitive Props to Constants
+
+When a memoized component has a default value for a non-primitive optional parameter (array, function, object), calling the component without that parameter breaks memoization because new value instances are created on every re-render.
+
+**Incorrect (`onClick` has different values on every re-render):**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
+ // ...
+})
+
+
+```
+
+**Correct (stable default value):**
+
+```tsx
+const NOOP = () => {};
+
+const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
+ // ...
+})
+
+
+```
+
+---
+
+## Narrow Effect Dependencies
+
+Specify primitive dependencies instead of objects to minimize effect re-runs.
+
+**Incorrect (re-runs on any user field change):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user])
+```
+
+**Correct (re-runs only when id changes):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id)
+}, [user.id])
+```
+
+**For derived state, compute outside effect:**
+
+```tsx
+// Incorrect: runs on width=767, 766, 765...
+useEffect(() => {
+ if (width < 768) {
+ enableMobileMode()
+ }
+}, [width])
+
+// Correct: runs only on boolean transition
+const isMobile = width < 768
+useEffect(() => {
+ if (isMobile) {
+ enableMobileMode()
+ }
+}, [isMobile])
+```
+
+---
+
+## Subscribe to Derived State
+
+Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
+
+**Incorrect (re-renders on every pixel change):**
+
+```tsx
+function Sidebar() {
+ const width = useWindowWidth() // updates continuously
+ const isMobile = width < 768
+ return
+}
+```
+
+**Correct (re-renders only when boolean changes):**
+
+```tsx
+function Sidebar() {
+ const isMobile = useMediaQuery('(max-width: 767px)')
+ return
+}
+```
+
+---
+
+## Calculate Derived State During Rendering
+
+If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift.
+
+**Incorrect (redundant state and effect):**
+
+```tsx
+function Form() {
+ const [firstName, setFirstName] = useState('First')
+ const [lastName, setLastName] = useState('Last')
+ const [fullName, setFullName] = useState('')
+
+ useEffect(() => {
+ setFullName(firstName + ' ' + lastName)
+ }, [firstName, lastName])
+
+ return
+}
+```
+
+Reference: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
+
+---
+
+## Use Functional setState Updates
+
+When updating state based on the current state value, use the functional update form of setState. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
+
+**Incorrect (requires state as dependency):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems([...items, ...newItems])
+ }, [items]) // items dependency causes recreations
+
+ const removeItem = useCallback((id: string) => {
+ setItems(items.filter(item => item.id !== id))
+ }, []) // Missing items dependency - stale closure!
+
+ return
+}
+```
+
+**Correct (stable callbacks, no stale closures):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems(curr => [...curr, ...newItems])
+ }, [])
+
+ const removeItem = useCallback((id: string) => {
+ setItems(curr => curr.filter(item => item.id !== id))
+ }, [])
+
+ return
+}
+```
+
+**When to use functional updates:**
+- Any setState that depends on the current state value
+- Inside useCallback/useMemo when state is needed
+- Event handlers that reference state
+- Async operations that update state
+
+**When direct updates are fine:**
+- Setting state to a static value: `setCount(0)`
+- Setting state from props/arguments only: `setName(newName)`
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness.
+
+---
+
+## Use Lazy State Initialization
+
+Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
+
+**Incorrect (runs on every render):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ return
+}
+```
+
+**Correct (runs only once):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ return
+}
+```
+
+Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
+
+For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
+
+---
+
+## Do Not Wrap Simple Expressions in useMemo
+
+When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`. Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.
+
+**Incorrect:**
+
+```tsx
+function Header({ user, notifications }: Props) {
+ const isLoading = useMemo(() => {
+ return user.isLoading || notifications.isLoading
+ }, [user.isLoading, notifications.isLoading])
+
+ if (isLoading) return
+}
+```
+
+**Correct:**
+
+```tsx
+function Header({ user, notifications }: Props) {
+ const isLoading = user.isLoading || notifications.isLoading
+
+ if (isLoading) return
+}
+```
+
+---
+
+## Put Interaction Logic in Event Handlers
+
+If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect.
+
+**Incorrect (event modeled as state + effect):**
+
+```tsx
+function Form() {
+ const [submitted, setSubmitted] = useState(false)
+ const theme = useContext(ThemeContext)
+
+ useEffect(() => {
+ if (submitted) {
+ post('/api/register')
+ showToast('Registered', theme)
+ }
+ }, [submitted, theme])
+
+ return
+}
+```
+
+**Correct (do it in the handler):**
+
+```tsx
+function Form() {
+ const theme = useContext(ThemeContext)
+
+ function handleSubmit() {
+ post('/api/register')
+ showToast('Registered', theme)
+ }
+
+ return
+}
+```
+
+Reference: [Should this code move to an event handler?](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
+
+---
+
+## Use Transitions for Non-Urgent Updates
+
+Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
+
+**Incorrect (blocks UI on every scroll):**
+
+```tsx
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => setScrollY(window.scrollY)
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
+
+**Correct (non-blocking updates):**
+
+```tsx
+import { startTransition } from 'react'
+
+function ScrollTracker() {
+ const [scrollY, setScrollY] = useState(0)
+ useEffect(() => {
+ const handler = () => {
+ startTransition(() => setScrollY(window.scrollY))
+ }
+ window.addEventListener('scroll', handler, { passive: true })
+ return () => window.removeEventListener('scroll', handler)
+ }, [])
+}
+```
+
+---
+
+## Use useRef for Transient Values
+
+When a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`.
+
+**Incorrect (renders every update):**
+
+```tsx
+function Tracker() {
+ const [lastX, setLastX] = useState(0)
+
+ useEffect(() => {
+ const onMove = (e: MouseEvent) => setLastX(e.clientX)
+ window.addEventListener('mousemove', onMove)
+ return () => window.removeEventListener('mousemove', onMove)
+ }, [])
+
+ return
+}
+```
+
+**Correct (no re-render for tracking):**
+
+```tsx
+function Tracker() {
+ const lastXRef = useRef(0)
+ const dotRef = useRef(null)
+
+ useEffect(() => {
+ const onMove = (e: MouseEvent) => {
+ lastXRef.current = e.clientX
+ if (dotRef.current) {
+ dotRef.current.style.transform = `translateX(${e.clientX}px)`
+ }
+ }
+ window.addEventListener('mousemove', onMove)
+ return () => window.removeEventListener('mousemove', onMove)
+ }, [])
+
+ return
+}
+```
diff --git a/.github/prompts/audit-monorepo.md b/.github/prompts/audit-monorepo.md
new file mode 100644
index 00000000..cf4eafe2
--- /dev/null
+++ b/.github/prompts/audit-monorepo.md
@@ -0,0 +1,94 @@
+# Audit Monorepo
+
+You are an automated agent responsible for auditing this monorepo against best practices defined in locally installed agent skills. You produce a report of actionable findings as a GitHub issue.
+
+## Severity levels
+
+- **High** — actively harmful pattern causing broken caches, incorrect builds, or security risk.
+- **Medium** — suboptimal pattern with measurable impact on performance, maintainability, or correctness.
+- **Low** — minor improvement opportunity, non-urgent.
+- **Informational** — working as designed or acceptable trade-off. **Do NOT report these.**
+
+## False positive prevention
+
+Before including ANY finding in the report, you MUST:
+
+1. Identify the potential issue from the skill documentation.
+2. Re-read the actual source file to confirm the issue exists.
+3. Check for comments, ADRs, or consistent repo-wide patterns that explain the choice. If you find evidence it is intentional, do NOT report it.
+4. Ask yourself: "Does this finding describe a **real problem** the maintainers would want to fix, or am I just noting a deviation from a textbook default?" Only real problems belong in the report.
+5. Do NOT recommend replacing a working pattern with an alternative that has its own trade-offs (e.g., recommending a remote URL over a local path, or vice versa). If both options are reasonable, it's not a finding.
+6. Only include the finding if you are confident it is a genuine issue at severity Low or higher.
+
+When in doubt, do NOT report the finding.
+
+**Examples of patterns that are NOT findings:**
+
+- A task using `pkg#task` dependencies instead of `^build` (may be intentional for isolated/module-federation workflows)
+- Root tasks (`//#task`) that exist because the task genuinely applies to root-level code only
+- A `$schema` pointing to a local path or a remote URL — both are valid choices
+- A workspace glob like `samples/**` that correctly matches the actual directory structure
+- An env var that exists at runtime but isn't in `globalEnv` — only flag it if there's evidence of actual cache correctness issues, not just because it's "missing" from a list
+
+---
+
+## Step 1: Load skill documentation
+
+Read all files in `.agents/skills/turborepo/`, `.agents/skills/pnpm/`, and `.agents/skills/workleap-react-best-practices/` (including all subdirectories) so you understand the best practices to audit against. Do not skip any file.
+
+## Step 2: Audit
+
+Using the best practices and anti-patterns from the skill documentation loaded in Step 1, audit the repository. Read whatever files you need (turbo.json, package.json files, pnpm-workspace.yaml, .npmrc, pnpm-lock.yaml, tsconfig files, .env files, CI workflows, etc.) to check for issues. The skill documentation describes what to look for — use it to guide your investigation.
+
+## Step 3: Validate findings with a subagent
+
+Before generating the report, validate your findings using a subagent to eliminate false positives. Launch a subagent with the Task tool using the **opus** model and provide it with:
+
+1. The full list of candidate findings (severity, skill, file, description, and recommendation for each).
+2. Instructions to independently re-read each referenced file and verify whether the issue actually exists.
+3. Instructions to check whether each flagged pattern might be intentional (e.g., comments explaining the choice, consistency with the rest of the codebase, or a valid trade-off).
+4. Instructions to return a verdict for each finding: **confirmed** or **rejected** with a brief justification.
+
+Only carry forward findings that the subagent confirms. Drop any finding that the subagent rejects.
+
+## Step 4: Generate report
+
+Compile all confirmed findings (severity Low, Medium, or High only) into a structured report.
+
+If there are **zero findings**, STOP. The audit passed cleanly. You are done.
+
+If there are findings, create a GitHub issue:
+
+```bash
+gh issue create \
+ --title "audit: monorepo audit findings — $(date -u +%Y-%m-%d)" \
+ --body "$(cat <<'EOF'
+## Monorepo Audit Report — YYYY-MM-DD
+
+### Skills Audited
+- Turborepo (best practices from `.agents/skills/turborepo/`)
+- pnpm (best practices from `.agents/skills/pnpm/`)
+- Workleap React Best Practices (best practices from `.agents/skills/workleap-react-best-practices/`)
+
+### Summary
+
+| # | Severity | Skill | Finding | File |
+|---|----------|-------|---------|------|
+| 1 | Medium | Turborepo | Brief description | `turbo.json` |
+
+### Details
+
+#### 1. [Finding title]
+
+**Severity:** Medium
+**Skill:** Turborepo
+**File:** `turbo.json:15`
+**Issue:** Description of the problem.
+**Recommendation:** How to fix it.
+
+---
+EOF
+)"
+```
+
+Replace the placeholder content above with the actual findings. Then STOP. You are done.
diff --git a/.github/prompts/code-review.md b/.github/prompts/code-review.md
new file mode 100644
index 00000000..e6979734
--- /dev/null
+++ b/.github/prompts/code-review.md
@@ -0,0 +1,29 @@
+# Code Review
+
+You are an automated code reviewer for this repository. Analyze the PR diff for bugs, security vulnerabilities, and code quality problems.
+
+## Rules
+
+- Only report definite issues introduced by this change (not pre-existing ones).
+- Every reported issue must include a clear fix, with the file path and line number.
+- Skip style preferences, minor nitpicks, and issues typically caught by linters.
+- Do not include positive feedback; focus only on problems.
+
+## Severity
+
+- **Critical** — data loss or security breach.
+- **High** — incorrect behavior.
+- **Medium** — conditional issues.
+- **Low** — minor issues or typos.
+
+## Agent skills
+
+When performing code reviews, load and use agent skills from `.agents/skills/`. Apply the skill mapping defined in [agent-skills.md](../../agent-docs/docs/references/agent-skills.md) (both "By file type" and "By import" tables) to the changed lines in the PR diff.
+
+## Issues reporting
+
+When reporting issues:
+
+- If the issue matches an agent skill, name the skill explicitly.
+- Otherwise, choose an appropriate category based on the nature of the issue.
+- All issues must be reported as inline pull request comments using the `mcp__github_inline_comment__` tools.
diff --git a/.github/prompts/sync-agent-skill.md b/.github/prompts/sync-agent-skill.md
new file mode 100644
index 00000000..bff0a800
--- /dev/null
+++ b/.github/prompts/sync-agent-skill.md
@@ -0,0 +1,153 @@
+# Sync Workleap Telemetry Skill
+
+You are an automated agent responsible for keeping the `workleap-telemetry` agent skill in sync with the documentation in `./docs`.
+
+## Constraints
+
+When updating the skill:
+
+- Do NOT change the format of existing skill files.
+- You MAY create new `references/*.md` files when new content does not fit any existing reference file.
+- Do NOT embed metadata in skill files.
+- Do NOT add "Sources:" lines to skill files.
+- Do NOT create or modify any files outside `agent-skills/workleap-telemetry/`.
+- Do NOT use TodoWrite, TaskCreate, or any task tracking tools.
+- Never update a versioned skill. You can identify a versioned skill with its folder name pattern, e.g. `workleap-telemetry-v*`.
+- Never change skill content unless you can point to a specific line in `./docs` that contradicts the current skill text. If you cannot identify the exact discrepancy, do not touch the content.
+- The SKILL.md body must stay under ~250 lines. New API content goes in the appropriate `references/` file, not in the body. Only add to the body if the content is a critical pattern that agents need in nearly every conversation.
+
+## Excluded docs
+
+The following docs are **not** part of the skill and must be ignored:
+
+- `docs/updating/` — migration guides
+- `docs/standalone-libraries/` — standalone packages (skill covers the umbrella package only)
+- `docs/reference/common-room/` — separate tool not part of the umbrella package
+- `docs/introduction/use-with-agents.md` — meta-doc about using the skill itself
+- `docs/static/` — images and assets
+- Site configuration files: `index.yml`, `default.md`, `about.md`, `samples.md`
+
+All other `.md` files under `docs/` are in scope.
+
+## Docs-to-skill file routing
+
+Use this table to determine which skill file to update when a doc change is found:
+
+| Skill file | Docs |
+|---|---|
+| `SKILL.md` | `docs/introduction/getting-started.md`, `docs/introduction/use-correlation-values.md` |
+| `references/api.md` | `docs/reference/telemetry/*`, `docs/reference/LogRocketLogger.md` |
+| `references/integrations.md` | `docs/introduction/learn-honeycomb/*`, `docs/introduction/learn-logrocket/*`, `docs/introduction/learn-mixpanel/*` |
+| `references/examples.md` | `docs/introduction/setup-project.md`, `docs/guides/*` |
+
+If a doc does not match any row above, use your best judgment to route it to the most relevant skill file. Always respect the "Excluded docs" section — never sync content from excluded paths.
+
+---
+
+## Step 1: Update skill
+
+Review the existing `workleap-telemetry` skill in `./agent-skills/workleap-telemetry/` and make sure that all API definitions and examples match the current documentation available in `./docs`. Use the routing table above to determine which skill file to update. Ignore any docs listed in the "Excluded docs" section.
+
+## Step 2: Check for changes
+
+After updating the skill, check whether any files were actually modified:
+
+```bash
+git diff --name-only HEAD -- agent-skills/workleap-telemetry/
+git ls-files --others --exclude-standard -- agent-skills/workleap-telemetry/
+```
+
+If both commands produce empty output (no changes at all), STOP immediately. Print "No skill changes needed — skill is already in sync." You are done.
+
+## Step 3: Validate
+
+Spawn a subagent with the `Task` tool using the **opus** model to validate the updated skill with a fresh context. The subagent validates from two angles: (1) can the skill answer key questions, and (2) are the API signatures and examples factually correct compared to the actual docs.
+
+Use the following prompt for the subagent:
+
+> You are a validator for the `workleap-telemetry` agent skill. Your job is to verify both **coverage** and **accuracy**.
+>
+> ## Part A — Coverage
+>
+> Read all files in `./agent-skills/workleap-telemetry/`. Using ONLY those files, determine whether the skill can adequately answer each question below. For each, respond PASS or FAIL with a brief explanation.
+>
+> 1. What is `@workleap/telemetry` and what problems does it solve?
+> 2. How do you initialize telemetry in a frontend application using `initializeTelemetry`?
+> 3. What is `productFamily` and what values are valid (`"wlp"`, `"sg"`)?
+> 4. How does automatic correlation work across Honeycomb, LogRocket, and Mixpanel?
+> 5. What are Telemetry Id and Device Id, and how are they propagated?
+> 6. How do you configure Honeycomb tracing and what is automatically instrumented?
+> 7. How do you add custom Honeycomb spans and attributes using OpenTelemetry?
+> 8. How do you configure LogRocket session replay?
+> 9. What privacy controls does LogRocket provide (`data-public`/`data-private`)?
+> 10. How do you identify users in LogRocket using trait helpers?
+> 11. How do you configure Mixpanel analytics?
+> 12. How do you track custom events in Mixpanel?
+> 13. How do you use `MixpanelPropertiesProvider` for scoped properties?
+> 14. What React hooks are available (`useTelemetryClient`, `useHoneycombInstrumentationClient`, `useLogRocketInstrumentationClient`, `useMixpanelClient`)?
+> 15. How do you set up `NoopTelemetryClient` for Storybook and tests?
+> 16. How do you configure `LogRocketLogger` for diagnostic logging?
+> 17. What is `TelemetryProvider` and how is it used in React?
+> 18. How do you troubleshoot missing or inconsistent telemetry?
+>
+> ## Part B — Accuracy
+>
+> Now read all `.md` files under `./docs/`, excluding the paths listed in the "Excluded docs" section. For each code example and API signature in the skill files, verify it matches the docs. Report any discrepancies: wrong parameter names, missing arguments, incorrect types, outdated patterns.
+>
+> ## Output
+>
+> End with:
+> - `COVERAGE: X/18 PASSED`
+> - `ACCURACY: list of discrepancies (or "No discrepancies found")`
+
+If any coverage question is marked FAIL or accuracy discrepancies are found, go back to Step 1 and fix the gaps. Retry at most 3 times. If validation passes, proceed to Step 4. If validation still fails after 3 retries, proceed to Step 4 anyway but include the unresolved issues in the PR (see Step 4c).
+
+## Step 4: Success
+
+### 4a: Increment version
+
+Read the `metadata.version` field in the YAML frontmatter of `agent-skills/workleap-telemetry/SKILL.md`. Increment the **minor** part of the version (e.g., `3.2` → `3.3`, `5.3` → `5.4`). Update the file with the new version.
+
+### 4b: Create branch and commit
+
+```bash
+BRANCH_NAME="agent/skill-sync-$(date -u +%Y%m%d-%H%M%S)-$(git rev-parse --short HEAD)"
+git checkout -b "$BRANCH_NAME"
+git add agent-skills/workleap-telemetry/
+git commit -m "chore(skill): sync workleap-telemetry skill with docs [skip ci]"
+git push origin "$BRANCH_NAME"
+```
+
+### 4c: Create pull request
+
+If validation passed cleanly:
+
+```bash
+gh pr create \
+ --base main \
+ --head "$BRANCH_NAME" \
+ --title "chore(skill): sync workleap-telemetry skill" \
+ --body "## Summary
+
+"
+```
+
+If validation still had failures after 3 retries, create the PR anyway but include a warnings section:
+
+```bash
+gh pr create \
+ --base main \
+ --head "$BRANCH_NAME" \
+ --title "chore(skill): sync workleap-telemetry skill" \
+ --body "## Summary
+
+
+
+## ⚠️ Validation Warnings
+
+The following issues could not be resolved after 3 retries:
+
+"
+```
+
+Then STOP. You are done.
diff --git a/.github/prompts/update-dependencies.md b/.github/prompts/update-dependencies.md
new file mode 100644
index 00000000..21a900b9
--- /dev/null
+++ b/.github/prompts/update-dependencies.md
@@ -0,0 +1,191 @@
+# Update Dependencies
+
+You are an automated agent responsible for updating the dependencies of this monorepo and validating that everything still works correctly.
+
+## Constraints
+
+- You have a maximum of **10 attempts** to pass all validation steps. If you exhaust all attempts, go to Step 4 (Failure).
+- You MUST execute every validation step (2a, 2b, 2c, 2d) in order. Do NOT skip any step.
+- Do NOT create new source files as part of a dependency update. Only modify existing files when migrating to a newer API.
+- Do NOT read `AGENTS.md` or `agent-docs/`.
+- **Avoid rabbit holes**: If you spend more than 3 attempts or 10 tool calls investigating a single issue without progress, stop. Revert the problematic package to its previous version, open an issue, and move on.
+
+---
+
+## Step 1: Update dependencies
+
+Update all dependencies to their latest versions:
+
+```bash
+pnpm update-outdated-deps
+```
+
+Then, check if any `package.json` files were modified:
+
+```bash
+git diff --name-only -- '**/package.json' ':!node_modules'
+```
+
+If the output is empty, there are no dependency updates. STOP immediately. You are done.
+
+Otherwise, install the updated dependencies:
+
+```bash
+pnpm install --no-frozen-lockfile
+```
+
+## Step 2: Validation loop
+
+Run steps 2a through 2d in order. If ANY step fails, diagnose and fix the issue before retrying. Read error messages carefully, inspect failing source code, look up changelogs or migration guides for packages with breaking changes, then apply the fix and restart from Step 2a.
+
+**When fixing breaking changes:**
+
+- Migrate code to use the newer API or pattern introduced by the updated package.
+- Do NOT add polyfills, shims, or workarounds for features already available in the runtime.
+- Do NOT patch or monkey-patch libraries to suppress errors.
+- If a breaking change cannot be resolved by a clean migration, revert that specific package to its previous version, open an issue using the template below, then continue with the remaining updates.
+
+```bash
+gh issue create \
+ --title "[agent] Failed to update " \
+ --body "## Failed dependency update
+
+**Package**: \`\`
+**From**: \`\` → **To**: \`\`
+
+## Error
+
+
+
+## What was tried
+
+
+
+## Workflow run
+
+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
+```
+
+### Step 2a: Linting
+
+```bash
+pnpm lint
+```
+
+All checks must pass with zero errors.
+
+### Step 2b: Tests
+
+```bash
+pnpm test
+```
+
+All tests must pass. If a test fails, run the failing package's tests directly (e.g., `pnpm --filter test`) to get clearer output instead of re-running the full suite.
+
+### Step 2c: Validate the "all-platforms" sample app
+
+```bash
+pnpm build-all-platforms
+```
+
+The build must complete successfully with zero errors. This step validates that all publishable packages (`@workleap/telemetry`, `@workleap/honeycomb`, `@workleap/logrocket`, `@workleap/mixpanel`, `@workleap/common-room`) compile and integrate correctly. Running the dev server is NOT possible for this sample because it requires HTTPS certificates that are not available in CI.
+
+### Step 2d: Validate the "honeycomb/api-key" sample app
+
+Use `pnpx agent-browser` for all browser interactions in this step. Read the locally installed agent skill at `.agents/skills/agent-browser/` to learn the available commands. **Important**: the skill examples use bare `agent-browser` commands, but you MUST always prefix with `pnpx` (e.g., `agent-browser open ` becomes `pnpx agent-browser open `). Running a build is NOT sufficient — you must start the dev server and validate in a real browser.
+
+1. Start the dev server in the background using the shell `&` operator (do NOT use `run_in_background: true`): `pnpm dev-honeycomb-api-key > /tmp/honeycomb-dev.log 2>&1 &`
+2. The app listens on port **8080** and the Express server on port **1234**. Wait for both to be ready — do NOT use `sleep`, do NOT write polling loops, do NOT parse the log file for a URL. Instead, immediately run: `curl --retry 30 --retry-delay 5 --retry-connrefused --silent --output /dev/null http://localhost:8080 && curl --retry 30 --retry-delay 5 --retry-connrefused --silent --output /dev/null http://localhost:1234/api/subscription`
+3. Navigate to the following pages and check that each renders without errors:
+ - `/` (Home page)
+ - `/movies`
+ - `/subscription`
+4. For each page, use `pnpx agent-browser snapshot` to verify the page rendered content, and use `pnpx agent-browser console` to check for console errors. Ignore these specific console messages — they are expected in CI: (1) network errors to `api.honeycomb.io` (trace export failures with the CI dummy API key), (2) OpenTelemetry SDK warnings about dropped spans or failed exports, (3) `[honeycomb]` or `[telemetry]` verbose debug messages. Treat any OTHER console errors as real failures. Do NOT inspect the dev server log file (`/tmp/honeycomb-dev.log`) — only check the browser console.
+5. Stop the dev server processes when done: `kill $(lsof -t -i:8080) 2>/dev/null || true; kill $(lsof -t -i:1234) 2>/dev/null || true; fuser -k 8080/tcp 2>/dev/null || true; fuser -k 1234/tcp 2>/dev/null || true`
+
+## Step 3: Success
+
+All validations passed.
+
+### 3a: Create a changeset (if needed)
+
+Only include publishable packages (`@workleap/*` or `@workleap-telemetry/*`) whose `dependencies` or `peerDependencies` have actually changed. Do NOT include packages where only `devDependencies` changed — devDependency-only changes do not affect the published package. If no publishable packages qualify (i.e., every change is devDependency-only), skip this step entirely — do NOT create a changeset file.
+
+Create a changeset file at `.changeset/update-deps-.md` (use the current UTC date-time to avoid filename collisions with unreleased changesets). Use `patch` as the default bump level, but use your judgment to bump as `minor` or `major` if warranted by the dependency changes.
+
+Example format:
+
+```markdown
+---
+"@workleap/telemetry": patch
+"@workleap/honeycomb": patch
+---
+
+Updated dependencies to their latest versions.
+```
+
+### 3b: Commit and create pull request
+
+Close any existing open dependency-update PRs before creating a new one:
+
+```bash
+gh pr list --search "chore: update dependencies" --state open --json number --jq '.[].number' | xargs -I{} gh pr close {} --comment "Superseded by a newer dependency update run."
+```
+
+Then create the new PR with the body described below.
+
+#### How to write the Summary section
+
+Before writing the summary, run `git diff main -- '**/package.json'` to see the actual changes. Then apply these rules:
+
+1. In unified diff output, context lines start with a SPACE character, removed lines start with a single `-`, and added lines start with a single `+`. Only `-` and `+` lines represent actual changes — do NOT include dependencies that only appear on space-prefixed context lines.
+2. Deduplicate: list each dependency name + version change only once, even if it appears in multiple package.json files.
+3. If a dependency appears in different categories across packages (e.g., peerDependencies in a library and devDependencies in a sample), list all categories it belongs to (e.g., "peerDependencies, devDependencies").
+4. Format each as: `package-name`: `old-version` → `new-version` (category), where category is dependencies, peerDependencies, or devDependencies.
+5. Sort by category priority: peerDependencies first, then dependencies, then devDependencies. If a dependency belongs to multiple categories, sort it by its highest-priority category.
+6. Highlight any peerDependency range narrowing with "(range narrowed — may break consumers)" — these affect consumers.
+7. Do NOT mention transitive dependencies (pnpm-lock.yaml only).
+
+#### PR body template
+
+```markdown
+## Summary
+
+
+
+## Validation checklist
+- [x] Step 2a: Linting
+- [x] Step 2b: Tests
+- [x] Step 2c: All-platforms sample app
+- [x] Step 2d: Honeycomb API key sample app
+```
+
+#### Create the PR
+
+```bash
+BRANCH_NAME="agent/update-deps-$(date -u +%Y%m%d-%H%M%S)"
+git checkout -b "$BRANCH_NAME"
+git add -A
+git commit -m "chore: update dependencies"
+git push origin "$BRANCH_NAME"
+
+gh pr create \
+ --base main \
+ --head "$BRANCH_NAME" \
+ --title "chore: update dependencies $(date -u +%Y-%m-%d)" \
+ --body "