Skip to content

Commit c880fbd

Browse files
authored
Merge branch 'main' into posthog-code/add-mark-file-as-read-in-pr-review
Generated-By: PostHog Code Task-Id: 71e782ac-16eb-409f-a5d3-2aa4ad9117f8
2 parents e19fc76 + fc33dfb commit c880fbd

384 files changed

Lines changed: 25047 additions & 4683 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/quill-code/SKILL.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
name: quill-code
3+
description: Edit the @posthog/quill design system locally and consume the change in this repo (posthog-code) before it is published to npm. Use when changing quill components/primitives/tokens, when a quill change must be tested inside the Code app, or when the user mentions quill, the design system, the .local-quill tarball, or the @posthog/quill pnpm override.
4+
---
5+
6+
# quill-code
7+
8+
`@posthog/quill` is **not** in this repo. It is a published catalog dependency whose
9+
source lives in the main PostHog monorepo at `../posthog/packages/quill`. To test an
10+
unpublished quill change inside this repo (posthog-code), you build quill, pack it to a
11+
tarball, and point a pnpm `overrides` entry at that tarball. This is a **temporary
12+
local-dev** state — revert before merging (see below).
13+
14+
## Quill layout (where to edit)
15+
16+
`../posthog/packages/quill` is the **workspace** (`@posthog/quill-workspace`). It
17+
contains sub-packages, each a layer of the design system:
18+
19+
- `packages/primitives/src` — base components (Card, Badge, Button, Progress, …)
20+
- `packages/components/src` — composed components (DataTable, DateTimePicker, Metric)
21+
- `packages/blocks/src`**product-level blocks** (e.g. `ExperimentCard`). Add the
22+
file here and export it from `packages/blocks/src/index.ts`.
23+
- `packages/quill/src` — the **aggregate** that re-exports all layers as `@posthog/quill`.
24+
25+
A new export in any sub-package flows to `@posthog/quill` automatically on build.
26+
27+
## The loop (every quill change)
28+
29+
1. Edit/add the component in the right sub-package (above) and export it from that
30+
package's `src/index.ts`.
31+
2. Re-sync into this repo manually — build → pack → point the override → reinstall:
32+
33+
```bash
34+
QUILL_DIR="${QUILL_DIR:-../posthog/packages/quill/packages/quill}"
35+
36+
# a. Build the WHOLE quill workspace (two levels up from the aggregate), so the
37+
# sub-packages rebuild BEFORE the aggregate bundles them.
38+
( cd "$QUILL_DIR/../.." && pnpm build )
39+
40+
# b. Pack into .local-quill/ under a UNIQUE filename. pnpm pins a tarball by
41+
# integrity, so a stable name caches stale across re-syncs — drop old local
42+
# tarballs first, then rename the packed file to a unique local name.
43+
rm -f .local-quill/posthog-quill-local-*.tgz
44+
( cd "$QUILL_DIR" && npm pack --pack-destination "$(git rev-parse --show-toplevel)/.local-quill" )
45+
mv .local-quill/posthog-quill-[0-9]*.tgz ".local-quill/posthog-quill-local-$(git rev-parse --short HEAD)-$$.tgz"
46+
47+
# c. Point the override at the new tarball, then reinstall.
48+
# Edit pnpm-workspace.yaml so overrides['@posthog/quill'] = file:./.local-quill/<new file>
49+
pnpm install
50+
```
51+
52+
> Building only the aggregate (`packages/quill/packages/quill`) re-bundles the
53+
> sub-packages' **stale** `dist/`, so edits to primitives/components/blocks are
54+
> silently dropped. Always build at the workspace root.
55+
3. Verify in the Code app (`pnpm dev`, or the `test-electron-app` skill). Repeat from 1.
56+
57+
After every quill edit you **must** re-run the sync — the app consumes the tarball, not
58+
the quill source, so unsynced edits are invisible here.
59+
60+
If quill lives elsewhere, set `QUILL_DIR=/abs/path/to/posthog/packages/quill/packages/quill`.
61+
62+
## Why a tarball, not `link:`
63+
64+
`link:` symlinks into the mono's `node_modules` and drags in its **React 18** types,
65+
colliding with this repo's **React 19** (dual-React → broken typecheck +
66+
invalid-hook-call at runtime). The tarball is copied into this repo's store and deduped
67+
against React 19. The filename is **content-hashed** because pnpm pins a tarball by
68+
integrity, so a stable filename gets cached stale across re-syncs.
69+
70+
## The override (what the script rewrites)
71+
72+
In `pnpm-workspace.yaml`, under `overrides:`:
73+
74+
```yaml
75+
'@posthog/quill': file:./.local-quill/posthog-quill-local-<hash>.tgz
76+
```
77+
78+
There is also a permanent pin you should leave alone:
79+
80+
```yaml
81+
'@posthog/quill>@base-ui/react': ^1.3.0 # quill ships a broken catalog: dep; do not remove
82+
```
83+
84+
## Reverting (before merge)
85+
86+
The override is local-dev only. Once the quill change is published to npm:
87+
88+
1. Bump the catalog version in `pnpm-workspace.yaml` (`'@posthog/quill': 0.3.0-beta.x`)
89+
to the published version.
90+
2. Restore the override line to point back at the catalog, or remove the `file:` override.
91+
3. `pnpm install`.
92+
93+
Do not commit a `file:./.local-quill/...` override or the `.local-quill/` tarballs.
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
name: Storybook visual regression
2+
3+
# Captures a screenshot of every story (dark + light) and submits them to the
4+
# PostHog Visual Review product, which diffs against the signed baseline
5+
# manifest committed at apps/code/snapshots.yml. Visual changes are reviewed
6+
# and approved at https://us.posthog.com/project/2/visual_review; on approval
7+
# the VR backend commits the updated manifest back to the PR branch. PNGs are
8+
# never committed to this repo.
9+
#
10+
# Until the one-time setup is done (VR repo registration + VR_API_TOKEN secret
11+
# + committed snapshots.yml), the vr step silently no-ops: the job still runs
12+
# the capture, which catches stories that crash or never settle.
13+
14+
on:
15+
pull_request:
16+
push:
17+
branches: [main]
18+
19+
concurrency:
20+
group: code-storybook-${{ github.head_ref || github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
changes:
25+
if: github.event_name == 'pull_request'
26+
runs-on: ubuntu-latest
27+
permissions:
28+
pull-requests: read
29+
outputs:
30+
code: ${{ steps.filter.outputs.code }}
31+
baseline_only_push: ${{ steps.baseline_push.outputs.result }}
32+
steps:
33+
- name: Detect relevant changes
34+
id: filter
35+
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
36+
with:
37+
predicate-quantifier: every
38+
filters: |
39+
# Anything that can change a rendered story. The baseline manifest
40+
# is excluded, but note paths-filter diffs the whole PR against
41+
# its base, so once a PR has code changes this filter stays true
42+
# for every later push — the baseline_push step below is what
43+
# skips the VR backend's own approval commits.
44+
code:
45+
- "{apps/code/**,packages/**,pnpm-lock.yaml,.github/workflows/code-storybook.yml}"
46+
- "!apps/code/snapshots.yml"
47+
- "!**/*.md"
48+
49+
# Visual Review commits the approved baseline back to the PR branch as
50+
# a bot; re-running the suite for that push is pure waste (only the
51+
# manifest changed). Compare the push delta (before...after), not the
52+
# PR diff, so it only matches the bot's own baseline commit.
53+
- name: Detect Visual Review baseline-only push
54+
id: baseline_push
55+
if: github.event_name == 'pull_request' && github.event.action == 'synchronize'
56+
env:
57+
GH_TOKEN: ${{ github.token }}
58+
REPO: ${{ github.repository }}
59+
ACTOR: ${{ github.actor }}
60+
BEFORE: ${{ github.event.before }}
61+
AFTER: ${{ github.event.after }}
62+
run: |
63+
set -euo pipefail
64+
result=false
65+
case "$ACTOR" in
66+
*'[bot]' | posthog-bot) is_bot=true ;;
67+
*) is_bot=false ;;
68+
esac
69+
zero=0000000000000000000000000000000000000000
70+
if [ "$is_bot" = true ] && [ -n "$BEFORE" ] && [ "$BEFORE" != "$zero" ] && [ -n "$AFTER" ]; then
71+
# compare uses merge-base(before, after), which for an appended
72+
# commit is exactly the push delta; a force-push/rebase widens it
73+
# and won't match.
74+
files=$(gh api "repos/${REPO}/compare/${BEFORE}...${AFTER}" --jq '.files[].filename' || true)
75+
if [ -n "$files" ] && ! grep -qv '^apps/code/snapshots\.yml$' <<<"$files"; then
76+
result=true
77+
fi
78+
fi
79+
echo "result=$result" >>"$GITHUB_OUTPUT"
80+
echo "Visual Review baseline-only push: $result"
81+
82+
visual-regression:
83+
needs: changes
84+
# Fail closed: if change detection failed (or this is a main push, where
85+
# `changes` is skipped), run instead of skipping — unless the push was
86+
# just the VR bot committing an approved baseline.
87+
if: >-
88+
${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.code == 'true') &&
89+
needs.changes.outputs.baseline_only_push != 'true' }}
90+
runs-on: ubuntu-latest
91+
timeout-minutes: 30
92+
permissions:
93+
contents: read
94+
steps:
95+
- name: Checkout
96+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
97+
with:
98+
persist-credentials: false
99+
100+
- name: Setup pnpm
101+
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
102+
103+
- name: Setup Node.js
104+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
105+
with:
106+
node-version: 22
107+
cache: "pnpm"
108+
109+
- name: Cache Playwright browsers
110+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
111+
id: playwright-cache
112+
with:
113+
path: ~/.cache/ms-playwright
114+
key: playwright-chromium-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
115+
restore-keys: |
116+
playwright-chromium-${{ runner.os }}-
117+
118+
- name: Install dependencies
119+
run: pnpm install --frozen-lockfile
120+
121+
- name: Install Playwright Chromium
122+
if: steps.playwright-cache.outputs.cache-hit != 'true'
123+
working-directory: apps/code
124+
run: pnpm exec playwright install --with-deps chromium
125+
126+
- name: Install Playwright system dependencies
127+
# The cache restores browser binaries but not the OS packages they need.
128+
if: steps.playwright-cache.outputs.cache-hit == 'true'
129+
working-directory: apps/code
130+
run: pnpm exec playwright install-deps chromium
131+
132+
- name: Build workspace packages
133+
# Bare `wait` returns the exit status of only the last job passed to
134+
# it, silently swallowing failures from the others. `wait_all` waits
135+
# on each PID individually and checks its own status so any failed
136+
# build fails the step.
137+
run: |
138+
wait_all() {
139+
status=0
140+
for pid in "$@"; do
141+
wait "$pid" || status=$?
142+
done
143+
[ "$status" -eq 0 ]
144+
}
145+
146+
pnpm --filter @posthog/electron-trpc build &
147+
pid1=$!
148+
(pnpm --filter @posthog/shared build && pnpm --filter @posthog/platform build) &
149+
pid2=$!
150+
wait_all "$pid1" "$pid2"
151+
152+
# @posthog/agent imports @posthog/git's dist, so git must finish
153+
# before the last group starts.
154+
pnpm --filter @posthog/git build
155+
156+
pnpm --filter @posthog/enricher build &
157+
pid3=$!
158+
pnpm --filter @posthog/agent build &
159+
pid4=$!
160+
wait_all "$pid3" "$pid4"
161+
162+
- name: Build Storybook
163+
working-directory: apps/code
164+
run: pnpm build-storybook
165+
166+
- name: Serve Storybook
167+
working-directory: apps/code
168+
run: |
169+
pnpm exec http-server storybook-static --port 6006 --silent &
170+
pnpm exec wait-on http://127.0.0.1:6006/iframe.html --timeout 30000
171+
172+
- name: Capture story screenshots
173+
working-directory: apps/code
174+
run: |
175+
rm -rf .storybook/__snapshots__
176+
pnpm test:visual:ci:update
177+
178+
# The vr CLI isn't published anywhere; build it from posthog/posthog's
179+
# master, the same way that repo's own CI does.
180+
- name: Checkout Visual Review CLI
181+
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push'
182+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
183+
with:
184+
repository: PostHog/posthog
185+
path: vr-cli
186+
persist-credentials: false
187+
sparse-checkout: |
188+
products/visual_review/cli
189+
products/visual_review/frontend/generated/api.schemas.ts
190+
sparse-checkout-cone-mode: false
191+
192+
- name: Install Visual Review CLI
193+
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push'
194+
run: cd vr-cli/products/visual_review/cli && npm ci && npm run build && npm link
195+
196+
- name: Submit snapshots to Visual Review
197+
# Fork PRs can't read the token; their captures still ran above, so a
198+
# broken story fails the job either way.
199+
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push'
200+
# On main pushes the run is tracking-only ("observe"), but vr submit
201+
# still exits 1 when it detects changes; keep main green regardless.
202+
continue-on-error: ${{ github.event_name == 'push' }}
203+
env:
204+
VR_TOKEN: ${{ secrets.VR_API_TOKEN }}
205+
VR_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
206+
VR_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
207+
VR_PR: ${{ github.event.pull_request.number }}
208+
VR_PURPOSE: ${{ github.event_name == 'push' && 'observe' || 'review' }}
209+
# Lets the VR web UI re-trigger this job via the Actions API.
210+
JOB_CHECK_RUN_ID: ${{ job.check_run_id }}
211+
run: |
212+
if [ -z "$VR_TOKEN" ]; then
213+
echo "::notice::VR_API_TOKEN secret not set - skipping Visual Review submission"
214+
exit 0
215+
fi
216+
vr submit \
217+
--type storybook \
218+
--dir apps/code/.storybook/__snapshots__/ \
219+
--baseline apps/code/snapshots.yml \
220+
--branch "$VR_BRANCH" \
221+
--commit "$VR_COMMIT" \
222+
${VR_PR:+--pr "$VR_PR"} \
223+
--purpose "$VR_PURPOSE" \
224+
--token "$VR_TOKEN"
225+
226+
- name: Upload failure screenshots
227+
if: failure()
228+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.0
229+
with:
230+
name: storybook-visual-failures
231+
path: |
232+
apps/code/.storybook/__snapshots__/__diff_output__/
233+
apps/code/.storybook/__snapshots__/__failures__/
234+
if-no-files-found: ignore
235+
retention-days: 7
236+
237+
- name: Upload test results to Trunk
238+
# Run even when tests fail so flaky/failed results are still reported,
239+
# but never let an upload problem fail the job.
240+
if: ${{ !cancelled() }}
241+
continue-on-error: true
242+
uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2
243+
with:
244+
junit-paths: "apps/code/junit.xml"
245+
org-slug: posthog-inc
246+
token: ${{ secrets.TRUNK_API_TOKEN }}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,8 @@ apps/mobile/ROADMAP.md
8383

