Skip to content

feat(ui): shared page header, space breadcrumbs, artifact views - #3922

Merged
trunk-io[bot] merged 14 commits into
mainfrom
page-titles-and-artifacts
Jul 29, 2026
Merged

feat(ui): shared page header, space breadcrumbs, artifact views#3922
trunk-io[bot] merged 14 commits into
mainfrom
page-titles-and-artifacts

Conversation

@adamleithp

@adamleithp adamleithp commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Behind project-bluebird / the spaces layout — every adopted surface keeps its old header on the flag-off branch.

2026-07-29 11 28 55 2026-07-29 11 28 30 2026-07-29 11 28 14

UX changes

Shared page header

  • New compound PageHeader primitive: full-bleed shell, heading, title, chip, description, actions, sub-nav, filters. Pages take only the parts they need.
  • Adopted on Inbox, Activity, Loops, and the space Artifacts / Context / Loops pages, so titles, descriptions and tab strips line up across the app.
  • Inbox tabs keep their place in the header, with the reviewer-scope select as a filter to their right; Loops' My/Team tabs moved up into the header too.
  • Context gained a header it never had (title + version chip); Command Center dropped its empty breadcrumb row.

Space wayfinding

  • Every space page now reads {space} / {page} — Feed, Context, Loops, Artifacts, Recents, Canvases — with the space segment linking home.
  • Labels and icons come from one CHANNEL_PAGES table shared with the sidebar rows, so a page can't be named two things. A test fails if a routable section has no entry.
  • Loops attached to a space read {space} / Loops / {loop}, with Loops linking back to the space's loops tab — previously a dead end, since loops live outside the space routes.
  • Every breadcrumb segment is now a button with identical padding and height; segments with nowhere to go are aria-disabled and out of the tab order, but keep full opacity and a normal cursor so they read as text, not as blocked controls.
  • Renaming a task or canvas is one click (was double-click), and the inline editor matches the segment's size and fills the row — no jump when it opens.
  • Tooltips and title attributes removed from fixed segments; only the renamable leaf keeps one, since it can truncate.

Artifacts

  • List / grid / masonry view switcher (quill ButtonGroup), persisted per device.
  • Grid and masonry render live canvas previews, lazily mounted near the viewport and unmounted when they scroll away. Masonry heights are stable per canvas.
  • PR artifacts show their lifecycle icon and state; canvases keep their delete-undo behaviour in card form.

Notes

  • FreeformPreview moved out of the dashboards grid so both surfaces share one implementation.
  • Legacy branches are marked in comments for deletion when the flags graduate.
  • 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

adamleithp and others added 4 commits July 28, 2026 19:42
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
@trunk-io

trunk-io Bot commented Jul 29, 2026

Copy link
Copy Markdown

😎 Merged successfully - details.

Comment on lines +101 to +107
spacesLayout && contextTarget ? (
<LoopSpaceBreadcrumb
folderId={contextTarget.folder_id}
spaceName={contextTarget.name}
leafLabel={loop?.name ?? "Loop"}
/>
) : null,

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.

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

Comment on lines +248 to +250
<AlertDialogDescription>
Permanently delete <span className="font-medium">{name}</span>?
This deletes its code and version history for everyone in the

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 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!

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
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
@adamleithp adamleithp added the Stamphog This will request an autostamp by stamphog on small changes label Jul 29, 2026
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

React Doctor found 1 issue in 1 file · 1 warning.

1 warning

src/features/loops/components/LoopDetailView.tsx

Reviewed by React Doctor for commit 16f1472.

@stamphog

stamphog Bot commented Jul 29, 2026

Copy link
Copy Markdown

Note

