Skip to content

fix(canvas): register system.canvases and retire the home-canvas concept - #77176

Merged
trunk-io[bot] merged 4 commits into
masterfrom
posthog-code/register-system-canvases-hogql-table
Aug 4, 2026
Merged

fix(canvas): register system.canvases and retire the home-canvas concept#77176
trunk-io[bot] merged 4 commits into
masterfrom
posthog-code/register-system-canvases-hogql-table

Conversation

@k11kirky

@k11kirky k11kirky commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Problem

After the canvas remodel landed (#73874 backend, #76649 desktop client), every channel in the desktop app appeared to have lost all of its canvases.

The data was intact — the migration converted the old desktop file-system rows into first-class Canvas rows correctly, UUIDs preserved. The gap was in the read path: each channel's auto-created home canvas (the seeded board that lists the channel's canvases) queries system.canvases via HogQL, but that table was never registered in the HogQL schema. The query fails with Unknown table 'system.canvases' and the board's catch-all error handling silently renders "No canvases yet."

Investigating the incident also surfaced that the home-canvas concept itself is vestigial and fragile: the channel landing page is already a native view (WebsiteChannelHome), the only navigation into a home canvas (useOpenHomeCanvas) had zero callers, and the board is UI shipped as per-channel data — its code is frozen at publish time, can't be updated fleet-wide, makes channel-open depend on the build pipeline, and hides failures as empty states. So rather than only patching the query, this PR also retires the concept.

Changes

  • Register a canvases system table in the HogQL schema (posthog/hogql/database/schema/system.py), following the tasks table's conventions: scope-gated (access_scope="canvas"), soft-deleted rows excluded via a table predicate. Useful for SQL access to canvases generally, and how the incident was diagnosed.
  • Remove the home-canvas concept:
    • Desktop client: delete ensureHomeCanvas/resetHomeCanvas/publishHomeSeed/buildHomeCanvasCode, the tRPC routes, the dead useOpenHomeCanvas hook, the "Reset to default" affordance, and all isHome plumbing. useCanvasNavigation moves to its own file.
    • Backend: drop is_home from the serializers and views (create 409 race handling included), remove the field and the unique_home_canvas_per_channel constraint. Per safe-migration policy the constraint is dropped for real, the column gets a database-side default and is removed from Django state only; a later migration can drop it physically.
    • The migration soft-deletes existing auto-created home boards (is_home=Truedeleted=True, reversible). They were system-generated directory listings, not user content; user-created canvases are untouched.
    • Regenerated: OpenAPI/Orval types, MCP server types/tools/snapshots, HogQL database snapshot.
  • Preserve migrated canvases' source in history: the first publish to a migrated canvas nulled legacy_code without it ever becoming a version, so the pre-remodel source was unrecoverable. It is now materialized as a real parent version ("Imported source") on first publish.
  • Generated canvases must surface query failures: the authoring skills (building-react-quill-canvases, querying-canvas-data) now require a rejected query to render a visible error with a retry — distinct from a successful-but-empty result — and the starter scaffold demonstrates the pattern. A swallowed error is how this incident presented as data loss.

How did you test this code?

  • Red-green on the system.canvases table: with the schema change stashed, the new tests fail exactly like production (Unknown table); with it applied, all pass. Added a canvases case to the parameterized team-isolation suite plus SQL-shape and end-to-end tests that soft-deleted canvases are excluded (mirrors the existing tasks/sandbox_environments pattern) — catches the table being missing, cross-team leaks, and the deleted-filter being dropped.
  • Legacy-source preservation is red-green tested (TestLegacySourcePreservation): without the fix the publish leaves no parent version; with it, the legacy source round-trips from the materialized version. Canvas backend API tests pass against a fresh test DB; makemigrations --check clean; sqlmigrate verified the migration emits only DROP INDEX + ALTER COLUMN ... SET DEFAULT false (state-only field removal).
  • Desktop workspace: pnpm typecheck (24 packages), Biome lint, @posthog/core (3034) and @posthog/ui (2541) test suites pass.
  • HogQL database suite passes (119) with snapshots regenerated; MCP tool-schema snapshot test passes after regen.

