diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css b/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css
index 9d1e3f82156..4783e9d3b8b 100644
--- a/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css
+++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css
@@ -37,3 +37,41 @@
.MutedTime {
color: var(--fgColor-muted);
}
+
+/* Underline treatment when MutedTime renders as a permalink to the event. */
+.MutedTimeLink {
+ text-decoration: underline;
+}
+
+/* Span-branch actor name (UserActor without an href, and bot names). No hover-accent:
+ live-GitHub bold actor text doesn't hover-color — only real links (BoldLink) do. */
+.BoldName {
+ font-weight: var(--base-text-weight-semibold);
+ color: var(--fgColor-default);
+ margin-right: var(--base-size-4);
+}
+
+/* Trailing "bot" Label spacing in UserActor. */
+.BotLabel {
+ margin-left: var(--base-size-4);
+}
+
+/* Glyph rendered by UserActor for system actors (in place of an avatar). */
+.ActorIcon {
+ vertical-align: middle;
+ margin-right: var(--base-size-4);
+}
+
+/* Muted "icon + small text" sub-row rendered below an event body. */
+.EventSubRow {
+ display: flex;
+ align-items: center;
+ margin-top: var(--base-size-4);
+ font-size: var(--text-body-size-small);
+ color: var(--fgColor-muted);
+}
+
+.EventSubRowIcon {
+ margin-right: var(--base-size-4);
+ color: var(--fgColor-muted);
+}
diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx b/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx
index cb08804ffe6..08b7cbdd894 100644
--- a/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx
+++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx
@@ -1,5 +1,15 @@
import type {Meta} from '@storybook/react-vite'
-import {BoldLink, Examples, InlineAvatar, MutedTime, RealisticTimeline, VariantSection} from './timelineStoryHelpers'
+import {MarkGithubIcon, NoteIcon} from '@primer/octicons-react'
+import {
+ BoldLink,
+ EventSubRow,
+ Examples,
+ InlineAvatar,
+ MutedTime,
+ RealisticTimeline,
+ UserActor,
+ VariantSection,
+} from './timelineStoryHelpers'
/**
* Reference stories for the internal, story-only Timeline helpers
@@ -53,6 +63,24 @@ export const Helpers = () => (
time treatment.
+
+
+
+ linked+muted, unlinked 16px, and a system actor{' '}
+ rendering a glyph instead of an avatar.
+
+
+
+
+ Example note sub-row
+
+
+
+
+ Updated as a permalink, beside a plain{' '}
+ with no href.
+
+
)
diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx b/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx
index 0097949f625..4ef9b2ac899 100644
--- a/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx
+++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx
@@ -1,7 +1,9 @@
import type React from 'react'
import {clsx} from 'clsx'
import Avatar from '../../Avatar'
+import Label from '../../Label'
import Link from '../../Link'
+import Octicon from '../../Octicon'
import RelativeTime from '../../RelativeTime'
import classes from './timelineStoryHelpers.module.css'
@@ -64,7 +66,82 @@ export const InlineAvatar = ({className, size = 20, alt = '', ...props}: React.C
)
-// TODO(github/primer#6828): remove when Timeline provides a muted relative-time treatment
-export const MutedTime = ({date}: {date: Date}) => (
-
+export const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4'
+
+/**
+ * An actor "avatar + name" cluster, composed from the shared `InlineAvatar` + `BoldLink`
+ * helpers so every Timeline surface renders actors identically.
+ *
+ * The bot shape mirrors live-GitHub `shared.tsx`: a `[bot]` suffix is stripped from the
+ * display name, the name renders as an unlinked ``, and a secondary `bot` `Label`
+ * follows it. The optional `icon` prop renders a glyph instead of an avatar for system
+ * actors (e.g. Secret scanning's GitHubActor: ``).
+ *
+ * TODO(github/primer#6827): remove when Primer ships an inline (in-text) avatar treatment.
+ */
+export const UserActor = ({
+ login = 'monalisa',
+ src = MONALISA_AVATAR,
+ size = 20,
+ href,
+ muted = false,
+ icon,
+}: {
+ login?: string
+ src?: string
+ size?: number
+ href?: string
+ muted?: boolean
+ icon?: React.ElementType
+}) => {
+ const isBot = login.endsWith('[bot]')
+ const display = isBot ? login.replace(/\[bot\]$/i, '') : login
+ return (
+ <>
+ {icon ? : }
+ {href && !isBot ? (
+
+ {display}
+
+ ) : (
+ {display}
+ )}
+ {isBot && (
+
+ )}
+ >
+ )
+}
+
+/**
+ * A muted "icon + small text" sub-row rendered below an event body (e.g. a commit
+ * reference or a note). The four current surfaces drift between flex/margin-top-4 and
+ * block/margin-top-8 layouts — that variance is incidental, so this canonicalizes to a
+ * single flex-centered layout. Only the icon and its size vary.
+ */
+export const EventSubRow = ({
+ icon,
+ iconSize = 16,
+ children,
+}: {
+ icon: React.ElementType
+ iconSize?: number
+ children: React.ReactNode
+}) => (
+
+
+ {children}
+
)
+
+// TODO(github/primer#6828): remove when Timeline provides a muted relative-time treatment
+export const MutedTime = ({date, href}: {date: Date; href?: string}) =>
+ href ? (
+
+
+
+ ) : (
+
+ )