Skip to content

feat(canvas): diff-aware guarded source edits - #73731

Closed
k11kirky wants to merge 1 commit into
posthog-code/canvas-build-pipeline-phase-3from
posthog-code/canvas-build-pipeline-phase-4
Closed

feat(canvas): diff-aware guarded source edits#73731
k11kirky wants to merge 1 commit into
posthog-code/canvas-build-pipeline-phase-3from
posthog-code/canvas-build-pipeline-phase-4

Conversation

@k11kirky

@k11kirky k11kirky commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

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)
  • Cross-repo: each posthog PR should deploy before the same-phase code PR ships (skills/tools/endpoints must exist before clients rely on them).

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 tool desktop-file-system-canvas-edit-create): per-file operations — each sets a file's complete content, or deletes it when content is null — applied in order to the canvas's current source project.
  • The guard is mandatory here. A whole-project publish can be unguarded (it's self-contained); a diff's meaning depends on its base, so expected_current_version_id is a required field and a stale base gets the same 409 version_conflict recovery 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.
  • Edits are atomic: a delete targeting a missing file, or an edited project that fails validation, rejects the whole request with diagnostics and publishes nothing.
  • Reuses the shared publish path from phase 3 (upload-then-commit source-version recording, build queuing), so diff edits get the identical lifecycle. The validating-and-publishing-canvases skill 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.
  • Full posthog/api/file_system suite: 182 passed (phases 1–3 behavior unchanged). Repo-wide mypy clean, ruff clean, OpenAPI --fail-on-warn passes, MCP snapshots + generate-tools green, skills lint clean.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

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.

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
@k11kirky k11kirky self-assigned this Jul 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hey @k11kirky! 👋

It looks like your git author email on this PR isn't your @posthog.com address (k11kirky@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

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 budget

How 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 import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files no change ███░░░░░░░ 27.6% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.08 MiB · 3,014 files no change ████████░░ 83.2% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
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

Copy link
Copy Markdown
Contributor Author

Superseded by #73874, which consolidates the full Canvas build pipeline into one reviewable PR.

@k11kirky k11kirky closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant