diff --git a/__tests__/unit/components/repost-renders-as-a-post.test.tsx b/__tests__/unit/components/repost-renders-as-a-post.test.tsx
new file mode 100644
index 000000000..bcf4e19b5
--- /dev/null
+++ b/__tests__/unit/components/repost-renders-as-a-post.test.tsx
@@ -0,0 +1,92 @@
+/**
+ * A repost is shown as the post it is; a quote repost shows two posts.
+ *
+ * A simple repost used to suppress its own content and render the original
+ * inside a bordered panel instead — a panel repeating the original author's
+ * avatar and handle, which the post header directly above was ALREADY showing
+ * (PostCard swaps the reposter for the original author on a simple repost).
+ * One repost drew the same person twice, two lines apart, with the actual text
+ * boxed off underneath.
+ *
+ * The distinction these tests hold: a SIMPLE repost has one author and one
+ * body, so it renders flat. A QUOTE repost genuinely has two of each, so it
+ * keeps the nested panel. Collapsing both cases the same way would be the
+ * opposite bug.
+ */
+
+import { render, screen } from '@testing-library/react';
+import { PostContent } from '@/components/timeline/PostContent';
+import type { TimelineDisplayEvent } from '@/types/timeline';
+
+jest.mock('@/utils/markdown', () => ({
+ renderMarkdownToReact: (text: string) => text,
+}));
+
+const base = {
+ id: 'e1',
+ actor: { id: 'a1', name: 'Reposter', username: 'reposter', type: 'user' },
+ description: '',
+ metadata: {},
+} as unknown as TimelineDisplayEvent;
+
+function simpleRepost(): TimelineDisplayEvent {
+ return {
+ ...base,
+ description: '',
+ metadata: {
+ is_repost: true,
+ original_event_id: 'orig-1',
+ original_actor_name: 'Original Author',
+ original_actor_username: 'original',
+ original_description: 'The original words.',
+ },
+ } as unknown as TimelineDisplayEvent;
+}
+
+function quoteRepost(): TimelineDisplayEvent {
+ return {
+ ...base,
+ description: 'My take on this.',
+ metadata: {
+ is_repost: true,
+ is_quote_repost: true,
+ original_event_id: 'orig-1',
+ original_actor_name: 'Original Author',
+ original_actor_username: 'original',
+ original_description: 'The original words.',
+ },
+ } as unknown as TimelineDisplayEvent;
+}
+
+describe('a simple repost', () => {
+ it('shows the original text as the post body', () => {
+ render();
+
+ expect(screen.getByText('The original words.')).toBeInTheDocument();
+ });
+
+ it('does not repeat the original author, who is already in the header', () => {
+ render();
+
+ // PostCard renders the original author in the post header for a simple
+ // repost. PostContent must not draw them a second time.
+ expect(screen.queryByText('Original Author')).not.toBeInTheDocument();
+ expect(screen.queryByText('@original')).not.toBeInTheDocument();
+ });
+});
+
+describe('a quote repost', () => {
+ it('shows the quoter’s own words as the post body', () => {
+ render();
+
+ expect(screen.getByText('My take on this.')).toBeInTheDocument();
+ });
+
+ it('keeps the quoted original in its own panel, author and all', () => {
+ render();
+
+ // Two posts, two authors: here the panel earns its place.
+ expect(screen.getByText('Original Author')).toBeInTheDocument();
+ expect(screen.getByText('The original words.')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/timeline/PostContent.tsx b/src/components/timeline/PostContent.tsx
index 150b6173b..83fc8789b 100644
--- a/src/components/timeline/PostContent.tsx
+++ b/src/components/timeline/PostContent.tsx
@@ -107,8 +107,23 @@ export function PostContent({ event }: PostContentProps) {
)}
- {/* Event Description/Content */}
- {!articleSlug && displayContent && (!isRepost || isQuoteRepost) && (
+ {/*
+ A reposted post is shown as the post it is.
+
+ A simple repost used to suppress its own content here and render the
+ original inside a bordered panel below instead — a panel that repeated
+ the original author's avatar and handle, which the post header directly
+ above was ALREADY showing (PostCard swaps the reposter for the original
+ author on a simple repost). So one repost drew the same person twice,
+ two lines apart, with the actual text boxed off underneath. That is
+ what "reposts look ugly" was.
+
+ `getDisplayContent` already returns the original's text for a simple
+ repost, so it renders here like any other post. The nested panel is
+ kept for QUOTE reposts, where there genuinely are two posts and two
+ authors to tell apart.
+ */}
+ {!articleSlug && displayContent && (
{renderMarkdownToReact(displayContent)}
@@ -206,40 +221,6 @@ export function PostContent({ event }: PostContentProps) {
)}
{/* Simple Repost: show original post inside a quoted card for consistency */}
- {isRepost && !isQuoteRepost && event.metadata?.original_event_id && (
-
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element -- avatar_url is a free-form user URL (any host); next/image would throw for hosts outside images.remotePatterns */}
-
-
-
- )}
{/* Attached image — plain : Openverse hosts aren't in next/image remotePatterns */}
{postImage && !isRepost && (
diff --git a/src/components/timeline/TimelineComponent.tsx b/src/components/timeline/TimelineComponent.tsx
index 12c872c22..8a90501a0 100644
--- a/src/components/timeline/TimelineComponent.tsx
+++ b/src/components/timeline/TimelineComponent.tsx
@@ -11,6 +11,7 @@ import { usePostSelection } from '@/hooks/usePostSelection';
import EmptyState from '@/components/ui/EmptyState';
import { BulkActionsToolbar } from './BulkActionsToolbar';
import { BulkDeleteConfirmDialog } from './BulkDeleteConfirmDialog';
+import { TIMELINE_SURFACE } from '@/config/timeline';
interface TimelineComponentProps {
feed: TimelineFeedResponse;
@@ -186,16 +187,31 @@ export const TimelineComponent: React.FC = ({
{enableMultiSelect && (
<>
{!isSelectionMode ? (
- // Entry point to selection mode - small button
-
+ /*
+ The way IN to selection mode is not itself worth a banner.
+
+ This used to be a full-width bar with its own border, background
+ and `sticky top-16` — so a control for bulk-deleting old posts
+ followed you down the entire feed, above every post you came to
+ read. It was the fourth separate bordered band before the first
+ post.
+
+ Managing posts is a rare, deliberate task; reading them is the
+ reason the page exists. So the entry point is a quiet inline
+ control, and everything it opens — the full toolbar with counts,
+ select-all and the destructive actions — is unchanged, because
+ once you ARE selecting, that toolbar is the thing you need and
+ it earns being sticky.
+ */
+