From 6bebe870cf5490dba9e2b19ac54b2a8ccacd7b2e Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:27:21 -0700 Subject: [PATCH 1/3] Add Secret Scanning alert timeline stories (proof-of-pattern: Created group) Storybook-only stories recreating GitHub's secret-scanning alert timeline events with Primer Timeline slots, mirroring the Issues and Dependabot surfaces. Scopes this commit to the canonical Created/Opened event group plus the full file scaffold (file-scoped FeatureFlags decorator, GitHub system actor + muted Time helpers, header doc block). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ecret-scanning.features.stories.module.css | 45 ++++++ ...eline.secret-scanning.features.stories.tsx | 137 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css create mode 100644 packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css new file mode 100644 index 00000000000..a95131b6aa1 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css @@ -0,0 +1,45 @@ +.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 actor + 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; +} + +/* System "GitHub" actor — live `AlertTimeline.tsx` `TimelineItemBody` renders + ` GitHub` (no avatar) + when `isGitHubActor`. This class spaces the leading octicon. */ +.ActorIcon { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Bold "GitHub" label, mirroring the live `text-bold` span. */ +.ActorName { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +/* Muted relative timestamp. The live secret-scanning `TimelineItemBody` renders + a plain `RelativeTime` (no link wrapper) — muted text only, matching the + Dependabot timeline (and a deliberate difference from the Issues `Ago` + deep-link). */ +.Timestamp { + color: var(--fgColor-muted); +} diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx new file mode 100644 index 00000000000..410f9306509 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -0,0 +1,137 @@ +import type {Meta} from '@storybook/react-vite' +import type {ComponentProps} from '../utils/types' +import {FeatureFlags} from '../FeatureFlags' +import Timeline from './Timeline' +import {MarkGithubIcon, ShieldIcon} from '@primer/octicons-react' +import Octicon from '../Octicon' +import RelativeTime from '../RelativeTime' +import classes from './Timeline.secret-scanning.features.stories.module.css' + +/** + * Secret Scanning alert Timeline event examples (Phase 2 of github/primer#6663). + * + * These stories recreate GitHub's live secret-scanning-alert-timeline events + * using the Primer `Timeline` compositional slots. They mirror the established + * Issues (`Timeline.issues.features.stories.tsx`) and Dependabot + * (`Timeline.dependabot.features.stories.tsx`) stories and are sourced from the + * `timeline-audit` inventory (`secret-scanning-timeline-events-for-figma.md`), + * verified against the live React implementation. + * + * SOURCE OF TRUTH — Secret Scanning is FULLY React (NOT ERB). The alert show + * page is a React SPA in `github/github-ui`, package + * `packages/secret-scanning-alerts/`. The timeline is rendered by + * `components/show/AlertTimeline.tsx`, whose `switch (event.type)` is the + * authoritative dispatch for every event's badge variant + octicon + copy. It + * already composes Primer React `Timeline` + `Timeline.Badge variant=`, so the + * badge colors map directly. The actor is rendered by + * `components/shared/UserComponent.tsx` (16px circle avatar + bold login); the + * system actor is `MarkGithubIcon` + bold "GitHub". (No ERB secret-scanning + * timeline exists — the migration to React is complete.) + * + * 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="created"`) 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 / Dependabot groups): + * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar. + * Reserved for comment-style events. Badge-row events like Created do NOT use + * it — the live `AlertTimeline.tsx` renders the actor INLINE in the body + * (`UserComponent`, or the system `MarkGithubIcon` + "GitHub"), not in the + * gutter. We mirror that: actor inline in `Timeline.Body`. + * - `Timeline.Actions` (right-controls slot, #6678): for buttons on the right + * edge (e.g. the delegated-closure "Review request" / "Cancel request" + * buttons). Created has no right controls, so it is omitted here. + * + * BADGE COLORS (live `Timeline.Badge variant=`): success (green) — Creation, + * Reopened; done (purple) — Closed as revoked; danger (red) — Validity active; + * attention (amber) — Validity unknown; default (gray) — everything else. Where + * the live code uses a default (gray) badge with no solid background, downstream + * groups should drive `--timelineBadge-bgColor` inline (as the Issues "not + * planned" variant does) rather than relying on a non-existent `neutral` variant. + */ + +/** + * System "GitHub" actor — live `TimelineItemBody` (`AlertTimeline.tsx`) in + * `isGitHubActor` mode renders ` GitHub` (no avatar). Used by the Created event and by + * automated validity changes. + */ +const GitHubActor = () => ( + <> + + GitHub + +) + +// Muted relative timestamp. The live secret-scanning `TimelineItemBody` renders +// a plain `RelativeTime` with no link wrapper — muted text only (matching the +// Dependabot timeline, unlike the Issues `Ago` deep-link). +const Time = ({date}: {date: string}) => ( + + + +) + +export default { + title: 'Components/Timeline/Events/Secret Scanning', + 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 Created event group — `SecretScanTimeline.eventCreated` (audit § 1). + * + * Source: `case TimelineEventType.Creation` in `AlertTimeline.tsx` — the only + * event that uses `isGitHubActor` unconditionally. The actor is ALWAYS the + * GitHub system actor. Badge: `ShieldIcon` on `success` (green) — the live code + * renders `}>`. Copy is a + * fixed `"opened this alert"` + the relative time. + * + * Single variant — the live `Creation` case has exactly one rendering (no + * source / from-PR / from-push branches like Dependabot's Opened). + */ +export const EventCreated = () => ( +
    + {/* Created — GitHub system actor, ShieldIcon on success (green) */} +
    +

    Created

    + + + + + + + + {'opened this alert '} + + + +
    +
    +) From ea8c6a2eb6e7b704ac07ac1e469e10c7e8101e69 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:36:12 -0700 Subject: [PATCH 2/3] Add remaining Secret Scanning timeline event groups Complete the secret-scanning surface with all 7 event groups (29 variants): Resolution (7 closed reasons + Reopened), Bypass + delegated bypass, Validity change (active/inactive/unknown x automated/manual), Report, delegated closure request/approve/deny/cancel (with Review/Cancel right-controls), and Assignment (actor + assignee avatars). Extract UserActor and CommentSubRow helpers. Every group is verified against the live AlertTimeline.tsx switch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ecret-scanning.features.stories.module.css | 28 +- ...eline.secret-scanning.features.stories.tsx | 686 +++++++++++++++++- 2 files changed, 703 insertions(+), 11 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css index a95131b6aa1..9d9a78e3980 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css @@ -29,13 +29,39 @@ margin-right: var(--base-size-4); } -/* Bold "GitHub" label, mirroring the live `text-bold` span. */ +/* Bold actor name, mirroring the live `text-bold` span. Shared by the system + "GitHub" label and the user `display_login` (live `UserComponent` renders the + login as bold TEXT, not a link). */ .ActorName { font-weight: var(--base-text-weight-semibold); color: var(--fgColor-default); margin-right: var(--base-size-4); } +/* Inline user-actor avatar in the body. Live `UserComponent` renders a 16px + CIRCLE `GitHubAvatar` followed by the bold login. */ +.InlineAvatar { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Optional comment sub-row. Live `TimelineItemBody` renders a small indented + block below the body: a 12px muted `CommentIcon` + f6 comment text. Driven by + `resolution.comment` / `exemption_request.requester_comment` / + `exemption_response.reviewer_comment`. */ +.CommentRow { + display: flex; + align-items: center; + margin-top: var(--base-size-4); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); +} + +.CommentRowIcon { + margin-right: var(--base-size-4); + color: var(--fgColor-muted); +} + /* Muted relative timestamp. The live secret-scanning `TimelineItemBody` renders a plain `RelativeTime` (no link wrapper) — muted text only, matching the Dependabot timeline (and a deliberate difference from the Issues `Ago` diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx index 410f9306509..ee842603ca2 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -2,7 +2,22 @@ import type {Meta} from '@storybook/react-vite' import type {ComponentProps} from '../utils/types' import {FeatureFlags} from '../FeatureFlags' import Timeline from './Timeline' -import {MarkGithubIcon, ShieldIcon} from '@primer/octicons-react' +import { + AlertIcon, + CheckCircleIcon, + CommentIcon, + MarkGithubIcon, + PersonIcon, + ShieldCheckIcon, + ShieldIcon, + ShieldSlashIcon, + SkipIcon, + SyncIcon, + XIcon, +} from '@primer/octicons-react' +import type React from 'react' +import Avatar from '../Avatar' +import {Button} from '../Button' import Octicon from '../Octicon' import RelativeTime from '../RelativeTime' import classes from './Timeline.secret-scanning.features.stories.module.css' @@ -26,7 +41,10 @@ import classes from './Timeline.secret-scanning.features.stories.module.css' * badge colors map directly. The actor is rendered by * `components/shared/UserComponent.tsx` (16px circle avatar + bold login); the * system actor is `MarkGithubIcon` + bold "GitHub". (No ERB secret-scanning - * timeline exists — the migration to React is complete.) + * timeline exists — the migration to React is complete.) The exact event list + * built below is the full set of cases the live `switch` actually renders; the + * defined-but-never-dispatched `REVOCATION` event type (no `case`) renders + * nothing and is intentionally NOT built. * * 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 @@ -42,22 +60,36 @@ import classes from './Timeline.secret-scanning.features.stories.module.css' * * SLOT USAGE (Phase 1 slots — same convention as the Issues / Dependabot groups): * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar. - * Reserved for comment-style events. Badge-row events like Created do NOT use - * it — the live `AlertTimeline.tsx` renders the actor INLINE in the body + * Reserved for comment-style events. The badge-row events here do NOT use it — + * the live `AlertTimeline.tsx` renders the actor INLINE in the body * (`UserComponent`, or the system `MarkGithubIcon` + "GitHub"), not in the * gutter. We mirror that: actor inline in `Timeline.Body`. * - `Timeline.Actions` (right-controls slot, #6678): for buttons on the right - * edge (e.g. the delegated-closure "Review request" / "Cancel request" - * buttons). Created has no right controls, so it is omitted here. + * edge. Only the delegated-closure "requested to dismiss" event has right + * controls (a "Review request" / "Cancel request" button), so it is the only + * group that uses this slot. * * BADGE COLORS (live `Timeline.Badge variant=`): success (green) — Creation, * Reopened; done (purple) — Closed as revoked; danger (red) — Validity active; - * attention (amber) — Validity unknown; default (gray) — everything else. Where - * the live code uses a default (gray) badge with no solid background, downstream - * groups should drive `--timelineBadge-bgColor` inline (as the Issues "not - * planned" variant does) rather than relying on a non-existent `neutral` variant. + * attention (amber) — Validity unknown; default (gray) — everything else. The + * live code passes `variant={undefined}` for the gray events, which renders the + * DEFAULT `Timeline.Badge` (a muted icon on a subtle/borderless circle, NO solid + * fill). We render those as a BARE `` to match exactly — none of + * the secret-scanning gray events is a solid-gray badge, so (unlike the Issues + * "not planned" variant) the `--timelineBadge-bgColor` hook is not used here. + * + * ACCESSIBILITY NOTE: none of the secret-scanning events render an in-text + * `` — actors and resolution reasons are BOLD TEXT (the live + * `UserComponent` uses a `text-bold` span, not a profile link, and reasons are + * bolded plain text). So the axe `link-in-text-block` rule (which failed the + * Dependabot CI for underline-less in-text links) is never exercised by this + * surface. Any future in-text link added here must use `inline`/bold. */ +const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' +const SIX7_AVATAR = 'https://avatars.githubusercontent.com/six7' +const HUBOT_AVATAR = 'https://avatars.githubusercontent.com/hubot' + /** * System "GitHub" actor — live `TimelineItemBody` (`AlertTimeline.tsx`) in * `isGitHubActor` mode renders ` `). Note the login is bold TEXT, not a link (hovercard attrs only), + * so there is no in-text-link a11y concern. Used by every non-system event. + */ +const UserActor = ({login = 'monalisa', src = MONALISA_AVATAR}: {login?: string; src?: string}) => ( + <> + + {login} + +) + // Muted relative timestamp. The live secret-scanning `TimelineItemBody` renders // a plain `RelativeTime` with no link wrapper — muted text only (matching the // Dependabot timeline, unlike the Issues `Ago` deep-link). @@ -80,6 +125,21 @@ const Time = ({date}: {date: string}) => ( ) +/** + * Optional comment sub-row — live `TimelineItemBody` renders a + * `
    {comment}
    ` below the body whenever any of + * `resolution.comment` / `exemption_request.requester_comment` / + * `exemption_response.reviewer_comment` is present. Shared by the Resolution + * closures and the delegated-closure request/approve/deny events. + */ +const CommentSubRow = ({children}: {children: React.ReactNode}) => ( +
    + + {children} +
    +) + export default { title: 'Components/Timeline/Events/Secret Scanning', component: Timeline, @@ -135,3 +195,609 @@ export const EventCreated = () => (
) + +/** + * The Resolution event group — `SecretScanTimeline.eventResolution` (audit § 2). + * + * Source: `case TimelineEventType.Resolution` in `AlertTimeline.tsx`. Two + * shapes share this event type: + * - REOPENED (`resolution.type === 'reopened'`): `SyncIcon` on `success` + * (green), copy "reopened this". The live code renders a `Timeline.Break` + * immediately BEFORE this item (sibling-selector CSS) — reproduced here. + * - CLOSED (any other `resolution.type`): copy "closed this as {reason}", where + * `{reason}` is `resolutionText(resolution.type)` rendered as BOLD TEXT (not a + * link). Only `revoked` gets the special `ShieldCheckIcon` on `done` (purple); + * every other reason uses `ShieldSlashIcon` on the default (gray) badge. + * + * The seven closed reasons are exactly the `resolutionText()` outputs (live + * `helper.ts`): revoked, false positive, won't fix, used in tests, pattern + * deleted, pattern edited, ignored by configuration. Each may carry an optional + * `(comment)` sub-row from `resolution.comment` — demonstrated on the revoked + * variant. Actor is always the user. + */ +export const EventResolution = () => ( +
+ {/* Closed as revoked — the only reason with the purple `done` / ShieldCheck + badge. Shown WITH an optional resolution-comment sub-row. */} +
+

Closed as revoked

+ + + + + + + + {'closed this as '} + revoked + + +
+ + {/* Closed as false positive — gray (default) ShieldSlash badge. */} +
+

Closed as false positive

+ + + + + + + + {'closed this as '} + false positive + + +
+ + {/* Closed as won't fix */} +
+

Closed as won't fix

+ + + + + + + + {'closed this as '} + won't fix + + +
+ + {/* Closed as used in tests */} +
+

Closed as used in tests

+ + + + + + + + {'closed this as '} + used in tests + + +
+ + {/* Closed as pattern deleted */} +
+

Closed as pattern deleted

+ + + + + + + + {'closed this as '} + pattern deleted + + +
+ + {/* Closed as pattern edited */} +
+

Closed as pattern edited

+ + + + + + + + {'closed this as '} + pattern edited + + +
+ + {/* Closed as ignored by configuration (resolution type `hidden_by_config`) */} +
+

Closed as ignored by configuration

+ + + + + + + + {'closed this as '} + ignored by configuration + + +
+ + {/* Reopened — SyncIcon on success (green), preceded by a Timeline.Break. + The live code emits the Break as a sibling immediately BEFORE the + reopened Item so the sibling-selector CSS applies. */} +
+

Reopened

+ + + + + + + + + {'reopened this '} + + + +
+
+) + +/** + * The Push-protection / bypass event group — + * `SecretScanTimeline.eventPushProtection` (audit § 3). + * + * Source: `case TimelineEventType.Bypass`, + * `DelegatedBypassRequestOpened`, `DelegatedBypassRequestApproved` in + * `AlertTimeline.tsx`. All three use the user actor and the default (gray) + * badge; only the icon + copy differ. The two delegated variants render only + * for repos with delegated bypass enabled (backend-gated org feature). + */ +export const EventBypass = () => ( +
+ {/* Bypassed — AlertIcon, default (gray) badge */} +
+

Bypassed push protection

+ + + + + + + + {'bypassed push protection '} + + + +
+ + {/* Bypass requested — CommentIcon. Delegated bypass: only renders when the + repo has delegated bypass enabled. */} +
+

Bypass requested (delegated bypass enabled)

+ + + + + + + + {'requested bypass privileges '} + + + +
+ + {/* Bypass approved — CheckCircleIcon. Delegated bypass: gated as above. */} +
+

Bypass approved (delegated bypass enabled)

+ + + + + + + + {'approved a bypass '} + + + +
+
+) + +/** + * The Validity-change event group — `SecretScanTimeline.eventValidity` + * (audit § 4). + * + * Source: `ValidityChangeTimelineEvent` in `AlertTimeline.tsx`. The validity + * bucket drives the badge + icon: active -> `AlertIcon` on `danger` (red); + * inactive -> `SkipIcon` on default (gray); unknown -> `AlertIcon` on + * `attention` (amber). Whether the change is AUTOMATED (no `event.actor` -> + * GitHub system actor + "verified this secret is …" / "is unable to determine + * …") or MANUAL (`event.actor` present -> user actor + "set validity to …") is + * decided purely by `!event.actor`. Both forms are shown per bucket. + */ +export const EventValidityChange = () => ( +
+ {/* Active — automated (GitHub), AlertIcon on danger (red) */} +
+

Validity: active (automated)

+ + + + + + + + {'verified this secret is active '} + + + +
+ + {/* Active — manual (user), same danger badge */} +
+

Validity: active (manual)

+ + + + + + + + {'set validity to active '} + + + +
+ + {/* Inactive — automated (GitHub), SkipIcon on default (gray) */} +
+

Validity: inactive (automated)

+ + + + + + + + {'verified this secret is inactive '} + + + +
+ + {/* Inactive — manual (user) */} +
+

Validity: inactive (manual)

+ + + + + + + + {'set validity to inactive '} + + + +
+ + {/* Unknown — automated (GitHub), AlertIcon on attention (amber) */} +
+

Validity: unknown (automated)

+ + + + + + + + {'is unable to determine the validity of this secret '} + + + +
+ + {/* Unknown — manual (user) */} +
+

Validity: unknown (manual)

+ + + + + + + + {'set validity to unknown '} + + + +
+
+) + +/** + * The Report event group — `SecretScanTimeline.eventReport` (audit § 5). + * + * Source: `case TimelineEventType.Report` in `AlertTimeline.tsx`. Badge: + * `ShieldCheckIcon` on the default (gray) badge. Copy: "reported this secret". + * The case does NOT pass `isGitHubActor`, so the actor is the USER (not the + * GitHub system actor) — confirmed against the live switch. + */ +export const EventReport = () => ( +
+ {/* Reported — ShieldCheckIcon, default (gray) badge, user actor */} +
+

Reported

+ + + + + + + + {'reported this secret '} + + + +
+
+) + +/** + * The delegated-closure (dismissal) event group — + * `SecretScanTimeline.eventClosureRequest` (audit § 6). + * + * Source: `case TimelineEventType.DelegatedClosureRequestOpened` / + * `…Approved` / `…Rejected` / `…Cancelled` in `AlertTimeline.tsx`. This is the + * org-level delegated-dismissal feature (backend-gated). All four use the user + * actor and the default (gray) badge; icon + copy differ: + * - Opened -> `CommentIcon`, "requested to dismiss this[ as {reason}]" + * - Approved -> `CheckCircleIcon`, "approved dismissal" + * - Rejected -> `XIcon`, "denied dismissal" + * - Cancelled -> `SkipIcon`, "cancelled request to dismiss" + * + * RIGHT CONTROLS (`Timeline.Actions`): the Opened event shows, while the request + * is pending & not expired, EITHER a small primary "Review request" button + * (shown to reviewers via the `show_closure_request_review_buttons` payload + * flag, which opens `ClosureRequestReviewButtons`' review dialog) OR a small + * invisible "Cancel request" button (shown to the requester via + * `show_closure_request_cancel_button`). They are driven by two independent, + * viewer-specific payload flags — mutually exclusive per viewer — so both + * variants are shown below. The optional `(comment)` sub-row carries the + * requester comment (Opened) or reviewer comment (Approved / Denied). + */ +export const EventClosureRequest = () => ( +
+ {/* Requested — with the reviewer-facing "Review request" primary button and + a requester comment sub-row. The `[ as {reason}]` clause is the + un-bolded `resolutionText(exemption_request.reason)`. */} +
+

Dismissal requested (reviewer view)

+ + + + + + + + {'requested to dismiss this as false positive '} + + + + + + +
+ + {/* Requested — requester view: the invisible "Cancel request" button. */} +
+

Dismissal requested (requester view)

+ + + + + + + + {'requested to dismiss this as false positive '} + + + + + + +
+ + {/* Approved — CheckCircleIcon, with a reviewer comment sub-row. */} +
+

Dismissal approved

+ + + + + + + + {'approved dismissal '} + + + +
+ + {/* Denied — XIcon */} +
+

Dismissal denied

+ + + + + + + + {'denied dismissal '} + + + +
+ + {/* Cancelled — SkipIcon */} +
+

Dismissal request cancelled

+ + + + + + + + {'cancelled request to dismiss '} + + + +
+
+) + +/** + * The Assignment event group — `SecretScanTimeline.eventAssignments` + * (audit § 7). + * + * Source: `AssignmentChangeTimelineEvent` in `AlertTimeline.tsx`. Badge: + * `PersonIcon` on the default (gray) badge. Unlike the avatar-less PR/Issue + * assignment pattern, the actor AND the assignee/unassignee are each rendered + * with an avatar via `UserComponent` (matching Dependabot). The five shapes are + * derived from which of `assigned_user` / `unassigned_user` is present and + * whether the actor equals the (un)assignee (self vs other). + */ +export const EventAssignment = () => ( +
+ {/* Self-assigned — actor === assignee */} +
+

Self-assigned

+ + + + + + + + {'self-assigned this '} + + + +
+ + {/* Assigned someone else — both actor + assignee avatars */} +
+

Assigned another user

+ + + + + + + + {'assigned '} + + + +
+ + {/* Self-unassigned — actor removed their own assignment */} +
+

Removed own assignment

+ + + + + + + + {'removed their assignment '} + + + +
+ + {/* Unassigned someone else */} +
+

Unassigned another user

+ + + + + + + + {'unassigned '} + + + +
+ + {/* Assigned one user and unassigned another in a single event */} +
+

Assigned and unassigned

+ + + + + + + + {'assigned '} + + {' and unassigned '} + + + +
+
+) From 2a80e64690785942722597553cf17696ee8cd662 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:04:08 -0700 Subject: [PATCH 3/3] Address Copilot review on Secret Scanning timeline stories - Render the Resolution "Reopened" variant with the preceding closed Item so Timeline.Break sits between two items (mirrors live break-between-events placement; avoids a stray first-child break). - Use the u/{id}?v=4 avatar URL form for the demo assignee avatars, matching the convention used elsewhere in the Timeline stories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...eline.secret-scanning.features.stories.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx index ee842603ca2..cbe5952617d 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -87,8 +87,8 @@ import classes from './Timeline.secret-scanning.features.stories.module.css' */ const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' -const SIX7_AVATAR = 'https://avatars.githubusercontent.com/six7' -const HUBOT_AVATAR = 'https://avatars.githubusercontent.com/hubot' +const SIX7_AVATAR = 'https://avatars.githubusercontent.com/u/4548309?v=4' +const HUBOT_AVATAR = 'https://avatars.githubusercontent.com/u/480938?v=4' /** * System "GitHub" actor — live `TimelineItemBody` (`AlertTimeline.tsx`) in @@ -340,10 +340,23 @@ export const EventResolution = () => ( {/* Reopened — SyncIcon on success (green), preceded by a Timeline.Break. The live code emits the Break as a sibling immediately BEFORE the - reopened Item so the sibling-selector CSS applies. */} + reopened Item so the sibling-selector CSS applies. We include the + preceding (closed) Item here so the Break renders BETWEEN two items, as + it does in product — mirroring the live "break between events" + placement rather than leaving the Break as a stray first child. */}

Reopened

+ + + + + + + {'closed this as '} + false positive +