Skip to content

feat(canvas): show generation task nested under canvas in sidebar#2847

Open
raquelmsmith wants to merge 8 commits into
mainfrom
posthog-code/canvas-generation-task-row
Open

feat(canvas): show generation task nested under canvas in sidebar#2847
raquelmsmith wants to merge 8 commits into
mainfrom
posthog-code/canvas-generation-task-row

Conversation

@raquelmsmith

Copy link
Copy Markdown
Member

Problem

When a canvas has a generation task associated with it, there was no way to see that task from the channels sidebar. This surfaces the run inline so you can jump straight to it.

Why: Raquel wants the generating task visible right under its canvas for quick access.

Changes

  • Render the canvas's generation task beneath the canvas name in the channels sidebar: indented under the canvas, smaller font, with a down-then-right elbow arrow showing the relationship. Shows the task icon and title only — no draft/merged/queued status subtitle. Clicking opens the task.
  • Surface generationTaskId on the dashboard summary so the sidebar can render the run without a per-canvas get().

How did you test this?

Not yet manually tested — opening as draft for you to pull down and verify in the running app.

Automatic notifications

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

Created with PostHog Code

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 671d4e3.

@raquelmsmith raquelmsmith marked this pull request as ready for review June 23, 2026 23:34
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(canvas): keep nested task put while ..." | Re-trigger Greptile

// Tasks are private to their creator; one that isn't in our list can't be
// shown (or deduped) — leave it out.
if (!task) continue;
const data = deriveTaskData(narrowFullTask(task as unknown as FullTask), {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Task cast to FullTask bypasses type safety

task as unknown as FullTask sidesteps the compiler. If FullTask later gains a required field that Task doesn't carry, this will silently pass compilation and blow up at runtime inside narrowFullTask. The two types are currently structurally compatible (both have id, title, created_at, updated_at, origin_product, repository, latest_run), but a narrower overload of narrowFullTask — or a dedicated helper that accepts Task directly — would eliminate the escape hatch entirely.

Comment on lines 824 to 832
);

// A short status word under the title (running / merged / …), mirroring the
// task's live state. Falls back to the run status when there's no PR yet.
// task's live state. Repo-less local tasks (e.g. canvas generation) have no
// backend run record, so `taskRunStatus` is undefined once the turn ends —
// fall back to the live session so the row still shows a status line.
const status =
taskData?.isGenerating === true
? "running"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "connecting" session state mapped to "completed" status

When a local canvas-generation task's session is in the "connecting" state (the ACP handshake is in flight) but isPromptPending hasn't been set yet, taskData.isGenerating will be false. The fallback chain then reaches the session branch and maps any non-"error" status — including "connecting" — to "completed", briefly showing a wrong status. A guard that treats "connecting" as "running" (or just skips the session fallback until the session reaches a terminal state) would prevent the flash.

Comment on lines 735 to +745
</ContextMenuContent>
</ContextMenu>

{generationTask && dashboard.generationTaskId ? (
<CanvasGenerationTaskRow
channelId={channelId}
taskId={dashboard.generationTaskId}
task={generationTask}
channelTaskId={generationChannelTaskId}
channels={channels}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant dashboard.generationTaskId guard

generationTask is only ever non-undefined when showGen is true, which already requires genTaskId (i.e. dashboard.generationTaskId) to be truthy. Checking dashboard.generationTaskId again in the render condition is always vacuously true at that point — it can be dropped to say the same thing once.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

When a canvas has a generation task, render that task beneath the canvas
name in the channels sidebar: indented under the canvas with a
down-then-right elbow arrow, in a smaller font, showing only the task icon
and title (no draft/merged/queued status subtitle). Clicking it opens the
task.

Surfaces `generationTaskId` on the dashboard summary so the sidebar list can
render the run without a per-canvas get().

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
Show the generation task nested under its canvas only while the run is
still active; once it reaches a terminal state the row disappears. Also
dedupe: while a generation task is shown nested under its canvas, it no
longer appears in the channel's flat task list below.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
…ask actions

Rework when a canvas's generation task shows nested under the canvas:

- Show it while generating AND afterwards until the user has seen the
  result (unread), instead of only while the run is non-terminal. Canvas
  generation runs locally, so a stopped run never reaches a terminal
  latest_run.status — the old record-only check kept stopped tasks
  pinned. Keying off the session's isGenerating + unread fixes that: a
  stopped, viewed task now drops out, while a finished-but-unseen task
  stays put until opened.
- Decide nesting once, in bulk, via useNestedGenerationTaskIds (one
  sessions + timestamps read) so the nested row and the flat-list dedupe
  share a single source of truth and can't diverge.
- Give the nested row the same right-click menu as a regular filed task
  (File to… / Archive / Remove from channel) by extracting the shared
  TaskRowContextMenu used by both rows.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
…otice when done

Two fixes to canvas generation status:

- Nested sidebar row: stay until the user has actually seen the task,
  not just until it finishes. A never-viewed task counts as unseen, so a
  just-finished generation stays nested instead of vanishing the instant
  it completes; opening it (now also marks channel tasks viewed, which
  the website space wasn't doing) drops it back into the regular list,
  and a follow-up re-nests it while it regenerates.

- Canvas "Generating… View task" notice: a local ACP session stays
  "connected" after its single generation prompt finishes, so the notice
  (and disabled composer/undo) never cleared. Key the generating UI off
  the pending prompt instead, while still polling the record on the
  broader session-alive signal so a just-published canvas appears.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
Repo-less local tasks (canvas generation) have no backend run record, so
`taskRunStatus` is undefined once the turn ends and the channel TaskRow rendered
only the title. Fall back to the live session status — "failed" on error,
otherwise "completed" — so the moved-down row shows the same two-line treatment
(title + status) as other filed tasks.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
Opening a generation task from under its canvas marked it viewed, which dropped
it into the channel's regular list immediately — so it visibly jumped while the
user was still on its task view. Keep the currently-open task nested regardless
of viewed state; it moves into the flat list once the user navigates away.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
Viewed/unread state is stored per-device on the task's `workspaces` row, but
repo-less channel tasks (e.g. canvas generation) deliberately have no such row
— their working dir is a scratch dir. So `markViewed` updated zero rows and the
view was forgotten on reload, leaving a viewed generation task pinned under its
canvas forever.

Add a dedicated `task_metadata` table keyed by task id as the fallback home for
pin/view/activity timestamps. The metadata service routes by presence: tasks
that own a workspace row keep their exact current behavior; rowless tasks read
and write this table instead. Projections union both sources.

Generated-By: PostHog Code
Task-Id: 5345f523-bc53-4fa8-a3e8-f654aadc5015
Review feedback:
- Drop the `as unknown as FullTask` escape hatch: `narrowFullTask` now
  accepts the canonical `Task` directly, so the canvas hooks narrow without
  casting.
- Treat a session still mid-handshake ("connecting") as "running" in the
  task-row status line instead of flashing "completed".
- Remove the vacuously-true `dashboard.generationTaskId` guard on the nested
  row; use `generationTask.id`.

Cleanup:
- Extract a shared `TaskStatusIcon` so `TaskRow` and
  `CanvasGenerationTaskRow` build the status icon from one definition.

Flaky test:
- `event-stream-sender` "stops retrying after the stop deadline" used
  `stopTimeoutMs: 1`, racing the 5ms retry wait against timer granularity and
  occasionally sending a second request under CI load. Use `stopTimeoutMs: 0`
  so the deadline is hit deterministically after the first attempt.

Also Biome-format the generated migration snapshot/journal.

Generated-By: PostHog Code
Task-Id: caabd110-f966-47da-9912-171a5a1cb6e6
@raquelmsmith raquelmsmith force-pushed the posthog-code/canvas-generation-task-row branch from 0e9db59 to 671d4e3 Compare June 24, 2026 03:05
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