From 1046f3d52702975883b7880e08d392810992e305 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/6] 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 99e077ddb20ec07cb86dfe26c54294a7557f13f3 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/6] 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 f0858e5c5afe70ffa95b7d4afd8af7bba17890e2 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/6] 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 + From 23d019a54001b6b7195f64f437fad588eb967d82 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:18:51 -0700 Subject: [PATCH 4/6] a11y: remove badge double-announce on decorative Timeline badges (Secret Scanning stories) The badge octicons each carried an aria-label restating the adjacent Timeline.Body sentence, so screen readers announced every event twice. Render them bare (no aria-label, so Primer defaults them to aria-hidden), matching the live AlertTimeline.tsx, which leaves the Body text as the sole accessible description. Documented the rationale in the header a11y note. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...eline.secret-scanning.features.stories.tsx | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 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 cbe5952617d..8fe6c668328 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -84,6 +84,12 @@ import classes from './Timeline.secret-scanning.features.stories.module.css' * 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. + * + * The badge octicons are DECORATIVE — they are rendered bare (no `aria-label`, + * so Primer defaults them to `aria-hidden`), exactly as the live + * `AlertTimeline.tsx` renders them. The adjacent `Timeline.Body` sentence is the + * sole accessible description of each event, so the badge icon must NOT restate + * it (a labelled badge would make screen readers announce the event twice). */ const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' @@ -183,7 +189,7 @@ export const EventCreated = () => ( - + @@ -224,7 +230,7 @@ export const EventResolution = () => ( - + @@ -242,7 +248,7 @@ export const EventResolution = () => ( - + @@ -259,7 +265,7 @@ export const EventResolution = () => ( - + @@ -276,7 +282,7 @@ export const EventResolution = () => ( - + @@ -293,7 +299,7 @@ export const EventResolution = () => ( - + @@ -310,7 +316,7 @@ export const EventResolution = () => ( - + @@ -327,7 +333,7 @@ export const EventResolution = () => ( - + @@ -349,7 +355,7 @@ export const EventResolution = () => ( - + @@ -360,7 +366,7 @@ export const EventResolution = () => ( - + @@ -391,7 +397,7 @@ export const EventBypass = () => ( - + @@ -409,7 +415,7 @@ export const EventBypass = () => ( - + @@ -426,7 +432,7 @@ export const EventBypass = () => ( - + @@ -459,7 +465,7 @@ export const EventValidityChange = () => ( - + @@ -476,7 +482,7 @@ export const EventValidityChange = () => ( - + @@ -493,7 +499,7 @@ export const EventValidityChange = () => ( - + @@ -510,7 +516,7 @@ export const EventValidityChange = () => ( - + @@ -527,7 +533,7 @@ export const EventValidityChange = () => ( - + @@ -544,7 +550,7 @@ export const EventValidityChange = () => ( - + @@ -573,7 +579,7 @@ export const EventReport = () => ( - + @@ -619,7 +625,7 @@ export const EventClosureRequest = () => ( - + @@ -642,7 +648,7 @@ export const EventClosureRequest = () => ( - + @@ -664,7 +670,7 @@ export const EventClosureRequest = () => ( - + @@ -682,7 +688,7 @@ export const EventClosureRequest = () => ( - + @@ -700,7 +706,7 @@ export const EventClosureRequest = () => ( - + @@ -732,7 +738,7 @@ export const EventAssignment = () => ( - + @@ -749,7 +755,7 @@ export const EventAssignment = () => ( - + @@ -766,7 +772,7 @@ export const EventAssignment = () => ( - + @@ -783,7 +789,7 @@ export const EventAssignment = () => ( - + @@ -800,7 +806,7 @@ export const EventAssignment = () => ( - + From b75fc67452f486a26a3d3748cca096605a623b61 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:39:05 -0700 Subject: [PATCH 5/6] refactor(Timeline): use shared internal story helpers (Secret Scanning) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the local story scaffolding for the shared internal helpers introduced on the base branch: VariantSection, RealisticTimeline, InlineAvatar, and MutedTime. Delete the now-unused local .Variant/.VariantLabel/.RealisticTimeline/ .InlineAvatar/.Timestamp classes. Pure refactor — no visual or functional change. The bold actor name stays a local bold (.ActorName) rather than the shared BoldLink: this surface's actor is faithfully NOT a link (live UserComponent renders a text-bold span), so BoldLink would add semantics and a hover color change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ecret-scanning.features.stories.module.css | 42 +-- ...eline.secret-scanning.features.stories.tsx | 247 ++++++++---------- 2 files changed, 107 insertions(+), 182 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 9d9a78e3980..030c7f248dd 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 @@ -1,26 +1,3 @@ -.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. */ @@ -31,20 +8,15 @@ /* 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). */ + login as bold TEXT, not a link — so this surface intentionally has no in-text + actor link and does NOT use the shared `BoldLink` helper, which renders an + ``). */ .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` / @@ -61,11 +33,3 @@ 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` - 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 index 8fe6c668328..78c7fd47d69 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -16,10 +16,9 @@ import { 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 {InlineAvatar, MutedTime, RealisticTimeline, VariantSection} from './internal/timelineStoryHelpers' import classes from './Timeline.secret-scanning.features.stories.module.css' /** @@ -117,20 +116,11 @@ const GitHubActor = () => ( */ 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). -const Time = ({date}: {date: string}) => ( - - - -) - /** * Optional comment sub-row — live `TimelineItemBody` renders a * `
( -
+ {/* Created — GitHub system actor, ShieldIcon on success (green) */} -
-