🤖 stamphog reviewed 9285cf0b231b07f7f49c79462d07c095b02c1095 — verdict: REFUSED

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.

  • Author wrote 24% of the modified lines and has 27 merged PRs in these paths (familiarity MODERATE).
  • 👍 on the PR from greptile-apps[bot].
  • Gate size check failed: too large for auto-review (2679L/32F substantive, ceiling 1200L) — split into smaller PRs
  • Unresolved P1 comment from @greptile-apps on LoopDetailView.tsx (and LoopForm.tsx): disabling the spaces flag drops the identifying loop header — needs a fix before re-request
  • Unresolved P2 comment on WebsiteDashboardsIndex.tsx: delete confirmation copy contradicts the 8s undo window
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size too large for auto-review (2679L, 32F substantive, 2963L/37F incl. docs/generated/snapshots — ceiling is 1200L)
tier T1-agent / T1d-complex (2963L, 37F, single-area, feat)
stamphog 2.0.0b3 .stamphog/policy.yml @ 3b00ef5 · reviewed head 9285cf0

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 29, 2026
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The stamphog gate correctly identified two concrete bugs. Here's what's wrong:


P1 — Loop headers disappear when project-bluebird flag is off

Both LoopDetailView.tsx and LoopForm.tsx now pass null to useSetHeaderContent whenever spacesLayout is falsy:

spacesLayout && contextTarget ? <LoopSpaceBreadcrumb  /> : null

Before this PR, the flag-off branch showed a real header:

  • LoopDetailView: RepeatIcon + loop name
  • LoopForm: "Edit {name}" / "New loop" text

Now it shows nothing. The fix is to reinstate the old markup in the else branch of each useMemo.

In LoopDetailView.tsx, the useMemo body should be:

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 RepeatIcon import that was removed.)

In LoopForm.tsx:

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 AlertDialogDescription says "cannot be undone", but confirmDelete calls deleteCanvasWithUndo, which gives the user 8 seconds to undo. Two options:

  1. Fix the copy to match the actual behaviour:

    "This deletes its code and version history for everyone in the channel. You'll have a few seconds to undo."

  2. Or, if the intent of the confirmation dialog is to bypass the undo window (i.e. double confirmation → immediate hard delete), then confirmDelete should call deleteDashboard directly rather than deleteCanvasWithUndo.


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.

@posthog

posthog Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

👋 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
@adamleithp
adamleithp force-pushed the page-titles-and-artifacts branch from ab032c1 to 6f47a34 Compare July 29, 2026 10:57
@adamleithp
adamleithp changed the base branch from main to canvas-delete-undo July 29, 2026 11:01
@adamleithp
adamleithp changed the base branch from canvas-delete-undo to main July 29, 2026 11:02

@puemos puemos left a comment

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.

The only blocking issue is the inconsistent heights of the cards

Comment thread packages/shared/src/analytics-events.ts Outdated
| "canvas_mode_toggle"
| "activity_tab_change";
| "activity_tab_change"
/** Switched the artifacts list between list / grid / masonry. */

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.

let's remove this comment

Comment thread packages/shared/src/analytics-events.ts Outdated
/** For activity_tab_change: the tab landed on. */
tab?: string;
/** For artifacts_view_change: the layout landed on ("list"|"grid"|"masonry"). */
view_mode?: string;

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.

should we use better typing?

Suggested change
view_mode?: string;
view_mode?: "list" | "grid" | "masonry";

| "open"
| "create"
| "delete"
/** The delete was undone inside its undo window, so nothing was removed. */

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.

let's remove this comment

</Text>
</div>
{unreadCount > 0 && (
const markAllReadButton =

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.

useMemo?

</Button>
) : null;

const feed = (

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.

useMemo?

// 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);

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.

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();

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.

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({

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.

Image

card height is not consistent when there's no preview

);
}

function PreviewPlaceholder({ label }: { label: string }) {

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.

not loving the empty state

Image


<div className="min-h-0 flex-1 overflow-y-auto">
{/* The list reads best narrow; card layouts want the full width. */}
<div

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.

It's not part of this, but I'm not loving the fact that it's centered.

Image

adamleithp and others added 8 commits July 29, 2026 12:10
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
@trunk-io
trunk-io Bot merged commit 0227c99 into main Jul 29, 2026
32 of 34 checks passed
@trunk-io
trunk-io Bot deleted the page-titles-and-artifacts branch July 29, 2026 12:20
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.

2 participants