feat(canvas): diff-aware guarded source edits - #73731
Conversation
Phase 4 of the canvas application build pipeline plan: a per-file edit endpoint (POST canvas/edit) and MCP tool so agents publish small changes without resending the whole project. Each operation sets a file's complete content or deletes it; operations apply to the head the caller read, the edited project is validated before anything commits, and expected_current_version_id is mandatory — a relative edit against an unverified base could silently merge into someone else's newer work, so unguarded diff publishes are refused outright. Shares the publish path (upload-then-commit lifecycle recording, build queuing) with the whole-project tool. Generated-By: PostHog Code Task-Id: 9e9a7b3c-f90d-4867-aa0d-b9acc83e26e1
|
Hey @k11kirky! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 64.44 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 281.5 KiB | ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 236.0 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 226.1 KiB | ../node_modules/.pnpm/posthog-js@1.407.2/node_modules/posthog-js/dist/module.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 106.2 KiB | src/lib/api.ts |
| 94.0 KiB | ../packages/quill/packages/quill/dist/index.js |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 90.6 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Toolbar bundle — eager 2.18 MiB within budget
What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.
| Metric | Size | Δ vs base | Budget |
|---|---|---|---|
| Eager (shipped) entry + static imports |
2.18 MiB · 17 files | no change | ████░░░░░░ 38.1% of 5.72 MiB |
| Deferred (lazy) | 2.07 MiB · 33 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.1 KiB | no change | █░░░░░░░░░ 5.8% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 716.5 KiB | dist/toolbar/toolbar-app-HMV4VZ5O.css |
| 545.2 KiB | dist/toolbar/chunk-chunk-3RADJBLD.js |
| 484.3 KiB | dist/toolbar/chunk-chunk-ZXJK34VQ.js |
| 133.6 KiB | dist/toolbar/chunk-chunk-6SQZIKIH.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-T5KY5WYR.js |
| 71.0 KiB | dist/toolbar/toolbar-app-G6ARZY2E.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-27JL52RE.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-P4AKBHPC.js |
| 20.9 KiB | dist/toolbar/chunk-chunk-B7POBA4G.js |
| 12.2 KiB | dist/toolbar/chunk-chunk-PIK3PADE.js |
Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile
✅ Dist folder size — 🔺 +5.8 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1355.92 MiB · 🔺 +5.8 KiB (+0.0%)
ℹ️ MCP UI apps size — 32 app(s), 17065.5 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 599.5 KB | 187.7 KB |
| action | 457.8 KB | 187.7 KB |
| action-list | 564.3 KB | 187.7 KB |
| cohort | 456.8 KB | 187.7 KB |
| cohort-list | 563.3 KB | 187.7 KB |
| email-template | 456.6 KB | 187.7 KB |
| error-details | 472.4 KB | 187.7 KB |
| error-issue | 457.5 KB | 187.7 KB |
| error-issue-list | 564.2 KB | 187.7 KB |
| experiment | 561.5 KB | 187.7 KB |
| experiment-list | 565.1 KB | 187.7 KB |
| experiment-results | 563.2 KB | 187.7 KB |
| feature-flag | 567.1 KB | 187.7 KB |
| feature-flag-list | 570.9 KB | 187.7 KB |
| feature-flag-testing | 461.0 KB | 187.7 KB |
| insight-actors | 562.1 KB | 187.7 KB |
| invite-email-preview | 456.0 KB | 187.7 KB |
| llm-costs | 559.5 KB | 187.7 KB |
| session-recording | 458.6 KB | 187.7 KB |
| session-summary | 463.9 KB | 187.7 KB |
| survey | 458.4 KB | 187.7 KB |
| survey-global-stats | 562.2 KB | 187.7 KB |
| survey-list | 565.0 KB | 187.7 KB |
| survey-stats | 562.2 KB | 187.7 KB |
| trace-span | 457.2 KB | 187.7 KB |
| trace-span-list | 564.2 KB | 187.7 KB |
| workflow | 457.1 KB | 187.7 KB |
| workflow-list | 563.7 KB | 187.7 KB |
| loops-review | 461.2 KB | 187.7 KB |
| query-results | 745.5 KB | 187.7 KB |
| render-ui | 826.2 KB | 187.7 KB |
| visual-review-snapshots | 461.6 KB | 187.7 KB |
|
Superseded by #73874, which consolidates the full Canvas build pipeline into one reviewable PR. |
Note
Stacked PR chain (Graphite-style) — merge in this order:
posthog/posthog: #73725 (phase 1) → #73730 (phase 3) → #73731 (phase 4)PostHog/code: #3824 (phase 1) → #3825 (phase 2) → #3826 (phase 3) → #3827 (phase 4)Problem
Phase 4 of the canvas application build pipeline plan (PostHog/code#3823): "add source editing and diff-aware agent edits without weakening guarded publishing". Today an agent changing one line must resend the complete source project — wasteful for large projects and a place where partial rewrites can silently drop files.
Note
Stacked PR — merge order: #73725 (phase 1) → #73730 (phase 3) → this PR (phase 4). Base branch is
posthog-code/canvas-build-pipeline-phase-4's parent,posthog-code/canvas-build-pipeline-phase-3.Changes
POST …/{id}/canvas/edit/(+ MCP tooldesktop-file-system-canvas-edit-create): per-file operations — each sets a file's complete content, or deletes it whencontentis null — applied in order to the canvas's current source project.expected_current_version_idis a required field and a stale base gets the same 409version_conflictrecovery loop. The handler applies operations to the head it reads, and the row-locked guard guarantees that head is exactly the one the caller's expected id names — no torn merges.validating-and-publishing-canvasesskill now documents both publish shapes.How did you test this code?
I'm an agent; automated tests only, run locally against Postgres + ClickHouse:
test_canvas_edit.py(5 tests): edit publishes a new guarded version without resending the project, unguarded edits are refused by the serializer (the phase's core invariant), stale-base 409 leaving the canvas untouched, delete-of-missing-file rejecting the whole edit atomically, and an edit producing an invalid project surfacing diagnostics without publishing.posthog/api/file_systemsuite: 182 passed (phases 1–3 behavior unchanged). Repo-wide mypy clean, ruff clean, OpenAPI--fail-on-warnpasses, MCP snapshots + generate-tools green, skills lint clean.Automatic notifications
Docs update
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored by Claude continuing the PostHog/code#3823 phase delivery, directed by @k11kirky. Skills invoked: /improving-drf-endpoints, /writing-tests. The remaining phase-4 items (package-policy expansion, dependency/update UX) are driven by observed production admission failures per the plan and are intentionally not speculated here; the code-side asset-inlining slice is PostHog/code#3827.