Automatic notifications

  • Publish to changelog?

Docs update

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Started as an incident investigation ("all canvases are gone" after the remodel merged): verified via the canvases API that data survived, reproduced the home board's failing system.canvases query against production, and confirmed the table was referenced only by the desktop client with no backend registration. After reviewing the home-canvas design (nobody customizes the boards; the native channel home already exists; the seed is the only flow that overwrites canvas content without user intent), the DRI decided to remove the concept entirely. Skills invoked: /writing-tests, /django-migrations, /improving-drf-endpoints, /posthog-desktop. Known environment gap: test_cloud_builder tests require npm ci in canvas_builder (unrelated to this change).


Created with PostHog Code

The desktop channel home board lists a channel's canvases with
`SELECT ... FROM system.canvases WHERE channel_id = ...`, but the canvases
remodel never registered that table in the HogQL schema, so the query fails
with "Unknown table" and every home board silently renders "No canvases yet."

Register `system.canvases` over `posthog_canvas`, scope-gated like
`system.tasks`, excluding soft-deleted rows to mirror the REST API's
default filter.

Generated-By: PostHog Code
Task-Id: d75a71da-b662-4101-af83-0588664849c2
@trunk-io

trunk-io Bot commented Aug 4, 2026

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Aug 4, 2026

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. 🙂

@trunk-io

trunk-io Bot commented Aug 4, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@k11kirky
k11kirky marked this pull request as ready for review August 4, 2026 09:07
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team August 4, 2026 09:07
@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(canvas): register system.canvases Ho..." | Re-trigger Greptile

Comment thread posthog/hogql/database/schema/system.py
@veria-ai

veria-ai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Bundle size — 🔺 +1.3 KiB (+0.0%)

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

Total: 65.58 MiB · 🔺 +1.3 KiB (+0.0%)

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.25 MiB · 22 files 🔺 +574 B (+0.0%) ███░░░░░░░ 27.7% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.14 MiB · 3,036 files 🔺 +1.0 KiB (+0.0%) ████████░░ 83.8% 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
285.5 KiB ../node_modules/.pnpm/posthog-js@1.410.1/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
235.5 KiB src/taxonomy/core-filter-definitions-by-group.json
231.5 KiB ../node_modules/.pnpm/posthog-js@1.410.1/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
104.4 KiB src/lib/api.ts
94.7 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.19 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.19 MiB · 17 files 🔺 +96 B (+0.0%) ████░░░░░░ 38.3% of 5.72 MiB
Deferred (lazy) 2.08 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
718.1 KiB dist/toolbar/toolbar-app-GXPXCJTD.css
551.6 KiB dist/toolbar/chunk-chunk-2KIRIKZY.js
484.6 KiB dist/toolbar/chunk-chunk-PIOT3RXS.js
133.6 KiB dist/toolbar/chunk-chunk-NAYZ2JJF.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-66NMDAV4.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-4NYOFLKW.js
20.9 KiB dist/toolbar/chunk-chunk-OYRH6LH3.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 — 🔺 +507.0 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1390.04 MiB · 🔺 +507.0 KiB (+0.0%)

⚠️ Playwright — 1 flaky

🎭 Playwright report · View test results →

