Working in products/desktop
products/desktop/ is the PostHog desktop app (Electron + agent framework), imported from the
now-archived PostHog/code repo and kept as a nested standalone workspace: own
pnpm-workspace.yaml, own lockfile, own Biome config, Node 22. It is excluded from the root
pnpm-workspace.yaml, from ruff/mypy, from root Jest, from oxlint/oxfmt, from stylelint and
from pytest. posthog/posthog is the only source of truth: PRs, CI and publishing all happen
here, never in PostHog/code.
Read products/desktop/AGENTS.md before writing code
in this tree. It is the source of truth for architecture, layer boundaries, DI, and style.
This skill only covers the scoping contract around it.
Scope contract
Default: everything you read, edit, run and test lives under products/desktop/. Run all
commands with that as cwd. Do not go fishing in frontend/, posthog/, or other products/*
for patterns — the desktop tree has its own, and copying monorepo idioms in is a defect.
Step outside only when one of these is true:
- The user explicitly tells you to.
- A backend API the app calls needs to change. The client is
packages/api-client/; the endpoints it hits live in the Django monorepo (see below).
- Desktop CI / config that lives at the root by design. The list is fixed:
.github/workflows/desktop-*.yml, .github/actions/desktop-*/,
.github/scripts/desktop/, and the exclusion entries in pnpm-workspace.yaml,
pyproject.toml, pytest.ini, package.json (lint:css), frontend/jest.config.ts,
.dockerignore, .oxlintrc.json, .oxfmtrc.json, .config/.markdownlint-cli2.jsonc,
.github/workflows/ci-{frontend,storybook,backend}.yml (+ .depot/workflows/ci-backend.yml).
Touch these only to keep an exclusion correct; say so when you do.
Anything else outside the tree: stop and ask.
Toolchain — root CLAUDE.md does not apply here
| Root monorepo says |
In products/desktop use |
ruff / mypy / pytest |
nothing — no Python app code here |
oxlint + Oxfmt, pnpm --filter=@posthog/frontend fix |
pnpm lint (Biome check --write), pnpm format |
Jest, hogli test |
Vitest (pnpm test), Playwright (pnpm test:e2e) |
pnpm --filter=@posthog/frontend typescript:check |
pnpm typecheck (turbo, all packages) |
Kea logics, typegen:write |
Zustand stores + InversifyJS services; no kea, no typegen |
LemonUI in frontend/ and products/*/frontend/ |
@posthog/quill + Tailwind + Radix Themes |
hogli build:openapi → generated frontend types |
packages/api-client/src/generated.ts (hand-maintained here) |
| 4-space / repo Prettier habits |
Biome, 2-space, double quotes |
Commands (from products/desktop/): pnpm install, pnpm dev, pnpm build, pnpm typecheck,
pnpm lint, pnpm test, pnpm test:e2e, pnpm --filter <pkg> <task>,
node scripts/check-host-boundaries.mjs.
Never run root-level pnpm install expecting it to cover desktop, and never let a desktop
dependency change alter the root lockfile — the root install must stay byte-identical.
Cross-boundary: the Django API
packages/api-client/ calls monorepo endpoints — tasks (/api/projects/:id/tasks/...),
signals, agent applications, environments/MCP installations, /api/users/. When a change needs
the backend:
- Read the Django side to get the contract right; that is in scope without asking.
- Editing it is a separate, monorepo-side change: root CLAUDE.md rules apply again
(
/improving-drf-endpoints, /django-migrations, serializer help_text, hogli build:openapi).
- Keep the two sides in one PR only if they must ship together; otherwise land the backend first.
- Wiring the app at a local Django instance (OAuth app, RSA keys, flags) is
products/desktop/docs/LOCAL-DEVELOPMENT.md.
Provenance
The tree was imported from the standalone PostHog/code repo, which is now archived. There is
no resync process: the monorepo copy is the only maintained one, and fixes land here like any
other monorepo change. product.yaml declares ownership (team-posthog-code) for reviewer
auto-assignment and Slack routing; do not delete it.
Desktop-local skills
products/desktop/.claude/skills/ ships its own: test-electron-app (drive the running app
over CDP :9222), quill-code, storybook-stories, onboarding-videos.
Prefer these over monorepo equivalents while in this tree.
Before reporting done
pnpm typecheck and pnpm lint from products/desktop/, plus pnpm test for touched packages.
- After touching
packages/core: biome lint packages/core, zero noRestrictedImports.
- After touching
@posthog/platform: rebuild or typecheck its dist/.
- After moving logic out of
apps/code: node scripts/check-host-boundaries.mjs --prune.
- Confirm the diff touches nothing outside
products/desktop/ — or name what it touches and why.
1---2name: posthog-desktop3description: Scopes work to the desktop app at products/desktop — a nested standalone pnpm/turbo/Biome workspace imported from the now-archived PostHog/code repo, with posthog/posthog the only source of truth for PRs, CI and publishing, and not part of the root frontend or Django build. Use when the user says /posthog-desktop, or works on the Electron desktop app, apps/code, apps/web, apps/mobile, packages/core, packages/ui, packages/workspace-server, @posthog/api-client, @posthog/agent, or the agent framework. Pins the working directory to products/desktop, swaps in that tree's toolchain and conventions in place of the monorepo's, and defines the few paths outside the tree that may be touched (Django APIs the app calls, desktop-* CI at the root).4---5
6# Working in products/desktop
7
8`products/desktop/` is the PostHog desktop app (Electron + agent framework), imported from the
9now-archived PostHog/code repo and kept as a **nested standalone workspace**: own
10`pnpm-workspace.yaml`, own lockfile, own Biome config, Node 22. It is excluded from the root
11`pnpm-workspace.yaml`, from ruff/mypy, from root Jest, from oxlint/oxfmt, from stylelint and
12from pytest. posthog/posthog is the only source of truth: PRs, CI and publishing all happen
13here, never in PostHog/code.
14
15**Read [`products/desktop/AGENTS.md`](../../../products/desktop/AGENTS.md) before writing code
16in this tree.** It is the source of truth for architecture, layer boundaries, DI, and style.
17This skill only covers the scoping contract around it.
18
19## Scope contract
20
21Default: **everything you read, edit, run and test lives under `products/desktop/`.** Run all
22commands with that as cwd. Do not go fishing in `frontend/`, `posthog/`, or other `products/*`
23for patterns — the desktop tree has its own, and copying monorepo idioms in is a defect.
24
25Step outside only when one of these is true:
26
271. **The user explicitly tells you to.**
282. **A backend API the app calls needs to change.** The client is
29 `packages/api-client/`; the endpoints it hits live in the Django monorepo (see below).
303. **Desktop CI / config that lives at the root by design.** The list is fixed:
31 `.github/workflows/desktop-*.yml`, `.github/actions/desktop-*/`,
32 `.github/scripts/desktop/`, and the exclusion entries in `pnpm-workspace.yaml`,
33 `pyproject.toml`, `pytest.ini`, `package.json` (`lint:css`), `frontend/jest.config.ts`,
34 `.dockerignore`, `.oxlintrc.json`, `.oxfmtrc.json`, `.config/.markdownlint-cli2.jsonc`,
35 `.github/workflows/ci-{frontend,storybook,backend}.yml` (+ `.depot/workflows/ci-backend.yml`).
36 Touch these only to keep an exclusion correct; say so when you do.
37
38Anything else outside the tree: stop and ask.
39
40## Toolchain — root CLAUDE.md does not apply here
41
42| Root monorepo says | In products/desktop use |
43| ----------------------------------------------------- | ------------------------------------------------------------- |
44| `ruff` / `mypy` / pytest | nothing — no Python app code here |
45| oxlint + Oxfmt, `pnpm --filter=@posthog/frontend fix` | `pnpm lint` (Biome check --write), `pnpm format` |
46| Jest, `hogli test` | Vitest (`pnpm test`), Playwright (`pnpm test:e2e`) |
47| `pnpm --filter=@posthog/frontend typescript:check` | `pnpm typecheck` (turbo, all packages) |
48| Kea logics, `typegen:write` | Zustand stores + InversifyJS services; no kea, no typegen |
49| LemonUI in `frontend/` and `products/*/frontend/` | `@posthog/quill` + Tailwind + Radix Themes |
50| `hogli build:openapi` → generated frontend types | `packages/api-client/src/generated.ts` (hand-maintained here) |
51| 4-space / repo Prettier habits | Biome, 2-space, double quotes |
52
53Commands (from `products/desktop/`): `pnpm install`, `pnpm dev`, `pnpm build`, `pnpm typecheck`,
54`pnpm lint`, `pnpm test`, `pnpm test:e2e`, `pnpm --filter <pkg> <task>`,
55`node scripts/check-host-boundaries.mjs`.
56
57Never run root-level `pnpm install` expecting it to cover desktop, and never let a desktop
58dependency change alter the root lockfile — the root install must stay byte-identical.
59
60## Cross-boundary: the Django API
61
62`packages/api-client/` calls monorepo endpoints — tasks (`/api/projects/:id/tasks/...`),
63signals, agent applications, environments/MCP installations, `/api/users/`. When a change needs
64the backend:
65
66- Read the Django side to get the contract right; that is in scope without asking.
67- **Editing** it is a separate, monorepo-side change: root CLAUDE.md rules apply again
68 (`/improving-drf-endpoints`, `/django-migrations`, serializer `help_text`, `hogli build:openapi`).
69- Keep the two sides in one PR only if they must ship together; otherwise land the backend first.
70- Wiring the app at a local Django instance (OAuth app, RSA keys, flags) is
71 `products/desktop/docs/LOCAL-DEVELOPMENT.md`.
72
73## Provenance
74
75The tree was imported from the standalone PostHog/code repo, which is now archived. There is
76no resync process: the monorepo copy is the only maintained one, and fixes land here like any
77other monorepo change. `product.yaml` declares ownership (`team-posthog-code`) for reviewer
78auto-assignment and Slack routing; do not delete it.
79
80## Desktop-local skills
81
82`products/desktop/.claude/skills/` ships its own: `test-electron-app` (drive the running app
83over CDP `:9222`), `quill-code`, `storybook-stories`, `onboarding-videos`.
84Prefer these over monorepo equivalents while in this tree.
85
86## Before reporting done
87
88- `pnpm typecheck` and `pnpm lint` from `products/desktop/`, plus `pnpm test` for touched packages.
89- After touching `packages/core`: `biome lint packages/core`, zero `noRestrictedImports`.
90- After touching `@posthog/platform`: rebuild or typecheck its `dist/`.
91- After moving logic out of `apps/code`: `node scripts/check-host-boundaries.mjs --prune`.
92- Confirm the diff touches nothing outside `products/desktop/` — or name what it touches and why.