feat(canvas): build and securely deliver cloud artifacts - #3835
feat(canvas): build and securely deliver cloud artifacts#3835k11kirky wants to merge 27 commits into
Conversation
|
React Doctor found 4 issues in 1 file · 4 warnings. 4 warnings
Reviewed by React Doctor for commit |
7c3e20f to
af2a629
Compare
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
Too many files changed for review. ( Bypass the limit by tagging |
0d9ed53 to
c9c7332
Compare
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 2 · PR risk: 0/10 |
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
|
🤖 CI You Later — automated babysit pass. CI is fully green on I did not push anything. Resolving this needs a rebase + force-push (GitHub can't auto-merge, so the signed-merge API refuses), and rewriting your commits is outside what I'm allowed to do unattended. So here is the resolution I derived and verified locally instead. The conflictOne commit on 5 files conflict. Resolution
Patch for the non-mechanical parts (apply after resolving as above)diff --git a/packages/core/src/canvas/dashboardSchemas.ts b/packages/core/src/canvas/dashboardSchemas.ts
index 8ccabb805..4a18c7e36 100644
--- a/packages/core/src/canvas/dashboardSchemas.ts
+++ b/packages/core/src/canvas/dashboardSchemas.ts
@@ -15,8 +15,13 @@ export const dashboardRecordSchema = z.object({
// Id of the task currently generating this canvas (freeform gen runs as a
// dedicated task, like CONTEXT.md). null/absent = no generation in flight.
generationTaskId: z.string().nullish(),
- // Display name of the creator (from the backend's created_by user).
+ // Display name of the creator (from the backend's created_by user). Display
+ // only — a name is not an identity; ownership uses createdByUuid.
createdBy: z.string().optional(),
+ // Stable uuid of the creator (from the backend's created_by user). This, not
+ // the display name, decides personal-channel ownership. Absent for rows the
+ // API returns without a creator (e.g. once the creator is deleted).
+ createdByUuid: z.string().optional(),
createdAt: z.number(),
updatedAt: z.number(),
// Epoch ms the canvas was pinned to its channel; absent = not pinned.
diff --git a/packages/core/src/canvas/dashboardsService.test.ts b/packages/core/src/canvas/dashboardsService.test.ts
index 007b74bd4..f60e25919 100644
--- a/packages/core/src/canvas/dashboardsService.test.ts
+++ b/packages/core/src/canvas/dashboardsService.test.ts
@@ -75,6 +75,30 @@ describe("DashboardsService.list", () => {
});
expect(rows[0].createdAt).toBe(Date.parse("2026-07-01T00:00:00Z"));
});
+
+ it("maps the backend creator uuid onto records", async () => {
+ const { api } = fakeApi({
+ "canvases/?channel=chan-1": [
+ apiCanvas({ created_by: { uuid: "creator-uuid" } }),
+ ],
+ });
+
+ const [result] = await new DashboardsService(api).list("chan-1");
+
+ expect(result.createdByUuid).toBe("creator-uuid");
+ });
+
+ // The backend sends `created_by: null` once the creator is deleted. Leaving
+ // the uuid undefined is what makes such a row fail closed out of #me.
+ it("leaves the creator uuid undefined when the row has no creator", async () => {
+ const { api } = fakeApi({
+ "canvases/?channel=chan-1": [apiCanvas({ created_by: null })],
+ });
+
+ const [result] = await new DashboardsService(api).list("chan-1");
+
+ expect(result.createdByUuid).toBeUndefined();
+ });
});
describe("DashboardsService.ensureHomeCanvas", () => {
diff --git a/packages/core/src/canvas/dashboardsService.ts b/packages/core/src/canvas/dashboardsService.ts
index 650ce8d3c..facb66046 100644
--- a/packages/core/src/canvas/dashboardsService.ts
+++ b/packages/core/src/canvas/dashboardsService.ts
@@ -51,6 +51,7 @@ interface ApiCanvas {
current_version_id: string | null;
published_build_id: string | null;
created_by?: {
+ uuid: string;
first_name?: string | null;
last_name?: string | null;
email?: string | null;
@@ -92,6 +93,9 @@ function toRecord(api: ApiCanvas): DashboardRecord {
context: api.context ?? "",
generationTaskId: api.generation_task_id,
createdBy: creatorLabel(api.created_by),
+ // Undefined when the API returns no creator, which is what makes such a row
+ // fail closed out of the personal channel (see isOwnedBy in channelItems).
+ createdByUuid: api.created_by?.uuid,
createdAt: toEpoch(api.created_at) ?? 0,
updatedAt: toEpoch(api.updated_at) ?? 0,
pinnedAt: toEpoch(api.pinned_at),Verified on the resolved tree: Still needs you
I'll re-check on the next pass and won't repeat this comment. |
6387168 to
f31828d
Compare
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
eb4e2f8 to
73fd317
Compare
|
👋 Visual changes detected for this PR. Review and approve in PostHog Visual Review If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix. |
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
The `quality` CI job (`biome ci .`) failed with two format errors on lines this branch added: a wrapped `expect(...).not.toContain(...)` call in prompt-builder.test.ts and the collapsed-panel JSX guard in FreeformCanvasView.tsx. Reformatted with `biome format --write` so `biome ci` exits clean; no behaviour change. Generated-By: PostHog Code Task-Id: 4928140a-2ab8-4fb3-86c1-e02d25f60cca
…ffect react-doctor blocked CI with a `no-adjust-state-on-prop-change` error at CanvasBuildStatus.tsx: the effect called `setNow(Date.now())` to restart the clock when a new build became active, so the first commit after a build change briefly rendered the previous build's elapsed time. The `now` state was only ever read to compute the label, so drop it: the interval now just bumps a tick to force a re-render and the label is computed from `Date.now()` during render. This removes the duplicated state (one of the two remedies react-doctor suggests) rather than adding a `prev`-prop comparison, and it also means the label can never be stale. react-doctor and `biome ci` are clean; @posthog/ui typecheck plus the @posthog/ui (2323) and @posthog/core (2595) unit suites pass. Generated-By: PostHog Code Task-Id: 4928140a-2ab8-4fb3-86c1-e02d25f60cca
…build status, home seed (#3931)
Generated-By: PostHog Code Task-Id: 53286c7c-92b5-42a3-8e73-7f45d68382c8
Generated-By: PostHog Code Task-Id: 53286c7c-92b5-42a3-8e73-7f45d68382c8
Generated-By: PostHog Code Task-Id: 53286c7c-92b5-42a3-8e73-7f45d68382c8
Generated-By: PostHog Code Task-Id: 53286c7c-92b5-42a3-8e73-7f45d68382c8
8 updated Run: 556c77f3-ca53-4b8c-b076-ce14c09c6b1b Co-authored-by: k11kirky <4571455+k11kirky@users.noreply.github.com>
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Generated-By: PostHog Code Task-Id: 45da9f78-5b15-4cfe-a8a5-9afcbd6b4755
Reuse: use escapeXmlAttr from @posthog/shared for prompt metadata; derive platform pins (react version, canvasSdkVersion) from CANVAS_PLATFORM_MANIFEST; route the dashboards-grid delete through deleteCanvasWithUndo. Simplification: single build-record mapping shared by toBuildRecord and tryToBuildRecord; de-duplicate the ActivityView feed body across layouts; drop the freeformChatStore LRU/mount bookkeeping now that per-thread state is tiny; inline the one-line canvasSourcePresentation helpers; delete the orphaned desktop file-system client methods. Efficiency: stop re-validating build records in polled getBuilds; bound canvas data payloads without a per-request TextEncoder; keep onDataRequest referentially stable across builds polls; resolve the home canvas from the query cache before hitting ensureHomeCanvas. Altitude: extract a shared canvas host message router used by BuiltCanvas and FreeformCanvas (both paths now get the concurrency/size/timeout guards); move capability gating onto BuiltCanvas via a capabilities prop; extract the pinned-artifact lifecycle into usePinnedArtifact. Generated-By: PostHog Code Task-Id: aea6c5f4-02ae-448b-b4dc-f10f581ad597
ea7a3c6 to
06f431f
Compare
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
|
Hey @k11kirky, we've migrated this repo into the PostHog/posthog monorepo, so this PR needs to be recreated there. Easiest path: check out the monorepo and run the Shout if you get stuck. |
…cates Channel-composer generation tasks no longer arrive with a pre-created canvas id (PostHog/code#3835), so the building-canvases skill's resolve step now spells out the flow: list the channel's canvases, build on a clearly-matching one and say so in the reply, and only otherwise create — with a short descriptive name, never "Untitled canvas". Generated-By: PostHog Code Task-Id: b548ad79-52a8-496c-a064-6dfc4c61b688
Problem
Canvases rode on the desktop "file system": the record was an untyped
metablob (code, version snapshots, pointers) merged read-modify-write, and every channel feature did two lookups — the file-system folder plus a backend channel resolved by name. The build pipeline's output was also invisible in practice: the builds poll couldn't start itself for agent-driven builds, every poll tick reloaded the iframe, and build failures hid in an edit-mode-only tooltip. The backend remodel (PostHog/posthog#73874) retires that surface; this PR moves the client onto it and fixes the build-lifecycle UX.Changes
ProjectApiClient+ rewrittenDashboardsService); source lives in server-side versions (source/versions/revert), the rendered output is the published build's artifact. The freeform store shrinks to view state; undo/redo browse server versions and revert republishes (and rebuilds).useBackendChannel) and the file-system client are deleted. The home canvas is anis_homerow seeded via a real publish; its board readssystem.canvases/system.tasks.canvas-*/channel-*tools; the unused local-preview pipeline (canvas-preview, the workspace-server canvas builder, its divergent contract) is deleted, with the platform manifest vendored from the server's single source of truth for the whitelist contract test;assertCanvasCapabilitymoves to@posthog/core. Net −2k lines.Pairs with PostHog/posthog#73874 — land the backend first, this same day (the old routes are deleted server-side).
Canvas-mode submits no longer pre-create a canvas: the channel composer used to mint an "Untitled canvas" before starting the generation task. It now just starts the task and the agent resolves the target itself — building on a matching existing canvas in the channel or creating a descriptively-named one (the
building-canvasesskill's resolve step). While a generation runs on a not-yet-renderable canvas, the run's chat panel opens by default, in view mode too. Cloud “Generating” state now ends with the agent's turn (via the session's prompt-pending signal) instead of persisting through the sandbox's follow-up linger window until the inactivity timeout.Heads-up for existing sessions: the server grows a
canvasscope, and OAuth grants narrowed to an app's scope ceiling enumerate their scopes at grant time (refresh never widens them) — so pre-existing sessions would 403 on every canvases call.OAUTH_SCOPE_VERSIONis bumped so those sessions re-auth once and pick the scope up.Rebased onto the
code-spaces-layoutwork: the new spaces sidebar (search-driven Autocomplete list, hotkey slots, activity hover popover) is adapted to this PR's data layer — the personal channel is found bychannel_typeinstead of lazily provisioned client-side, starring uses the per-userstarredflag, unread state keys by channel id, and the activity feed's name→id channel bridge is removed in favor of the channel id the items now carry.How did you test this?
Automated: full monorepo typecheck; Biome clean;
check-host-boundaries(no new violations); 174 core canvas tests + 252 UI canvas tests + 681 shared tests, all updated to the new behavior (service/API mapping, home-canvas seeding incl. declared capabilities and TSX-valid seed, build lifecycle normalization, whitelist↔manifest contract, hooks over the new channel endpoints, store/view-state suites). Not done: no manual end-to-end run against a deployed backend from this session — the generate → build → artifact flow should get one manual pass in the dev stack before the pair merges.Created with PostHog Code