feat(ui): shared page header, space breadcrumbs, artifact views - #3922
Conversation
The canvas "…" menu had no way to delete; the canvases grid had one that deleted on click with no confirmation. Both now open an alert dialog, and confirming doesn't delete straight away: the canvas is marked pending and the host isn't told until the "Deleted artifact" toast's timer expires, so Undo simply cancels the timer rather than recreating anything. Pending canvases stay in their lists — the artifacts row swaps its template icon for a pulsing trash can and stops opening, the grid card dims behind the same icon — so undoing restores them in place. The commit runs outside React (module-level timer + hostClient) because deleting from inside a canvas navigates away immediately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Introduce a compound PageHeader primitive (header shell, heading, title,
chip, description, actions, sub-nav, filters) and adopt it on Inbox,
Activity, Loops, the space Artifacts / Context / Loops pages. Each
adoption keeps its previous header on the flag-off branch: space pages
switch with the spaces layout (useChannelsLayout), Inbox and the global
Loops list with project-bluebird.
Space wayfinding now reads "{space} / {page}" everywhere, with labels and
icons resolved from one CHANNEL_PAGES table that the sidebar rows share,
so the two can't drift. ChannelBreadcrumb renders every segment as a
button (uniform padding/height), marks non-navigable segments aria-disabled
without the disabled dimming, gained an optional middle segment
("{space} / Loops / {loop}"), and opens the rename editor on a single
click at matching type scale.
Artifacts gains list / grid / masonry views with live canvas previews,
persisted per device; the freeform preview moves out of the dashboards
grid so both surfaces share it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
|
😎 Merged successfully - details. |
| spacesLayout && contextTarget ? ( | ||
| <LoopSpaceBreadcrumb | ||
| folderId={contextTarget.folder_id} | ||
| spaceName={contextTarget.name} | ||
| leafLabel={loop?.name ?? "Loop"} | ||
| /> | ||
| ) : null, |
There was a problem hiding this comment.
Flag-off loop headers disappear
When the spaces layout is disabled, this condition passes null to useSetHeaderContent instead of retaining the legacy loop title, causing the identifying header to disappear from loop detail views. The same regression affects create and edit forms in LoopForm.tsx.
Knowledge Base Used: Loops (recurring/triggered agent automations)
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/loops/components/LoopDetailView.tsx
Line: 101-107
Comment:
**Flag-off loop headers disappear**
When the spaces layout is disabled, this condition passes `null` to `useSetHeaderContent` instead of retaining the legacy loop title, causing the identifying header to disappear from loop detail views. The same regression affects create and edit forms in `LoopForm.tsx`.
**Knowledge Base Used:** [Loops (recurring/triggered agent automations)](https://app.greptile.com/posthog-org-19734/-/custom-context/knowledge-base/posthog/code/-/docs/loops.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| <AlertDialogDescription> | ||
| Permanently delete <span className="font-medium">{name}</span>? | ||
| This deletes its code and version history for everyone in the |
There was a problem hiding this comment.
Deletion copy contradicts undo
The confirmation says deletion cannot be undone, but confirming starts an eight-second undo window and displays an Undo action. This contradictory copy misrepresents the operation and discourages users from relying on the available recovery path.
Knowledge Base Used: @posthog/ui shared UI package
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/canvas/components/WebsiteDashboardsIndex.tsx
Line: 248-250
Comment:
**Deletion copy contradicts undo**
The confirmation says deletion cannot be undone, but confirming starts an eight-second undo window and displays an Undo action. This contradictory copy misrepresents the operation and discourages users from relying on the available recovery path.
**Knowledge Base Used:** [`@posthog/ui` shared UI package](https://app.greptile.com/posthog-org-19734/-/custom-context/knowledge-base/posthog/code/-/docs/ui-package.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.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!
Prompt To Fix All With AI### Issue 1
packages/ui/src/features/loops/components/LoopDetailView.tsx:101-107
**Flag-off loop headers disappear**
When the spaces layout is disabled, this condition passes `null` to `useSetHeaderContent` instead of retaining the legacy loop title, causing the identifying header to disappear from loop detail views. The same regression affects create and edit forms in `LoopForm.tsx`.
### Issue 2
packages/ui/src/features/canvas/components/WebsiteDashboardsIndex.tsx:248-250
**Deletion copy contradicts undo**
The confirmation says deletion cannot be undone, but confirming starts an eight-second undo window and displays an Undo action. This contradictory copy misrepresents the operation and discourages users from relying on the available recovery path.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(ui): shared page header, space brea..." | Re-trigger Greptile |
…acts # Conflicts: # packages/ui/src/features/canvas/components/WebsiteChannelLoops.tsx
|
/trunk merge |
|
React Doctor found 1 issue in 1 file · 1 warning. 1 warning
Reviewed by React Doctor for commit |
|
Note 🤖 stamphog reviewed Gates denied this PR for size (2679 substantive lines / 32 files, well over the 1200L ceiling), and it still has an unresolved P1 review finding (loop headers disappear when the flag is off, in LoopDetailView.tsx and LoopForm.tsx) that wasn't fixed before requesting merge.
Gate mechanics and policy version
|
|
The stamphog gate correctly identified two concrete bugs. Here's what's wrong: P1 — Loop headers disappear when Both spacesLayout && contextTarget ? <LoopSpaceBreadcrumb … /> : nullBefore this PR, the flag-off branch showed a real header:
Now it shows nothing. The fix is to reinstate the old markup in the In spacesLayout && contextTarget ? (
<LoopSpaceBreadcrumb
folderId={contextTarget.folder_id}
spaceName={contextTarget.name}
leafLabel={loop?.name ?? "Loop"}
/>
) : (
<Flex align="center" gap="2" className="w-full min-w-0">
<RepeatIcon size={12} className="shrink-0 text-gray-10" />
<Text
className="truncate whitespace-nowrap font-medium text-[13px]"
title={loop?.name ?? "Loop"}
>
{loop?.name ?? "Loop"}
</Text>
</Flex>
)(You'll also need to restore the In spacesLayout && contextTarget ? (
<LoopSpaceBreadcrumb
folderId={contextTarget.folderId}
spaceName={contextTarget.name}
leafLabel={headerLeaf}
/>
) : (
<Text className="font-medium text-[13px]">
{isEdit ? `Edit ${loop.name}` : "New loop"}
</Text>
)P2 — Confirmation dialog copy contradicts the undo behaviour The
Once those two are addressed the P1/P2 blockers are cleared. The size gate (2679L / 32 files) still needs to be resolved by splitting the PR before it will pass. |
|
👋 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. |
The sandbox document painted with a hard light fallback (background: var(--background, #fff)) during the window before its stylesheets loaded and before the host's init/set-theme message toggled `.dark` — so every canvas preview scrolling into view flashed white over a dark app. It now stays transparent until the tokens land, and the host sets color-scheme on the iframe so the UA's base canvas is dark too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
ab032c1 to
6f47a34
Compare
puemos
left a comment
There was a problem hiding this comment.
The only blocking issue is the inconsistent heights of the cards
| | "canvas_mode_toggle" | ||
| | "activity_tab_change"; | ||
| | "activity_tab_change" | ||
| /** Switched the artifacts list between list / grid / masonry. */ |
| /** For activity_tab_change: the tab landed on. */ | ||
| tab?: string; | ||
| /** For artifacts_view_change: the layout landed on ("list"|"grid"|"masonry"). */ | ||
| view_mode?: string; |
There was a problem hiding this comment.
should we use better typing?
| view_mode?: string; | |
| view_mode?: "list" | "grid" | "masonry"; |
| | "open" | ||
| | "create" | ||
| | "delete" | ||
| /** The delete was undone inside its undo window, so nothing was removed. */ |
| </Text> | ||
| </div> | ||
| {unreadCount > 0 && ( | ||
| const markAllReadButton = |
| </Button> | ||
| ) : null; | ||
|
|
||
| const feed = ( |
| // The shared page header ships behind bluebird; everyone else keeps the | ||
| // header this page has always had. Delete the legacy branch when the flag | ||
| // graduates. | ||
| const bluebird = useFeatureFlag(PROJECT_BLUEBIRD_FLAG, import.meta.env.DEV); |
There was a problem hiding this comment.
Nice to have:
i wonder if we should centralize this to useBluebirdFlag since we add this custom import.meta.env.DEV logic in multiple places
| }) { | ||
| const navigate = useNavigate(); | ||
| // The loop's stored name can go stale after a rename, so prefer the live one. | ||
| const { channels } = useChannels(); |
There was a problem hiding this comment.
No for now and might need a api change but this is not an efficient way to get a space name (loading all and find), unless they are already loaded, in which it's fine
| ); | ||
| } | ||
|
|
||
| function ArtifactCardShell({ |
| ); | ||
| } | ||
|
|
||
| function PreviewPlaceholder({ label }: { label: string }) { |
|
|
||
| <div className="min-h-0 flex-1 overflow-y-auto"> | ||
| {/* The list reads best narrow; card layouts want the full width. */} | ||
| <div |
The options menu kept quill's default width, which clipped "Unpin from channel" mid-word; it now sizes to its longest item like the channel-list menus do. The label itself follows the layout — space under the new one, channel under the old. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
The dialog said the delete "cannot be undone" while the action routes through deleteCanvasWithUndo, which holds it for 8s behind an Undo toast. Say what actually happens instead, and call the container a space under the new layout, like the menu above it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
- one useBluebirdFlag hook so the dev default isn't repeated per call site - type view_mode on the channel-action event instead of a bare string - drop the two redundant comments on the analytics event union - memoize the Activity feed and its mark-all-read button Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
- loop scenes with no space keep an identifying header instead of an empty row (greptile P1) - artifacts body left-aligns under its header rather than centring - grid cards stretch to a common height, so a PR tile no longer ends short beside a canvas thumbnail - canvas previews get a skeleton while deferred and an icon + "Nothing built yet" when empty, instead of a bare line of text - the Activity nav entry is a quill icon button (28px) like its neighbours, not a hand-rolled 32px one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
Replaces the ButtonGroup of outline buttons and its hand-rolled data-[active] styling: ToggleGroup owns the pressed state, so the selected view styles itself. Tooltips share one provider with no open delay, since the icons are the only labelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
The list was capped at 680px and the cards at 1400px, which stranded whitespace to the right of the header on a wide window. Every layout here is a scannable list rather than prose, so drop the caps; the grid and masonry gain a fourth column past 2xl now that they have the room. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
- call useChannelsLayout unconditionally, not inside a ternary, in the two places that derive a "space"/"channel" noun from it - move inboxTabFromPath / inboxScopeApplies to core beside the tab routes they read, so the tab bar file exports components only - render the Activity feed as a memo'd child instead of JSX built in a useMemo above the parent's early return Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr



Behind
project-bluebird/ the spaces layout — every adopted surface keeps its old header on the flag-off branch.UX changes
Shared page header
PageHeaderprimitive: full-bleed shell, heading, title, chip, description, actions, sub-nav, filters. Pages take only the parts they need.Space wayfinding
{space} / {page}— Feed, Context, Loops, Artifacts, Recents, Canvases — with the space segment linking home.CHANNEL_PAGEStable shared with the sidebar rows, so a page can't be named two things. A test fails if a routable section has no entry.{space} / Loops / {loop}, withLoopslinking back to the space's loops tab — previously a dead end, since loops live outside the space routes.aria-disabledand out of the tab order, but keep full opacity and a normal cursor so they read as text, not as blocked controls.titleattributes removed from fixed segments; only the renamable leaf keeps one, since it can truncate.Artifacts
ButtonGroup), persisted per device.Notes
FreeformPreviewmoved out of the dashboards grid so both surfaces share one implementation.packages/ui: typecheck clean, biome clean, 2327 tests pass (new coverage for breadcrumb segments, page-table drift, masonry heights, and the loops header/body tab split).🤖 Generated with Claude Code
https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr