` wrapping `Primer::Beta::Link` with
+ `bg: :accent, px: 2, py: 1, border_radius: 3`). In primer/view_components
+ `bg: :accent` resolves to `.color-bg-accent` = the SUBTLE/MUTED light-blue
+ background (`--color-accent-subtle`), NOT the solid emphasis blue — so the
+ text stays accent-blue (the Link's default color), not white. This is the
+ standard GitHub SHA push-pill look. Primer `Link`'s own color rule is
+ `:where(.Link)` (zero specificity), so this CSS-module class fully drives the
+ pill appearance. */
+.PushPill {
+ /* The wrapping element — no own box, the inner link is the pill. */
+ font-family: var(--fontStack-monospace);
+}
+
+.PushPillLink {
+ display: inline-block;
+ font-family: var(--fontStack-monospace);
+ font-size: var(--text-body-size-small);
+ color: var(--fgColor-accent);
+ background-color: var(--bgColor-accent-muted);
+ padding: var(--base-size-2) var(--base-size-4);
+ border-radius: var(--borderRadius-medium);
+ text-decoration: none;
+}
+
+.PushPillLink:hover {
+ text-decoration: none;
+}
+
+/* Optional dismissal/auto sub-content row. Live ERB renders a
+ `` (small, indented block
+ below the event) holding a `note` octicon + the comment text. */
+.NoteComment {
+ margin-top: var(--base-size-8);
+ padding-left: var(--base-size-4);
+ font-size: var(--text-body-size-small);
+ color: var(--fgColor-muted);
+}
+
+.NoteIcon {
+ vertical-align: middle;
+ margin-right: var(--base-size-4);
+ color: var(--fgColor-muted);
+}
+
+/* Colored badge icons for the Dismissal Request / Review / Cancelled events.
+ Their live ERB renders `with_badge(color: :X, icon: …)` with NO `bg:` — in
+ Primer `color:` tints the ICON on the DEFAULT badge background (it is NOT a
+ solid-color badge). So these render as a bare `` (default gray
+ circle) with the icon color set here, mirroring the Dismissed muted icon. */
+.BadgeIconAttention {
+ color: var(--fgColor-attention);
+}
+
+.BadgeIconSuccess {
+ color: var(--fgColor-success);
+}
+
+.BadgeIconDanger {
+ color: var(--fgColor-danger);
+}
+
+/* `:subtle` icon color. Primer has no `--fgColor-subtle` token, so the closest
+ functional equivalent for the Dismissal Cancelled `color: :subtle` is muted. */
+.BadgeIconMuted {
+ color: var(--fgColor-muted);
+}
diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx
new file mode 100644
index 00000000000..d485f49ddda
--- /dev/null
+++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx
@@ -0,0 +1,644 @@
+import type {Meta} from '@storybook/react-vite'
+import type React from 'react'
+import type {ComponentProps} from '../utils/types'
+import {FeatureFlags} from '../FeatureFlags'
+import Timeline from './Timeline'
+import {
+ CheckIcon,
+ CommentIcon,
+ NoteIcon,
+ ShieldCheckIcon,
+ ShieldIcon,
+ ShieldSlashIcon,
+ SyncIcon,
+ XIcon,
+} from '@primer/octicons-react'
+import {Button} from '../Button'
+import Label from '../Label'
+import Link from '../Link'
+import Octicon from '../Octicon'
+import classes from './Timeline.dependabot.features.stories.module.css'
+import {BoldLink, Examples, InlineAvatar, MutedTime, VariantSection} from './internal/timelineStoryHelpers'
+
+/**
+ * Dependabot alert Timeline event examples (Phase 2 of github/primer#6663).
+ *
+ * These stories recreate GitHub's live Dependabot-alert-timeline events using
+ * the Primer `Timeline` compositional slots. They mirror the established Issues
+ * stories (`Timeline.issues.features.stories.tsx`) and are sourced from the
+ * `timeline-audit` Figma audit (`dependabot-timeline-events-for-figma.md`).
+ *
+ * SOURCE OF TRUTH — Dependabot is NOT (yet) migrated to React. The Dependabot
+ * alert timeline is entirely SERVER-RENDERED ERB (ViewComponent). The events
+ * are rendered by `DependabotAlerts::TimelineComponent`, which dispatches to
+ * per-event components in `app/components/dependabot_alerts/timeline_items/`
+ * (e.g. `OpenedComponent`) in the `github/github` Rails monorepo. There is no
+ * React equivalent in `github/github-ui` — that repo only ships Catalyst
+ * custom-element controllers (`dependabot-alert-*-element.ts`) for table-row /
+ * load-all / dismissal interactions, not the timeline event rows. So each event
+ * below is translated faithfully from the live ERB into Primer React, with the
+ * ERB source file commented inline.
+ *
+ * SCOPE: These are Storybook-only examples by design. They are intentionally
+ * NOT wired into components-json / the primer.style docs page (do NOT add this
+ * file to `Timeline.docs.json` or `build.ts`). Individual timeline events are
+ * not consumer-facing components — the primer.style Timeline page reflects the
+ * base `Timeline` component's own stories, and any docs-site representation is a
+ * Phase 3 consideration via base-component story changes, out of scope here.
+ *
+ * FUTURE FILTERING (taxonomy still open — github/primer#6663): category
+ * `data-*` attributes (e.g. `data-event-category="opened"`) will attach to each
+ * `Timeline.Item` below so stories can be filtered/grouped by event family. We
+ * intentionally do NOT add them yet to avoid baking in a taxonomy.
+ *
+ * SLOT USAGE (Phase 1 slots — same convention as the Issues group):
+ * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar.
+ * Reserved for comment-style events. Badge-row events like Opened do NOT use
+ * it — the live ERB renders the actor's small SQUARE avatar INLINE in the body
+ * (`ActorComponent`), not in the gutter. We mirror that: avatar inline in
+ * `Timeline.Body`.
+ * - `Timeline.Actions` (right-controls slot, #6678): for buttons / SHAs / status
+ * pills on the right edge. Opened has no right controls, so it is omitted here.
+ *
+ * DEPENDABOT-SPECIFIC COMPOSITION (see helpers below): the square bot avatar,
+ * the `(bot)` identifier tag, and the SUBTLE light-blue `(push-pill: SHA)` are
+ * composed from Primer primitives (`Avatar square`, `Label variant="secondary"`,
+ * and a ``-wrapped styled `Link`) to match the live ERB.
+ */
+
+// Live ERB uses the bundled `modules/site/dependabot-icon.png`; this is the
+// public dependabot[bot] avatar (square Dependabot logo) for Storybook.
+const DEPENDABOT_AVATAR = 'https://avatars.githubusercontent.com/u/27347476?v=4'
+const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4'
+
+/**
+ * Dependabot actor — `DependabotAlerts::TimelineItems::ActorComponent` in
+ * `:dependabot` mode (`actor_component.html.erb`):
+ * square avatar + bold `dependabot` docs link + `(bot)` identifier tag.
+ * (`bot_identifier_tag` → `bot`.)
+ * Used by Dependabot-authored events (Opened / Fixed / Reintroduced /
+ * Auto-dismissed / Auto-reopened).
+ */
+const DependabotActor = () => (
+ <>
+
+
+ dependabot
+
+
+ >
+)
+
+/**
+ * User actor — `ActorComponent` regular-user branch (`linked_avatar_for(actor,
+ * 20)` + `profile_link(... "text-bold")`): a CIRCLE 20px avatar + bold profile
+ * link, no `(bot)` tag. Used by user-authored events (manual Dismissed, manual
+ * Reopened, Dismissal Cancelled).
+ */
+const UserActor = () => (
+ <>
+
+
+ monalisa
+
+ >
+)
+
+/**
+ * Push-pill — `PushLinkComponent`: a `` wrapping a `Primer::Beta::Link`
+ * (`bg: :accent, px: 2, py: 1, border_radius: 3`) → SUBTLE light-blue rounded
+ * pill with accent-blue monospace text (the standard GitHub SHA pill), showing
+ * the 7-char `after` SHA (single commit) or a `before..after` range (multi).
+ */
+const PushPill = ({sha}: {sha: string}) => (
+
+
+ {sha}
+
+
+)
+
+/**
+ * Optional sub-content row rendered below a dismissal/auto event — the live ERB
+ * renders a `` with a `note`
+ * octicon and the comment text. Rendered here as a small muted block inside the
+ * event body.
+ */
+const NoteComment = ({children}: {children: React.ReactNode}) => (
+
+
+ {children}
+
+)
+
+export default {
+ title: 'Components/Timeline/Events/Dependabot',
+ component: Timeline,
+ subcomponents: {
+ 'Timeline.Item': Timeline.Item,
+ 'Timeline.Avatar': Timeline.Avatar,
+ 'Timeline.Badge': Timeline.Badge,
+ 'Timeline.Body': Timeline.Body,
+ 'Timeline.Break': Timeline.Break,
+ 'Timeline.Actions': Timeline.Actions,
+ },
+ decorators: [
+ // File-scoped: render every story in the future-state list semantics
+ // (``/`- `). The `primer_react_timeline_list_semantics` flag is merged
+ // on main; this opts these stories into the DOM the timeline will ship.
+ Story => (
+
+
+
+ ),
+ ],
+} as Meta>
+
+/**
+ * The Opened event group — `DepTimeline.eventOpened` (audit § 1).
+ *
+ * Source: `OpenedComponent` (`opened_component.html.erb`). The actor is ALWAYS
+ * Dependabot. Badge: `shield` icon on `success` (green) — the ERB renders
+ * `with_badge(bg: :success_emphasis, color: :on_emphasis, icon: :shield)`,
+ * which maps exactly to `Timeline.Badge variant="success"`.
+ *
+ * Three source variants (`Opened` / `OpenedFromPR` / `OpenedFromPush`) differ
+ * only by the optional "from …" clause: no source, a bold `#123` PR link, or a
+ * blue push-pill.
+ */
+export const EventOpened = () => (
+
+ {/* Opened — no source */}
+
+
+
+
+
+
+
+
+ {'opened this '}
+
+
+
+
+
+
+ {/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */}
+
+
+
+
+
+
+
+
+ {'opened this from '}
+ #123
+
+
+
+
+
+ {/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */}
+
+
+
+
+
+
+
+
+ {'opened this from '}
+
+
+
+
+
+
+)
+
+/**
+ * The Fixed event group — `DepTimeline.eventFixed` (audit § 2).
+ *
+ * Source: `FixedComponent` (`fixed_component.html.erb`). Actor is ALWAYS
+ * Dependabot. Badge: `shield-check` on `done` (purple) — the ERB renders
+ * `with_badge(bg: :done_emphasis, color: :on_emphasis, icon: "shield-check")`,
+ * mapping to `Timeline.Badge variant="done"`. Three source variants differ only
+ * by the optional "in …" clause (no source / bold `#123` PR link / push-pill).
+ *
+ * NOTE: the live ERB renders a `TimelineItem-break` separator after this event
+ * (unless it is the last). In these stacked, single-event stories we omit the
+ * runtime separator — it is a list-position concern, not part of the event.
+ */
+export const EventFixed = () => (
+
+ {/* Fixed — no source */}
+
+
+
+
+
+
+
+
+ {'closed this as completed '}
+
+
+
+
+
+
+ {/* FixedViaPR — bold `#123` pull-request link */}
+
+
+
+
+
+
+
+
+ {'closed this as completed in '}
+ #123
+
+
+
+
+
+ {/* FixedViaPush — blue push-pill */}
+
+
+
+
+
+
+
+
+ {'closed this as completed in '}
+
+
+
+
+
+
+)
+
+/**
+ * The Dismissed event group — `DepTimeline.eventDismissed` (audit § 3).
+ *
+ * Consolidates MANUAL user dismissals (`DismissedComponent`, circle user actor)
+ * and rule-based AUTO dismissals (`AutoDismissedComponent`, square Dependabot
+ * actor). Badge: `shield-slash` on the PLAIN DEFAULT badge (subtle gray circle,
+ * muted icon) — the live ERB renders `with_badge(color: :muted, ...)` /
+ * `with_badge(icon: "shield-slash")` with NO `bg:` override, so we use a bare
+ * `Timeline.Badge` (no variant), NOT the neutral-emphasis hook.
+ *
+ * Manual reasons are `RepositoryVulnerabilityAlert::DISMISS_REASONS` downcased.
+ * The optional comment renders as a small indented sub-row (note octicon + text).
+ */
+export const EventDismissed = () => (
+
+ {/* Manual — risk is tolerable (with an optional dismissal note) */}
+
+
+
+
+
+
+
+
+ {'dismissed this as risk is tolerable '}
+
+ This dependency is only used in our test tooling, so the risk is acceptable.
+
+
+
+
+
+ {/* Manual — fix started */}
+
+
+
+
+
+
+
+
+ {'dismissed this as fix started '}
+
+
+
+
+
+
+ {/* Manual — no bandwidth to fix this */}
+
+
+
+
+
+
+
+
+ {'dismissed this as no bandwidth to fix this '}
+
+
+
+
+
+
+ {/* Manual — vulnerable code is not actually used */}
+
+
+
+
+
+
+
+
+ {'dismissed this as vulnerable code is not actually used '}
+
+
+
+
+
+
+ {/* Manual — inaccurate */}
+
+
+
+
+
+
+
+
+ {'dismissed this as inaccurate '}
+
+
+
+
+
+
+ {/* Auto — rule-based, no source (with optional rule comment) */}
+
+
+
+
+
+
+
+
+ {'dismissed this due to an alert rule '}
+
+
+ {'Rule created and '}
+
+ low-severity-dev-deps
+
+ {' was applied'}
+
+
+
+
+
+
+ {/* Auto — from a pull request */}
+
+
+
+
+
+
+
+
+ {'dismissed this due to an alert rule from '}
+ #123
+
+
+
+
+
+ {/* Auto — from a push */}
+
+
+
+
+
+
+
+
+ {'dismissed this due to an alert rule from '}
+
+
+
+
+
+
+)
+
+/**
+ * The Reopened event group — `DepTimeline.eventReopened` (audit § 4).
+ *
+ * Consolidates MANUAL reopens (`ReopenedComponent`, circle user actor),
+ * REINTRODUCTIONS (`ReintroducedComponent`, square Dependabot actor, × source)
+ * and rule-based AUTO reopens (`AutoReopenedComponent`, square Dependabot
+ * actor). Badge: `sync` on `success` (green) for all — the ERB renders
+ * `with_badge(bg: :success_emphasis, color: :on_emphasis, icon: :sync)`.
+ */
+export const EventReopened = () => (
+
+ {/* Manual reopen — user actor */}
+
+
+
+
+
+
+
+
+ {'reopened this '}
+
+
+
+
+
+
+ {/* Reintroduced — no source */}
+
+
+
+
+
+
+
+
+ {'reopened this '}
+
+
+
+
+
+
+ {/* Reintroduced — from a pull request */}
+
+
+
+
+
+
+
+
+ {'reopened this from '}
+ #123
+
+
+
+
+
+ {/* Reintroduced — from a push */}
+
+
+
+
+
+
+
+
+ {'reopened this from '}
+
+
+
+
+
+
+ {/* Auto-reopened — rule change (with optional rule comment) */}
+
+
+
+
+
+
+
+
+ {'reopened this '}
+
+
+ {'Rule disabled: '}
+
+ low-severity-dev-deps
+
+
+
+
+
+
+
+)
+
+/**
+ * The Dismissal Request event group — `DepTimeline.eventDismissalRequest`
+ * (audit § 5). Part of the org-level delegated-closures system.
+ *
+ * SOURCE OF TRUTH — inline `erb_template` (NOT the dormant sidecar): each
+ * `dismissal_*_component.rb` does `include ViewComponent::InlineTemplate` and
+ * defines an `erb_template`, which is the ACTIVE render; the sibling
+ * `*.html.erb` sidecar is dead code left in the tree. So every variant here
+ * renders the actor via `ActorComponent` (a USER → circle avatar + bold link,
+ * our `UserActor`), with status-driven badges:
+ * - Requested (`DismissalRequestedComponent`): `comment` icon, attention color.
+ * - Approved (`DismissalReviewedComponent`, status approved): `check`, success.
+ * - Denied (`DismissalReviewedComponent`, status rejected): `x`, danger.
+ * - Cancelled (`DismissalCancelledComponent`): `x`, subtle color.
+ *
+ * BADGE MECHANIC: the ERB uses `with_badge(color: :X, icon: Y)` with NO `bg:`.
+ * In Primer that tints the ICON on the DEFAULT badge background — it does NOT
+ * produce a solid-color badge (unlike Opened/Fixed/Reopened, which DO set
+ * `bg: :X_emphasis` and so use solid `variant`s). So these four render as a bare
+ * `` (default gray circle) with the icon color set via a
+ * `.BadgeIcon*` class — the same structure as the muted Dismissed badge.
+ * Optional review/resolution notes render via the shared `NoteComment` sub-row.
+ */
+export const EventDismissalRequest = () => (
+
+ {/* Dismissal requested — circle user actor, attention/comment badge. When
+ `show_dismissal_actions` is true, the live inline template
+ (`dismissal_requested_component.rb`) renders a ``
+ holding `DismissalReviewDialogComponent` — whose visible control is a
+ SINGLE small primary "Review request" button that opens a review dialog
+ (the approve/deny choice lives inside the dialog, not on the row). We
+ place that right-aligned control in the `Timeline.Actions` slot. */}
+
+
+
+
+
+
+
+
+ {'requested to dismiss this as '}
+ Tolerable risk
+ This dependency is only used in our test tooling, so the risk is acceptable.
+
+
+
+
+
+
+
+
+ {/* Dismissal approved — circle user actor, success/check badge */}
+
+
+
+
+
+
+
+
+ {'approved dismissal '}
+
+ Confirmed this dependency is dev-only — approving the dismissal.
+
+
+
+
+
+ {/* Dismissal denied — circle user actor, danger/x badge */}
+
+
+
+
+
+
+
+
+ {'denied dismissal '}
+
+
+
+
+
+
+ {/* Dismissal cancelled — circle user actor, plain default (subtle) x badge.
+ `DismissalCancelledComponent` renders `with_badge(color: :subtle, icon:
+ "x")` (no bg) → a bare default badge, not a danger/emphasis one. */}
+
+
+
+
+
+
+
+
+ {'cancelled their dismissal request '}
+
+
+
+
+
+
+)