Created

+ @@ -194,12 +183,12 @@ export const EventCreated = () => ( {'opened this alert '} - -
-
+ + ) /** @@ -222,11 +211,10 @@ export const EventCreated = () => ( * 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

+ @@ -235,16 +223,15 @@ export const EventResolution = () => ( {'closed this as '} - revoked -
+ {/* Closed as false positive — gray (default) ShieldSlash badge. */} -
-

Closed as false positive

+ @@ -253,15 +240,14 @@ export const EventResolution = () => ( {'closed this as '} - false positive -
+ {/* Closed as won't fix */} -
-

Closed as won't fix

+ @@ -270,15 +256,14 @@ export const EventResolution = () => ( {'closed this as '} - won't fix -
+ {/* Closed as used in tests */} -
-

Closed as used in tests

+ @@ -287,15 +272,14 @@ export const EventResolution = () => ( {'closed this as '} - used in tests -
+ {/* Closed as pattern deleted */} -
-

Closed as pattern deleted

+ @@ -304,15 +288,14 @@ export const EventResolution = () => ( {'closed this as '} - pattern deleted -
+ {/* Closed as pattern edited */} -
-

Closed as pattern edited

+ @@ -321,15 +304,14 @@ export const EventResolution = () => ( {'closed this as '} - pattern edited -
+ {/* Closed as ignored by configuration (resolution type `hidden_by_config`) */} -
-

Closed as ignored by configuration

+ @@ -338,11 +320,11 @@ export const EventResolution = () => ( {'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 @@ -350,8 +332,7 @@ export const EventResolution = () => ( 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

+ @@ -360,7 +341,7 @@ export const EventResolution = () => ( {'closed this as '} - false positive @@ -371,12 +352,12 @@ export const EventResolution = () => ( {'reopened this '} - -
-
+ + ) /** @@ -390,10 +371,9 @@ export const EventResolution = () => ( * for repos with delegated bypass enabled (backend-gated org feature). */ export const EventBypass = () => ( -
+ {/* Bypassed — AlertIcon, default (gray) badge */} -
-

Bypassed push protection

+ @@ -402,16 +382,15 @@ export const EventBypass = () => ( {'bypassed push protection '} - -
+ {/* Bypass requested — CommentIcon. Delegated bypass: only renders when the repo has delegated bypass enabled. */} -
-

Bypass requested (delegated bypass enabled)

+ @@ -420,15 +399,14 @@ export const EventBypass = () => ( {'requested bypass privileges '} - -
+ {/* Bypass approved — CheckCircleIcon. Delegated bypass: gated as above. */} -
-

Bypass approved (delegated bypass enabled)

+ @@ -437,12 +415,12 @@ export const EventBypass = () => ( {'approved a bypass '} - -
-
+ + ) /** @@ -458,10 +436,9 @@ export const EventBypass = () => ( * decided purely by `!event.actor`. Both forms are shown per bucket. */ export const EventValidityChange = () => ( -
+ {/* Active — automated (GitHub), AlertIcon on danger (red) */} -
-

Validity: active (automated)

+ @@ -470,15 +447,14 @@ export const EventValidityChange = () => ( {'verified this secret is active '} - -
+ {/* Active — manual (user), same danger badge */} -
-

Validity: active (manual)

+ @@ -487,15 +463,14 @@ export const EventValidityChange = () => ( {'set validity to active '} - -
+ {/* Inactive — automated (GitHub), SkipIcon on default (gray) */} -
-

Validity: inactive (automated)

+ @@ -504,15 +479,14 @@ export const EventValidityChange = () => ( {'verified this secret is inactive '} - -
+ {/* Inactive — manual (user) */} -
-

Validity: inactive (manual)

+ @@ -521,15 +495,14 @@ export const EventValidityChange = () => ( {'set validity to inactive '} - -
+ {/* Unknown — automated (GitHub), AlertIcon on attention (amber) */} -
-

Validity: unknown (automated)

+ @@ -538,15 +511,14 @@ export const EventValidityChange = () => ( {'is unable to determine the validity of this secret '} - -
+ {/* Unknown — manual (user) */} -
-

Validity: unknown (manual)

+ @@ -555,12 +527,12 @@ export const EventValidityChange = () => ( {'set validity to unknown '} - -
-
+ + ) /** @@ -572,10 +544,9 @@ export const EventValidityChange = () => ( * GitHub system actor) — confirmed against the live switch. */ export const EventReport = () => ( -
+ {/* Reported — ShieldCheckIcon, default (gray) badge, user actor */} -
-

Reported

+ @@ -584,12 +555,12 @@ export const EventReport = () => ( {'reported this secret '} - -
-
+ + ) /** @@ -616,12 +587,11 @@ export const EventReport = () => ( * 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)

+ @@ -630,7 +600,7 @@ export const EventClosureRequest = () => ( {'requested to dismiss this as false positive '} - @@ -640,11 +610,10 @@ export const EventClosureRequest = () => ( -
+ {/* Requested — requester view: the invisible "Cancel request" button. */} -
-

Dismissal requested (requester view)

+ @@ -653,7 +622,7 @@ export const EventClosureRequest = () => ( {'requested to dismiss this as false positive '} -
+ {/* Approved — CheckCircleIcon, with a reviewer comment sub-row. */} -
-

Dismissal approved

+ @@ -675,16 +643,15 @@ export const EventClosureRequest = () => ( {'approved dismissal '} - -
+ {/* Denied — XIcon */} -
-

Dismissal denied

+ @@ -693,16 +660,15 @@ export const EventClosureRequest = () => ( {'denied dismissal '} - -
+ {/* Cancelled — SkipIcon */} -
-

Dismissal request cancelled

+ @@ -711,12 +677,12 @@ export const EventClosureRequest = () => ( {'cancelled request to dismiss '} - -
-
+ + ) /** @@ -731,10 +697,9 @@ export const EventClosureRequest = () => ( * whether the actor equals the (un)assignee (self vs other). */ export const EventAssignment = () => ( -
+ {/* Self-assigned — actor === assignee */} -
-

Self-assigned

+ @@ -743,15 +708,14 @@ export const EventAssignment = () => ( {'self-assigned this '} - -
+ {/* Assigned someone else — both actor + assignee avatars */} -
-

Assigned another user

+ @@ -760,15 +724,14 @@ export const EventAssignment = () => ( {'assigned '} - -
+ {/* Self-unassigned — actor removed their own assignment */} -
-

Removed own assignment

+ @@ -777,15 +740,14 @@ export const EventAssignment = () => ( {'removed their assignment '} - -
+ {/* Unassigned someone else */} -
-

Unassigned another user

+ @@ -794,15 +756,14 @@ export const EventAssignment = () => ( {'unassigned '} - -
+ {/* Assigned one user and unassigned another in a single event */} -
-

Assigned and unassigned

+ @@ -813,10 +774,10 @@ export const EventAssignment = () => ( {'assigned '} {' and unassigned '} - -
-
+ + ) From e459c56af13b0bb1f5f538552be06311b5387df9 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:10:21 -0700 Subject: [PATCH 6/6] refactor(Timeline): use shared UserActor + EventSubRow helpers (Secret Scanning) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the local GitHubActor/UserActor/CommentSubRow helpers for the shared UserActor and EventSubRow from ./internal/timelineStoryHelpers (merged in #8140), resolving adierkens' review. The GitHub system actor uses UserActor's icon prop; user actors pass size={16} to preserve the live 16px avatar; comment sub-rows use EventSubRow with CommentIcon at size 12. Deletes the now-unused local helpers and the entire per-surface CSS module (.ActorName/.ActorIcon/.CommentRow/.CommentRowIcon) — the shared helpers' classes (.BoldName/.ActorIcon/.EventSubRow/.EventSubRowIcon) are byte-identical, so this is a pure refactor with zero visual change (verified headless: system actor glyph, 16px user avatar + bold login, and comment sub-rows render identically). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ecret-scanning.features.stories.module.css | 35 ----- ...eline.secret-scanning.features.stories.tsx | 133 +++++++----------- 2 files changed, 50 insertions(+), 118 deletions(-) delete mode 100644 packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css 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 deleted file mode 100644 index 030c7f248dd..00000000000 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.module.css +++ /dev/null @@ -1,35 +0,0 @@ -/* 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 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 — so this surface intentionally has no in-text - actor link and does NOT use the shared `BoldLink` helper, which renders an - `
`). */ -.ActorName { - font-weight: var(--base-text-weight-semibold); - color: var(--fgColor-default); - 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); -} 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 78c7fd47d69..33a3f94780e 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -15,11 +15,9 @@ import { SyncIcon, XIcon, } from '@primer/octicons-react' -import type React from 'react' import {Button} from '../Button' import Octicon from '../Octicon' -import {InlineAvatar, MutedTime, RealisticTimeline, VariantSection} from './internal/timelineStoryHelpers' -import classes from './Timeline.secret-scanning.features.stories.module.css' +import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} from './internal/timelineStoryHelpers' /** * Secret Scanning alert Timeline event examples (Phase 2 of github/primer#6663). @@ -91,51 +89,11 @@ import classes from './Timeline.secret-scanning.features.stories.module.css' * it (a labelled badge would make screen readers announce the event twice). */ -const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' +// Demo assignee avatars for the Assignment group. All other actors fall back to +// the shared `UserActor` default avatar (monalisa). 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 - * `isGitHubActor` mode renders ` GitHub` (no avatar). Used by the Created event and by - * automated validity changes. - */ -const GitHubActor = () => ( - <> - - GitHub - -) - -/** - * User actor — live `UserComponent` (`components/shared/UserComponent.tsx`): - * a 16px CIRCLE `GitHubAvatar` + bold `display_login` (``). 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} - -) - -/** - * 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, @@ -181,7 +139,7 @@ export const EventCreated = () => ( - + {'opened this alert '} @@ -221,10 +179,12 @@ export const EventResolution = () => ( - + {'closed this as '} revoked - Rotated the leaked token and confirmed the provider revoked it. + + Rotated the leaked token and confirmed the provider revoked it. + @@ -238,7 +198,7 @@ export const EventResolution = () => ( - + {'closed this as '} false positive @@ -254,7 +214,7 @@ export const EventResolution = () => ( - + {'closed this as '} won't fix @@ -270,7 +230,7 @@ export const EventResolution = () => ( - + {'closed this as '} used in tests @@ -286,7 +246,7 @@ export const EventResolution = () => ( - + {'closed this as '} pattern deleted @@ -302,7 +262,7 @@ export const EventResolution = () => ( - + {'closed this as '} pattern edited @@ -318,7 +278,7 @@ export const EventResolution = () => ( - + {'closed this as '} ignored by configuration @@ -339,7 +299,7 @@ export const EventResolution = () => ( - + {'closed this as '} false positive @@ -350,7 +310,7 @@ export const EventResolution = () => ( - + {'reopened this '} @@ -380,7 +340,7 @@ export const EventBypass = () => ( - + {'bypassed push protection '} @@ -397,7 +357,7 @@ export const EventBypass = () => ( - + {'requested bypass privileges '} @@ -413,7 +373,7 @@ export const EventBypass = () => ( - + {'approved a bypass '} @@ -445,7 +405,7 @@ export const EventValidityChange = () => ( - + {'verified this secret is active '} @@ -461,7 +421,7 @@ export const EventValidityChange = () => ( - + {'set validity to active '} @@ -477,7 +437,7 @@ export const EventValidityChange = () => ( - + {'verified this secret is inactive '} @@ -493,7 +453,7 @@ export const EventValidityChange = () => ( - + {'set validity to inactive '} @@ -509,7 +469,7 @@ export const EventValidityChange = () => ( - + {'is unable to determine the validity of this secret '} @@ -525,7 +485,7 @@ export const EventValidityChange = () => ( - + {'set validity to unknown '} @@ -553,7 +513,7 @@ export const EventReport = () => ( - + {'reported this secret '} @@ -598,10 +558,12 @@ export const EventClosureRequest = () => ( - + {'requested to dismiss this as false positive '} - This token only ever pointed at our throwaway sandbox. + + This token only ever pointed at our throwaway sandbox. +