From c21f963a9629565796604711968b01927b8424c3 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:13:15 -0700 Subject: [PATCH 1/7] Add Timeline Dependabot eventOpened stories (Phase 2 proof-of-pattern) Storybook-only Dependabot alert timeline event stories under Components/Timeline/Events/Dependabot, mirroring the Issues stories pattern. Scoped to the eventOpened group (Opened / OpenedFromPR / OpenedFromPush) as a reviewable proof-of-pattern. Translated from the live server-rendered ERB (DependabotAlerts::TimelineItems::OpenedComponent). Not wired into components-json / primer.style docs by design. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ine.dependabot.features.stories.module.css | 83 ++++++++ .../Timeline.dependabot.features.stories.tsx | 201 ++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css create mode 100644 packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css new file mode 100644 index 00000000000..51808972d1e --- /dev/null +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css @@ -0,0 +1,83 @@ +.RealisticTimeline { + /* GitHub renders the timeline at most 1012px wide in product surfaces. */ + max-width: 1012px; +} + +/* Story-only scaffolding: each variant is wrapped in its own
with a + small caption heading ABOVE the event, so the event row itself (badge + + inline avatar + body) renders exactly as it would in product — the caption + never sits inside Timeline.Body. Variants stay scannable like a Figma + component set. Not part of the faithful event. */ +.Variant { + margin-bottom: var(--base-size-24); +} + +.VariantLabel { + margin: 0 0 var(--base-size-4); + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-muted); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +/* Inline 20px-ish actor avatar in the body. The Dependabot actor renders a + SQUARE avatar (live ERB `ActorComponent` uses `GitHub::AvatarComponent` with + `is_user: false` → square, the bundled `dependabot-icon.png`). */ +.InlineAvatar { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Bold actor / reference link: live ERB renders these via + `Primer::Beta::Link.new(scheme: :primary, font_weight: :bold)` — semibold, + default foreground, accent on hover. Used for the bold `dependabot` actor + link and the bold `#123` pull-request link. */ +.LinkWithBoldStyle { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +.LinkWithBoldStyle:hover { + color: var(--fgColor-accent); +} + +/* `(bot)` identifier tag. Live ERB `bot_identifier_tag` renders + `bot`; mirrored here with Primer + `Label variant="secondary"`. This class only adds inline spacing. */ +.BotLabel { + margin: 0 var(--base-size-4) 0 0; + vertical-align: middle; +} + +/* Muted relative timestamp. Unlike the Issues timeline (whose `Ago` renders a + muted UNDERLINED deep-link), the Dependabot ERB renders a plain + `Primer::Beta::RelativeTime` with no link wrapper — muted text only. */ +.Timestamp { + color: var(--fgColor-muted); +} + +/* Push-pill: the `(push-pill: SHA)` blue rounded pill from `PushLinkComponent` + (`` wrapping `Primer::Beta::Link` with `bg: :accent, px: 2, py: 1, + border_radius: 3`). 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-onEmphasis); + background-color: var(--bgColor-accent-emphasis); + padding: var(--base-size-2) var(--base-size-4); + border-radius: var(--borderRadius-medium); + text-decoration: none; +} + +.PushPillLink:hover { + text-decoration: none; +} 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..ddfed1d551a --- /dev/null +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -0,0 +1,201 @@ +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 {ShieldIcon} from '@primer/octicons-react' +import Avatar from '../Avatar' +import Label from '../Label' +import Link from '../Link' +import Octicon from '../Octicon' +import RelativeTime from '../RelativeTime' +import classes from './Timeline.dependabot.features.stories.module.css' + +/** + * 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 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' + +/** + * 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`.) + */ +const DependabotActor = () => ( + <> + + + dependabot + + + +) + +// Muted relative timestamp. The Dependabot ERB renders a plain +// `Primer::Beta::RelativeTime` (no link wrapper) — muted text only, which is a +// deliberate difference from the Issues `Ago` deep-link. +const Time = ({date}: {date: string}) => ( + + + +) + +/** + * Push-pill — `PushLinkComponent`: a `` wrapping a `Primer::Beta::Link` + * (`bg: :accent, px: 2, py: 1, border_radius: 3`) → blue rounded pill with the + * 7-char `after` SHA (single commit) or a `before..after` range (multi-commit). + */ +const PushPill = ({sha}: {sha: string}) => ( + + + {sha} + + +) + +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 + // (`
    `/`
  1. `). 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 = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Opened — no source */} +
    +

    Opened

    + + + + + + + + {'opened this '} + + + +
    + + {/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */} +
    +

    Opened from pull request

    + + + + + + + + {'opened this from '} + + #123 + {' '} + + + +
    + + {/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */} +
    +

    Opened from push

    + + + + + + + + {'opened this from '} + + + +
    +
    +) From 08be55c1932ed973bc7f48c9a56c90b25f862938 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:29:51 -0700 Subject: [PATCH 2/7] Add remaining Dependabot timeline event stories + fix push-pill Completes the Dependabot alert timeline event stories (Phase 2): adds eventFixed, eventDismissed, eventReopened, and eventDismissalRequest alongside the existing eventOpened group. Adds circle UserActor and no-avatar PlainActor helpers and a NoteComment sub-content helper, all translated from the live server-rendered ERB (github/github app/components/dependabot_alerts/timeline_items/). Corrects the push-pill to the subtle light-blue GitHub SHA pill (bgColor-accent-muted + fgColor-accent), matching Primer view_components' bg: :accent mapping rather than the solid emphasis blue. Storybook-only by design; not wired into components-json / primer.style docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ine.dependabot.features.stories.module.css | 72 ++- .../Timeline.dependabot.features.stories.tsx | 522 +++++++++++++++++- 2 files changed, 582 insertions(+), 12 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css index 51808972d1e..1a7e01b5366 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css @@ -58,10 +58,15 @@ color: var(--fgColor-muted); } -/* Push-pill: the `(push-pill: SHA)` blue rounded pill from `PushLinkComponent` - (`` wrapping `Primer::Beta::Link` with `bg: :accent, px: 2, py: 1, - border_radius: 3`). Primer `Link`'s own color rule is `:where(.Link)` (zero - specificity), so this CSS-module class fully drives the pill appearance. */ +/* Push-pill: the `(push-pill: SHA)` SUBTLE light-blue rounded pill from + `PushLinkComponent` (`` 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); @@ -71,8 +76,8 @@ display: inline-block; font-family: var(--fontStack-monospace); font-size: var(--text-body-size-small); - color: var(--fgColor-onEmphasis); - background-color: var(--bgColor-accent-emphasis); + 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; @@ -81,3 +86,58 @@ .PushPillLink:hover { text-decoration: none; } + +/* Plain bold actor (no avatar, no link) for the delegated-closures Dismissal + Request / Reviewed events, whose live `*.html.erb` renders the actor as + `{login}`. */ +.PlainActor { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +/* Muted inline copy — e.g. "requested dismissal" / "approved dismissal request" + verb phrases that the ERB wraps in `color-fg-muted`. */ +.Muted { + color: var(--fgColor-muted); +} + +/* Second line of the Dismissal Requested event: muted "Dismiss as: {resolution}" + (`mt-1` in the ERB). */ +.ResolutionLine { + margin-top: var(--base-size-4); + color: var(--fgColor-muted); +} + +/* 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); +} + +/* Dismissal Reviewed (Approved / Denied) optional review comment. Live ERB + renders a bordered subtle box (`mt-2 p-2 border rounded-2 color-bg-subtle`) + with a "Review comment:" label and a `markdown-body` body. */ +.ReviewCommentBox { + margin-top: var(--base-size-8); + padding: var(--base-size-8); + border: var(--borderWidth-thin) solid var(--borderColor-default); + border-radius: var(--borderRadius-medium); + background-color: var(--bgColor-muted); + font-size: var(--text-body-size-small); +} + +.ReviewCommentLabel { + margin-bottom: var(--base-size-4); + 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 index ddfed1d551a..66d8fc0ba8c 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -3,7 +3,16 @@ import type React from 'react' import type {ComponentProps} from '../utils/types' import {FeatureFlags} from '../FeatureFlags' import Timeline from './Timeline' -import {ShieldIcon} from '@primer/octicons-react' +import { + CheckCircleIcon, + NoteIcon, + PersonIcon, + ShieldCheckIcon, + ShieldIcon, + ShieldSlashIcon, + SyncIcon, + XIcon, +} from '@primer/octicons-react' import Avatar from '../Avatar' import Label from '../Label' import Link from '../Link' @@ -52,20 +61,23 @@ import classes from './Timeline.dependabot.features.stories.module.css' * 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 blue `(push-pill: SHA)` are composed from - * Primer primitives (`Avatar square`, `Label variant="secondary"`, and a - * ``-wrapped styled `Link`) to match the live ERB. + * 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 = () => ( <> @@ -79,6 +91,28 @@ const DependabotActor = () => ( ) +/** + * 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 + + +) + +/** + * Plain bold actor — for the delegated-closures Dismissal Request / Reviewed + * events, whose live `*.html.erb` renders the actor as plain bold text with NO + * avatar and NO profile link (``). + */ +const PlainActor = ({login = 'monalisa'}: {login?: string}) => {login} + // Muted relative timestamp. The Dependabot ERB renders a plain // `Primer::Beta::RelativeTime` (no link wrapper) — muted text only, which is a // deliberate difference from the Issues `Ago` deep-link. @@ -90,8 +124,9 @@ const Time = ({date}: {date: string}) => ( /** * Push-pill — `PushLinkComponent`: a `` wrapping a `Primer::Beta::Link` - * (`bg: :accent, px: 2, py: 1, border_radius: 3`) → blue rounded pill with the - * 7-char `after` SHA (single commit) or a `before..after` range (multi-commit). + * (`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}) => ( @@ -101,6 +136,19 @@ const PushPill = ({sha}: {sha: string}) => ( ) +/** + * 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, @@ -199,3 +247,465 @@ export const EventOpened = () => (
) + +/** + * 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 = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Fixed — no source */} +
+

Fixed

+ + + + + + + + {'closed this as completed '} + + + +
+ + {/* FixedViaPR — bold `#123` pull-request link */} +
+

Fixed via pull request

+ + + + + + + + {'closed this as completed in '} + + #123 + {' '} + + + +
+ + {/* FixedViaPush — blue push-pill */} +
+

Fixed via push

+ + + + + + + + {'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 = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Manual — risk is tolerable (with an optional dismissal note) */} +
+

Dismissed as risk is tolerable

+ + + + + + + + {'dismissed this as risk is tolerable '} + + + +
+ + {/* Manual — fix started */} +
+

Dismissed as fix started

+ + + + + + + + {'dismissed this as fix started '} + + + +
+ + {/* Manual — no bandwidth to fix this */} +
+

Dismissed as no bandwidth

+ + + + + + + + {'dismissed this as no bandwidth to fix this '} + + + +
+ + {/* Manual — vulnerable code is not actually used */} +
+

Dismissed as not used

+ + + + + + + + {'dismissed this as vulnerable code is not actually used '} + + + +
+ + {/* Manual — inaccurate */} +
+

Dismissed as inaccurate

+ + + + + + + + {'dismissed this as inaccurate '} + + + +
+ + {/* Auto — rule-based, no source (with optional rule comment) */} +
+

Auto-dismissed by alert rule

+ + + + + + + + {'dismissed this due to an alert rule '} + + + +
+ + {/* Auto — from a pull request */} +
+

Auto-dismissed from pull request

+ + + + + + + + {'dismissed this due to an alert rule from '} + + #123 + {' '} + + + +
+ + {/* Auto — from a push */} +
+

Auto-dismissed from 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 = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Manual reopen — user actor */} +
+

Reopened

+ + + + + + + + {'reopened this '} + + + +
+ + {/* Reintroduced — no source */} +
+

Reintroduced

+ + + + + + + + {'reopened this '} + + + +
+ + {/* Reintroduced — from a pull request */} +
+

Reintroduced from pull request

+ + + + + + + + {'reopened this from '} + + #123 + {' '} + + + +
+ + {/* Reintroduced — from a push */} +
+

Reintroduced from push

+ + + + + + + + {'reopened this from '} + + + +
+ + {/* Auto-reopened — rule change (with optional rule comment) */} +
+

Auto-reopened by rule change

+ + + + + + + + {'reopened this '} + + + +
+
+) + +/** + * The Dismissal Request event group — `DepTimeline.eventDismissalRequest` + * (audit § 5). Part of the org-level delegated-closures system. + * + * The Requested / Approved / Denied variants render the actor as PLAIN BOLD TEXT + * with NO avatar and NO profile link (live `dismissal_requested_component.html.erb` + * / `dismissal_reviewed_component.html.erb`), with a small octicon badge. + * Cancelled (`DismissalCancelledComponent`) instead renders the user via + * `ActorComponent` (circle avatar). + * + * SOURCE-OF-TRUTH UNCERTAINTY: each of these `.rb` components ALSO defines a + * newer inline `erb_template` (via `ViewComponent::InlineTemplate`) that renders + * the actor WITH an avatar and uses different badges (`comment`/attention for + * Requested; dynamic check/x for Reviewed). A ViewComponent can only have one + * active template, so one path is dormant. We follow the `.html.erb` sidecar / + * no-avatar rendering here because it matches the v11 audit (the audit author's + * proxy for what actually renders); the newer inline template may be an + * in-flight migration. FLAGGED for confirmation against the live site / Figma. + */ +export const EventDismissalRequest = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Dismissal requested — no avatar, plain bold actor */} +
+

Dismissal requested

+ + + + + + + + requested dismissal +
+ {'Dismiss as: '} + Risk is tolerable + {' • '} +
+
+
+
+
+ + {/* Dismissal approved — no avatar, optional review comment box */} +
+

Dismissal approved

+ + + + + + + + approved dismissal request +
+
+
+
Review comment:
+
Confirmed this dependency is dev-only — approving the dismissal.
+
+
+
+
+
+ + {/* Dismissal denied — no avatar */} +
+

Dismissal denied

+ + + + + + + + denied dismissal request +
+
+
+
+
+
+ + {/* Dismissal cancelled — user actor via ActorComponent (circle avatar) */} +
+

Dismissal cancelled

+ + + + + + + + {'cancelled their dismissal request '} + + + +
+
+) From b44cd12da9269ac6055b71ac3242dbfc0f6e081c Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:37:58 -0700 Subject: [PATCH 3/7] Rebuild Dependabot DismissalRequest group to live inline templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dismissal_*_component.rb files use ViewComponent::InlineTemplate, so their inline erb_template is the active render and the .html.erb sidecars are dead code. Rebuild Requested/Approved/Denied to the avatar-based inline version: circle user actor (UserActor), and status-driven badges via named variants — Requested attention/comment, Approved success/check, Denied danger/x. Cancelled keeps the plain default (subtle) x badge per its inline template. Optional review/resolution notes use the shared NoteComment sub-row. Removes the now-unused PlainActor helper and its CSS, plus the unused Muted, ResolutionLine, and ReviewCommentBox/Label styles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ine.dependabot.features.stories.module.css | 39 -------- .../Timeline.dependabot.features.stories.tsx | 96 ++++++++----------- 2 files changed, 42 insertions(+), 93 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css index 1a7e01b5366..fc736290f83 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css @@ -87,28 +87,6 @@ text-decoration: none; } -/* Plain bold actor (no avatar, no link) for the delegated-closures Dismissal - Request / Reviewed events, whose live `*.html.erb` renders the actor as - `{login}`. */ -.PlainActor { - font-weight: var(--base-text-weight-semibold); - color: var(--fgColor-default); - margin-right: var(--base-size-4); -} - -/* Muted inline copy — e.g. "requested dismissal" / "approved dismissal request" - verb phrases that the ERB wraps in `color-fg-muted`. */ -.Muted { - color: var(--fgColor-muted); -} - -/* Second line of the Dismissal Requested event: muted "Dismiss as: {resolution}" - (`mt-1` in the ERB). */ -.ResolutionLine { - margin-top: var(--base-size-4); - color: var(--fgColor-muted); -} - /* Optional dismissal/auto sub-content row. Live ERB renders a `
` (small, indented block below the event) holding a `note` octicon + the comment text. */ @@ -124,20 +102,3 @@ margin-right: var(--base-size-4); color: var(--fgColor-muted); } - -/* Dismissal Reviewed (Approved / Denied) optional review comment. Live ERB - renders a bordered subtle box (`mt-2 p-2 border rounded-2 color-bg-subtle`) - with a "Review comment:" label and a `markdown-body` body. */ -.ReviewCommentBox { - margin-top: var(--base-size-8); - padding: var(--base-size-8); - border: var(--borderWidth-thin) solid var(--borderColor-default); - border-radius: var(--borderRadius-medium); - background-color: var(--bgColor-muted); - font-size: var(--text-body-size-small); -} - -.ReviewCommentLabel { - margin-bottom: var(--base-size-4); - 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 index 66d8fc0ba8c..95255835719 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -4,9 +4,9 @@ import type {ComponentProps} from '../utils/types' import {FeatureFlags} from '../FeatureFlags' import Timeline from './Timeline' import { - CheckCircleIcon, + CheckIcon, + CommentIcon, NoteIcon, - PersonIcon, ShieldCheckIcon, ShieldIcon, ShieldSlashIcon, @@ -106,13 +106,6 @@ const UserActor = () => ( ) -/** - * Plain bold actor — for the delegated-closures Dismissal Request / Reviewed - * events, whose live `*.html.erb` renders the actor as plain bold text with NO - * avatar and NO profile link (``). - */ -const PlainActor = ({login = 'monalisa'}: {login?: string}) => {login} - // Muted relative timestamp. The Dependabot ERB renders a plain // `Primer::Beta::RelativeTime` (no link wrapper) — muted text only, which is a // deliberate difference from the Issues `Ago` deep-link. @@ -605,20 +598,21 @@ export const EventReopened = () => ( * The Dismissal Request event group — `DepTimeline.eventDismissalRequest` * (audit § 5). Part of the org-level delegated-closures system. * - * The Requested / Approved / Denied variants render the actor as PLAIN BOLD TEXT - * with NO avatar and NO profile link (live `dismissal_requested_component.html.erb` - * / `dismissal_reviewed_component.html.erb`), with a small octicon badge. - * Cancelled (`DismissalCancelledComponent`) instead renders the user via - * `ActorComponent` (circle avatar). + * 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`. + * - Approved (`DismissalReviewedComponent`, status approved): `check` / `success`. + * - Denied (`DismissalReviewedComponent`, status rejected): `x` / `danger`. + * - Cancelled (`DismissalCancelledComponent`): `x` / `subtle` (plain default badge). * - * SOURCE-OF-TRUTH UNCERTAINTY: each of these `.rb` components ALSO defines a - * newer inline `erb_template` (via `ViewComponent::InlineTemplate`) that renders - * the actor WITH an avatar and uses different badges (`comment`/attention for - * Requested; dynamic check/x for Reviewed). A ViewComponent can only have one - * active template, so one path is dormant. We follow the `.html.erb` sidecar / - * no-avatar rendering here because it matches the v11 audit (the audit author's - * proxy for what actually renders); the newer inline template may be an - * in-flight migration. FLAGGED for confirmation against the live site / Figma. + * `attention`, `success`, and `danger` are named `TimelineBadgeVariants`, so we + * use `variant="…"` directly (no inline `--timelineBadge-bgColor` hook needed). + * Optional review/resolution notes render via the shared `NoteComment` sub-row + * (the ERB's `note`-octicon `TimelineItem tmp-pl-5 …` block). */ export const EventDismissalRequest = () => (
( if ((e.target as HTMLElement).closest('a')) e.preventDefault() }} > - {/* Dismissal requested — no avatar, plain bold actor */} + {/* Dismissal requested — circle user actor, attention/comment badge. + (The live ERB also renders optional float-right Review/Deny actions via + `DismissalReviewDialogComponent` when `show_dismissal_actions` — those + would live in `Timeline.Actions`; omitted here as interactive controls.) */}

Dismissal requested

- - + + - - requested dismissal -
- {'Dismiss as: '} - Risk is tolerable - {' • '} -
+ + {'requested to dismiss this as '} + Tolerable risk
- {/* Dismissal approved — no avatar, optional review comment box */} + {/* Dismissal approved — circle user actor, success/check badge */}

Dismissal approved

- - + + - - approved dismissal request -
-
-
-
Review comment:
-
Confirmed this dependency is dev-only — approving the dismissal.
-
+ + {'approved dismissal '} +
- {/* Dismissal denied — no avatar */} + {/* Dismissal denied — circle user actor, danger/x badge */}

Dismissal denied

- - + + - - denied dismissal request -
-
+ + {'denied dismissal '} +
- {/* Dismissal cancelled — user actor via ActorComponent (circle avatar) */} + {/* 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. */}

Dismissal cancelled

From 5d6ddd7ec28dd851c019e3b2725b1d6b6a015615 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:43:34 -0700 Subject: [PATCH 4/7] Fix Dependabot DismissalRequest badges to bare badge + colored icon The dismissal inline ERB uses with_badge(color: :X, icon: Y) with no bg:, which tints the icon on the default badge background rather than producing a solid emphasis badge. Replace the named attention/success/danger Timeline.Badge variants with a bare Timeline.Badge plus a colored Octicon (icon color via a .BadgeIcon* class), mirroring the muted Dismissed treatment. Cancelled's :subtle maps to --fgColor-muted (no --fgColor-subtle token exists). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ine.dependabot.features.stories.module.css | 23 +++++++++++++ .../Timeline.dependabot.features.stories.tsx | 33 ++++++++++--------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css index fc736290f83..c6c90b1477b 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.module.css @@ -102,3 +102,26 @@ 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 index 95255835719..e8386a93854 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -604,15 +604,18 @@ export const EventReopened = () => ( * `*.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`. - * - Approved (`DismissalReviewedComponent`, status approved): `check` / `success`. - * - Denied (`DismissalReviewedComponent`, status rejected): `x` / `danger`. - * - Cancelled (`DismissalCancelledComponent`): `x` / `subtle` (plain default badge). + * - Requested (`DismissalRequestedComponent`): `comment` icon, attention color. + * - Approved (`DismissalReviewedComponent`, status approved): `check`, success. + * - Denied (`DismissalReviewedComponent`, status rejected): `x`, danger. + * - Cancelled (`DismissalCancelledComponent`): `x`, subtle color. * - * `attention`, `success`, and `danger` are named `TimelineBadgeVariants`, so we - * use `variant="…"` directly (no inline `--timelineBadge-bgColor` hook needed). - * Optional review/resolution notes render via the shared `NoteComment` sub-row - * (the ERB's `note`-octicon `TimelineItem tmp-pl-5 …` block). + * 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

- - + + @@ -647,8 +650,8 @@ export const EventDismissalRequest = () => (

Dismissal approved

- - + + @@ -665,8 +668,8 @@ export const EventDismissalRequest = () => (

Dismissal denied

- - + + @@ -685,7 +688,7 @@ export const EventDismissalRequest = () => ( - + From 44aa5896bcf324de37610563118c58968e5a08ae Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:01:01 -0700 Subject: [PATCH 5/7] Add dismissal review action button via Timeline.Actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dismissal Requested event's live inline template renders a float-right DismissalReviewDialogComponent when show_dismissal_actions is true — its visible control is a single small primary "Review request" button that opens the approve/deny review dialog. Surface it in the Timeline.Actions right-controls slot (#6678) on the Requested variant only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Timeline.dependabot.features.stories.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx index e8386a93854..393da15c5b3 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -14,6 +14,7 @@ import { XIcon, } from '@primer/octicons-react' import Avatar from '../Avatar' +import {Button} from '../Button' import Label from '../Label' import Link from '../Link' import Octicon from '../Octicon' @@ -624,10 +625,13 @@ export const EventDismissalRequest = () => ( if ((e.target as HTMLElement).closest('a')) e.preventDefault() }} > - {/* Dismissal requested — circle user actor, attention/comment badge. - (The live ERB also renders optional float-right Review/Deny actions via - `DismissalReviewDialogComponent` when `show_dismissal_actions` — those - would live in `Timeline.Actions`; omitted here as interactive controls.) */} + {/* 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. */}

Dismissal requested

@@ -641,6 +645,9 @@ export const EventDismissalRequest = () => ( Tolerable risk
From bd02340ec5fd03f49af6fc5fe5590851b2381e0e Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:24:55 -0700 Subject: [PATCH 6/7] Fix high-contrast a11y on auto-dismissal/reopen rule-name links The bare alert-rule-name links in the auto-dismissed and auto-reopened note rows had no non-color distinction, failing axe link-in-text-block (WCAG 1.4.1) in high-contrast themes. Add the inline prop (Primer underlines inline links), matching the live ERB (the rule name is a plain, non-bold Primer::Beta::Link) and the convention used by the Issues stories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Timeline/Timeline.dependabot.features.stories.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx index 393da15c5b3..c69fddf9d1c 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -438,7 +438,9 @@ export const EventDismissed = () => (
@@ -586,7 +588,9 @@ export const EventReopened = () => (
From ebebdf3d57d5537da079dd043e9c22ff84eb0cac Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:03:47 -0700 Subject: [PATCH 7/7] Make dismissal Review request button primary Match the live DismissalReviewDialogComponent show-button (scheme: :primary, size: :small) and the inline comment describing it. Addresses Copilot review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Timeline/Timeline.dependabot.features.stories.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx index c69fddf9d1c..f0e966391d0 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -650,7 +650,9 @@ export const EventDismissalRequest = () => ( This dependency is only used in our test tooling, so the risk is acceptable.
- +