8484
# pi project-local config (per-developer MCP servers, prompts, etc.)
8585
.pi/
86+
87+
# Storybook visual regression: PNGs are never committed. CI uploads captures
88+
# to the PostHog Visual Review product; the committed baseline is the signed
89+
# hash manifest apps/code/snapshots.yml.
90+
apps/code/.storybook/__snapshots__/

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ cp .env.example .env
3434

3535
### Running in Development
3636

37-
By default, `pnpm dev` uses phrocs (our custom process runner) to run the agent and code app in parallel. phrocs auto-installs on first run and reads the `mprocs.yaml` config file. The binary is downloaded to `bin/phrocs` and is git-ignored.
37+
By default, `pnpm dev` uses phrocs (our custom process runner) to run the agent and code app in parallel. phrocs auto-installs and keeps itself up to date: on every `pnpm install` the local binary is checked against the latest `phrocs-latest` release checksums and re-downloaded if it differs (skipped when offline or in CI). `pnpm dev` only downloads it if it is missing entirely. It reads the `mprocs.yaml` config file. The binary lives at `bin/phrocs` and is git-ignored.
3838

3939
```bash
4040
# Run both agent (watch mode) and code app in parallel
@@ -44,8 +44,6 @@ pnpm dev
4444
pnpm dev:agent # Run agent in watch mode
4545
pnpm dev:code # Run code app
4646

47-
# Manually update phrocs to the latest version
48-
pnpm update:phrocs
4947

5048
# Use mprocs instead of phrocs
5149
pnpm dev:mprocs

apps/code/.storybook/main.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ const config: StorybookConfig = {
100100
find: "@posthog/electron-trpc/renderer",
101101
replacement: path.resolve(__dirname, "./mocks/electron-trpc.ts"),
102102
},
103+
// The agent dist bundles are Node-targeted: tsup opens every file
104+
// with a createRequire(import.meta.url) shim, and tool-use-to-acp
105+
// imports fs/path. Shim those builtins so the bundles load in the
106+
// browser (see mocks/node-module.ts).
107+
{
108+
find: /^(node:)?module$/,
109+
replacement: path.resolve(__dirname, "./mocks/node-module.ts"),
110+
},
111+
{
112+
find: /^(node:)?fs$/,
113+
replacement: path.resolve(__dirname, "./mocks/node-fs.ts"),
114+
},
115+
{ find: /^(node:)?path$/, replacement: "pathe" },
103116
// Resolve the remaining @posthog/* workspace packages to source, exactly
104117
// like the renderer (vite.shared.mts). Without this, Storybook resolves
105118
// them through each package's "./*" exports map, which only falls

0 commit comments

Comments
 (0)