⚠️ 1 flaky test:

  • Editing an insight updates the dashboard tile (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

⚠️ Django migration SQL — 2 new migrations to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/canvas/backend/migrations/0007_soft_delete_home_canvases.py

BEGIN;
--
-- Raw Python operation
--
-- THIS OPERATION CANNOT BE WRITTEN AS SQL
COMMIT;

products/canvas/backend/migrations/0008_remove_home_canvas.py

BEGIN;
--
-- Remove constraint unique_home_canvas_per_channel from model canvas
--
DROP INDEX IF EXISTS "unique_home_canvas_per_channel";
--
-- Alter field is_home on canvas
--
ALTER TABLE "posthog_canvas" ALTER COLUMN "is_home" SET DEFAULT false;
--
-- Custom state/database change combination
--
-- (no-op)
COMMIT;

Last updated: 2026-08-04 10:21 UTC (e675e6e)

Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 0 Safe | 2 Needs Review | 0 Blocked

⚠️ Needs Review

May have performance impact

canvas.0007_soft_delete_home_canvases
  └─ #1 ⚠️ RunPython: RunPython data migration needs review for performance
canvas.0008_remove_home_canvas
  └─ #1 ⚠️ RemoveConstraint: Unscored Django operation: RemoveConstraint (needs manual review)
  └─ #2 ⚠️ AlterField
     Field alteration may cause table locks or data loss (check if changing type or constraints)
     model: canvas, field: is_home, field_type: BooleanField
  └─ #3 ✅ SeparateDatabaseAndState: Only state operations (no database changes)

📚 How to Deploy These Changes Safely

RunPython:

Use batching for large data migrations:

  • Use .iterator() to avoid loading all rows into memory
  • Use .bulk_update() instead of saving individual objects
  • Batch size: 1,000-10,000 rows per batch
  • Add pauses between batches
  • Consider background jobs for very large updates (millions of rows)

See the migration safety guide

Last updated: 2026-08-04 10:22 UTC (e675e6e)

The channel landing page is a native view already, and the only navigation
into a home canvas had no callers, so the auto-created seeded home board was
vestigial: UI shipped as per-channel data that can't be updated fleet-wide,
makes channel-open depend on the build pipeline, and hides query failures as
"No canvases yet." (how the missing system.canvases table read as data loss).

Remove the client machinery (ensure/reset/seed, tRPC routes, isHome plumbing),
the backend is_home field, filter, and unique constraint (constraint dropped
for real; column removed from Django state only with a db-side default first),
and soft-delete the system-generated home boards. User-created canvases are
untouched. Generated OpenAPI/MCP types and snapshots regenerated.

Generated-By: PostHog Code
Task-Id: d75a71da-b662-4101-af83-0588664849c2
@k11kirky k11kirky changed the title fix(canvas): register system.canvases HogQL table fix(canvas): register system.canvases and retire the home-canvas concept Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit e675e6e · box box-bb3612024155 · ready in 998s (push → usable) · build log · rebuilds on every push, torn down on close

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit e675e6e.

Comment thread posthog/hogql/database/schema/system.py
A migrated canvas carries its pre-remodel source in legacy_code, and the
first publish nulled that column while creating the new version from the
incoming project only — the old source never became a version, so undo and
revert could not reach it and the only copy was discarded.

Materialize legacy_code as a real parent version ("Imported source") before
the first post-migration publish, with the same upload-then-commit posture
as the main project.

Generated-By: PostHog Code
Task-Id: d75a71da-b662-4101-af83-0588664849c2
Generated boards swallowed query failures into empty states, so real
breakage (a missing table, an auth failure) rendered as "no data" — how the
unregistered system.canvases table read as data loss. The authoring skills
now require a rejected query to render a visible error with a retry,
distinct from a successful-but-empty result, and the starter scaffold
demonstrates the pattern.

Generated-By: PostHog Code
Task-Id: d75a71da-b662-4101-af83-0588664849c2
@k11kirky k11kirky added the skip-desktop-backend-check Allow independent desktop and backend changes in one PR label Aug 4, 2026 — with PostHog
@k11kirky k11kirky added the create desktop release Auto-release desktop on merge: desktop-tag.yml tags, desktop-release.yml builds and ships label Aug 4, 2026
@k11kirky

k11kirky commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

/trunk merge

@trunk-io
trunk-io Bot merged commit 311c00e into master Aug 4, 2026
385 of 391 checks passed
@trunk-io
trunk-io Bot deleted the posthog-code/register-system-canvases-hogql-table branch August 4, 2026 19:36
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Could not verify team-posthog-code membership (GitHub API error). Auto-release is limited to members of the team-posthog-code GitHub team, so this will ship with the next scheduled release instead.

@deployment-status-posthog

deployment-status-posthog Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-08-04 20:17 UTC Run
prod-us ✅ Deployed 2026-08-04 20:35 UTC Run
prod-eu ✅ Deployed 2026-08-04 20:40 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

create desktop release Auto-release desktop on merge: desktop-tag.yml tags, desktop-release.yml builds and ships skip-desktop-backend-check Allow independent desktop and backend changes in one PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants