From 352907cdca6f9fa049d746b70e2f12ce534c56de Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:01:56 -0700 Subject: [PATCH 1/3] Add Code Scanning Detected timeline story (proof-of-pattern) Recreate GitHub's Code Scanning alert 'Detected' timeline events (alert created / appeared in branch / reappeared) with Primer React Timeline, translated from the live ERB (CodeScanning::TimelineComponent). Scoped to a proof-of-pattern: one event group + file scaffold + helpers, mirroring the Dependabot and Secret Scanning story conventions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ....code-scanning.features.stories.module.css | 111 +++++++ ...imeline.code-scanning.features.stories.tsx | 277 ++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css create mode 100644 packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css new file mode 100644 index 00000000000..9b0a31cd34d --- /dev/null +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css @@ -0,0 +1,111 @@ +.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 + 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; +} + +/* Bold body text, mirroring the live `item.with_body(font_weight: :bold)` used + by every system (no-actor) Code Scanning event. */ +.BoldBody { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +/* Bold branch ref — live `formatted_ref_name` renders + `Primer::Beta::Text.new(font_weight: :bold, classes: "branch-name")` (dotcom + defines no `.branch-name` rule, so it is plain bold text, NOT the monospace + `BranchName` pill). */ +.RefName { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +/* Muted relative timestamp. The shared `timeline_item_component` renders a plain + `time_ago_in_words_js` — muted text only, no link wrapper (matching the + Dependabot / Secret Scanning timelines, unlike the Issues `Ago` deep-link). */ +.Timestamp { + color: var(--fgColor-muted); + margin-left: var(--base-size-4); +} + +/* "in configuration {category}" inline pill — live `show_analysis_origin?` rows + render `Primer::Beta::Truncate.new(bg: :subtle, font_family: :mono, + font_size: 6, border_radius: 1)`: a SUBTLE gray monospace rounded pill. + Rendered only when the alert has more than one analysis category. */ +.ConfigPill { + display: inline-block; + margin-left: var(--base-size-4); + padding: 0 var(--base-size-4); + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + color: var(--fgColor-default); + background-color: var(--bgColor-muted); + border-radius: var(--borderRadius-small); + vertical-align: middle; +} + +/* Supplementary sub-row below a detection event. Live ERB renders a condensed + `Primer::Beta::TimelineItem(condensed: true, font_size: 6, classes: + "tmp-pl-5")` — a small, indented row beneath the main item. We mirror the + established Dependabot / Secret Scanning precedent and compose it as a small + muted block inside `Timeline.Body` (not a badge-less Timeline.Item). */ +.SubRow { + margin-top: var(--base-size-8); + padding-left: var(--base-size-4); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); +} + +/* Bold segment inside the path sub-row — live ERB bolds + `{file_name}:{start_line} on branch` via + `Primer::Beta::Text(color: :default, font_weight: :bold)`. */ +.SubRowStrong { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +/* Workflow-run sub-row icon (the green check / pending dot before the run + link). Live ERB renders a check-suite conclusion octicon. */ +.SubRowIconSuccess { + margin-right: var(--base-size-4); + color: var(--fgColor-success); + vertical-align: middle; +} + +/* Bold workflow-run link — live ERB `Primer::Beta::Link(scheme: :primary, + font_weight: :bold)`. The a11y `link-in-text-block` rule requires in-text + links to be visually distinct without relying on color, so the bold weight is + the non-color differentiator (matching the Issues / Dependabot precedent). */ +.WorkflowLink { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +.WorkflowLink:hover { + color: var(--fgColor-accent); +} + +/* Monospace commit-SHA link — live ERB `Primer::Beta::Link(scheme: :secondary, + font_family: :mono)` truncated to 8 chars. */ +.CommitSha { + margin-left: var(--base-size-4); + font-family: var(--fontStack-monospace); + color: var(--fgColor-muted); +} diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx new file mode 100644 index 00000000000..ebb88306199 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx @@ -0,0 +1,277 @@ +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 {GitBranchIcon, ShieldIcon} from '@primer/octicons-react' +import Label from '../Label' +import Link from '../Link' +import Octicon from '../Octicon' +import RelativeTime from '../RelativeTime' +import classes from './Timeline.code-scanning.features.stories.module.css' + +/** + * Code Scanning alert Timeline event examples (Phase 2 of github/primer#6663). + * + * These stories recreate GitHub's live code-scanning-alert-timeline events using + * the Primer `Timeline` compositional slots. They mirror the established Issues + * (`Timeline.issues.features.stories.tsx`), Dependabot + * (`Timeline.dependabot.features.stories.tsx`), and Secret Scanning + * (`Timeline.secret-scanning.features.stories.tsx`) stories, and are sourced + * from the `timeline-audit` inventory + * (`code-scanning-timeline-events-for-figma.md`), verified against the live ERB. + * + * SOURCE OF TRUTH — Code Scanning is NOT (yet) migrated to React. The alert + * timeline is entirely SERVER-RENDERED ERB (ViewComponent), exactly like the + * Dependabot surface. The events are dispatched by + * `CodeScanning::TimelineComponent` (`app/components/code_scanning/ + * timeline_component.html.erb`), whose `case timeline_event.type` is the + * authoritative event list. Every row is rendered through the shared + * `CodeScanning::TimelineItemComponent` (`timeline_item_component.html.erb`), + * which wraps a `Primer::Beta::TimelineItem` + badge + body and appends optional + * supplementary sub-rows (commit card, path, workflow-run, resolution/reviewer + * comment). There is no React equivalent in `github/github-ui`. So each event + * below is translated faithfully from the live ERB into Primer React, with the + * ERB source noted 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. + * + * THIS FILE IS A PROOF-OF-PATTERN: it ships only the **Detected** event group + * (alert created / appeared / reappeared) plus the file scaffold + helpers. The + * remaining groups (Fixed / Config-deleted, Closed by user, Reopened, Dismissal + * requested, Dismissal reviewed) are deliberately deferred to a follow-up so the + * conventions below can be reviewed first. + * + * AUTHORITATIVE EVENT LIST (live `timeline_component.html.erb` dispatch), for + * planning the remaining groups: + * - ALERT_CREATED — shield (default), bold "First detected in commit", no actor + * - ALERT_APPEARED_IN_BRANCH — git-branch (default), bold "Appeared in branch + * {ref}", no actor + * - ALERT_REAPPEARED — shield (default), bold "Reappeared in branch {ref}", no + * actor + * - ALERT_CLOSED_BECAME_FIXED — shield-check on `done` (SOLID purple) when the + * event ref is the selected ref, else `check` (default); bold "Fixed in + * branch {ref}", no actor + * - ALERT_CLOSED_BECAME_OUTDATED — same conditional shield-check/check badge; + * bold "Closed as {category} configuration was deleted in branch {ref}", no + * actor + * - ALERT_CLOSED_BY_USER — shield-x on `danger` (SOLID red); USER actor; + * "closed this as {reason}" + * - ALERT_REOPENED_BY_USER — dot-fill on `success` (SOLID green); USER actor; + * "reopened this" + * - ALERT_DISMISSAL_REQUESTED — comment (default); USER actor; "requested to + * dismiss this as {reason}"; carries the Review-request button + * - ALERT_DISMISSAL_REVIEWED — check/x (default); USER actor; "approved / + * denied dismissal" + * + * FEATURE-GATED PATHS: the audit flagged the two delegated-dismissal events + * (DISMISSAL_REQUESTED / DISMISSAL_REVIEWED) as only rendering when + * `delegated_dismissal_enabled?`; the Review-request button itself is further + * gated on `timeline_event.show_dismissal_actions`. These are dormant on most + * repos — worth confirming the live gating before building those groups. + * + * BADGE MODEL (live `timeline_item_component.html.erb` → + * `item.with_badge(bg: @background, color: @color, icon: @icon)`, defaulting to + * `background: :subtle, color: :default`): only the events that explicitly pass + * a `background:` (`done_emphasis` / `danger_emphasis` / `success_emphasis`) + * render a SOLID-color badge → `Timeline.Badge variant=`. Every other event — + * including the entire Detected group below — passes only an `icon:`, so it + * renders as a BARE `` (default gray circle, NO solid fill). We + * render those as a bare badge to match exactly — the `--timelineBadge-bgColor` + * hook is intentionally NOT used. + * + * ACTOR TREATMENT: Code Scanning splits cleanly into SYSTEM events (the whole + * Detected group, Fixed, Config-deleted) which render NO actor — just the bold + * body text — and USER events (Closed by user, Reopened, both Dismissal events) + * which render a CIRCLE 20px avatar + bold `display_login` profile link. The + * Detected group below is system-only, so no actor and no in-text link appears. + * + * ACCESSIBILITY NOTE: the Detected group renders no in-text `` in the main + * body (system events are bold text only). The only links are in the workflow + * sub-row, where the bold weight is the non-color differentiator required by the + * axe `link-in-text-block` rule. Any in-text link added when the USER-actor + * groups are built must likewise use `inline`/bold styling. + * + * RIGHT CONTROLS (`Timeline.Actions`): the shared row renders a right-aligned + * tool-version `Primer::Beta::Label(scheme: :secondary)` whenever the event + * carries a `tool_version` (the title reads "Tool version X" or "Tool upgraded + * to X"). It is `margin-left: auto` inside the body in the ERB; we map it to the + * `Timeline.Actions` right-controls slot. The "First detected" variant below + * demonstrates it. + */ + +/** + * Bold branch ref — live `formatted_ref_name` renders + * `Primer::Beta::Text.new(font_weight: :bold, classes: "branch-name")`. Dotcom + * defines no `.branch-name` rule, so it is plain bold text (NOT the monospace + * `BranchName` pill). + */ +const Ref = ({name}: {name: string}) => {name} + +// Muted relative timestamp. The shared `timeline_item_component` renders a plain +// `time_ago_in_words_js` (no link wrapper) — muted text only. +const Time = ({date}: {date: string}) => ( + + + +) + +/** + * "in configuration {category}" inline pill — live `show_analysis_origin?` rows + * render `Primer::Beta::Truncate.new(bg: :subtle, font_family: :mono, + * font_size: 6, border_radius: 1)`: a subtle gray monospace rounded pill. Only + * shown when the alert has more than one analysis category + * (`has_more_than_one_category?`). + */ +const ConfigPill = ({category}: {category: string}) => ( + <> + {' in configuration'} + {category} + +) + +/** + * Optional supplementary sub-row below a detection event. Live ERB renders these + * as a condensed `Primer::Beta::TimelineItem(condensed: true, font_size: 6, + * classes: "tmp-pl-5")`. We mirror the established Dependabot / Secret Scanning + * precedent and compose the sub-row as a small muted block inside + * `Timeline.Body` rather than a badge-less Timeline.Item. + */ +const SubRow = ({children}: {children: React.ReactNode}) =>
{children}
+ +export default { + title: 'Components/Timeline/Events/Code 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 Detected event group — `CodeScanTimeline.eventDetected` (audit § 1). + * + * Source: the `ALERT_CREATED`, `ALERT_APPEARED_IN_BRANCH`, and `ALERT_REAPPEARED` + * cases in `timeline_component.html.erb`. All three are SYSTEM events with NO + * actor — the body is bold text only (`item.with_body(font_weight: :bold)`) — + * and all three pass only an `icon:` to `with_badge`, so they render as a BARE + * `` (default gray circle, no solid fill). + * + * Three variants, differing by icon, copy, and supplementary sub-row: + * - First detected (CREATED): `shield` icon, "First detected in commit"; the + * shared row adds a path sub-row and (when present) a tool-version Label. + * - Appeared in branch (APPEARED_IN_BRANCH): `git-branch` icon, "Appeared in + * branch {ref}"; adds a workflow-run sub-row (this is the only detection + * event with `show_timeline_commit?` false — no commit card). + * - Reappeared in branch (REAPPEARED): `shield` icon, "Reappeared in branch + * {ref}"; demonstrates the optional "in configuration {category}" pill. + */ +export const EventDetected = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* First detected — ALERT_CREATED. Bare shield badge, bold body, a path + sub-row, and a right-aligned tool-version Label (Timeline.Actions). */} +
    +

    First detected in commit

    + + + + + + + First detected in commit + + + {/* Tool-version Label — `Primer::Beta::Label(scheme: :secondary)`, + right-aligned, title "Tool version 2.15.0". */} + + + + +
    + + {/* Appeared in branch — ALERT_APPEARED_IN_BRANCH. Bare git-branch badge, + bold "Appeared in branch {ref}", and a workflow-run sub-row (no commit + card — `show_timeline_commit?` is false for this event). */} +
    +

    Appeared in branch

    + + + + + + + Appeared in branch + + + +
    + + {/* Reappeared in branch — ALERT_REAPPEARED. Bare shield badge, bold + "Reappeared in branch {ref}", and the optional "in configuration + {category}" pill (rendered when the alert has more than one category). */} +
    +

    Reappeared in branch

    + + + + + + + Reappeared in branch + + + + +
    +
    +) From 15c3561b98c3ec7cdd1e0cd3cd1939d1dab9f604 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:12:34 -0700 Subject: [PATCH 2/3] Add remaining Code Scanning timeline event groups Add the Fixed/Config-deleted, Closed-by-user, Reopened, Dismissal-requested, and Dismissal-reviewed event groups, translated from the live ERB (CodeScanning::TimelineComponent). Extract UserActor, MonoPill, and NoteSubRow helpers shared across the user-actor groups. Completes the Code Scanning surface of github/primer#6663. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ....code-scanning.features.stories.module.css | 35 ++ ...imeline.code-scanning.features.stories.tsx | 380 +++++++++++++++++- 2 files changed, 409 insertions(+), 6 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css index 9b0a31cd34d..12cd5cf577c 100644 --- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.module.css @@ -109,3 +109,38 @@ font-family: var(--fontStack-monospace); color: var(--fgColor-muted); } + +/* Inline user-actor avatar — live `profile_link` wraps a `Primer::Beta::Avatar` + (CIRCLE) before the bold login. */ +.InlineAvatar { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Bold actor / reference link — live `profile_link(scheme: :primary, + font_weight: :bold, class: "Link--primary text-bold")`: semibold, default + foreground, accent on hover. The bold weight is also the non-color + differentiator that satisfies the axe `link-in-text-block` rule. */ +.LinkWithBoldStyle { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +.LinkWithBoldStyle:hover { + color: var(--fgColor-accent); +} + +/* Bold closure / resolution reason — live ERB wraps the reason in + `Primer::Beta::Text(font_weight: :bold, classes: "branch-name")`. */ +.Reason { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +/* Leading `note` octicon in a NoteSubRow. */ +.SubRowIcon { + margin-right: var(--base-size-4); + color: var(--fgColor-muted); + vertical-align: middle; +} diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx index ebb88306199..4756a8c35df 100644 --- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx @@ -3,7 +3,19 @@ import type React from 'react' import type {ComponentProps} from '../utils/types' import {FeatureFlags} from '../FeatureFlags' import Timeline from './Timeline' -import {GitBranchIcon, ShieldIcon} from '@primer/octicons-react' +import { + CheckIcon, + CommentIcon, + DotFillIcon, + GitBranchIcon, + NoteIcon, + ShieldCheckIcon, + ShieldIcon, + ShieldXIcon, + 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' @@ -113,6 +125,28 @@ import classes from './Timeline.code-scanning.features.stories.module.css' */ const Ref = ({name}: {name: string}) => {name} +const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' + +/** + * User actor — live `profile_link(user, scheme: :primary, font_weight: :bold, + * class: "Link--primary text-bold")` wrapping `Primer::Beta::Avatar` + + * `display_login`: a CIRCLE avatar + bold login profile link. Used by every + * USER event (Closed by user, Reopened, both Dismissal events). + * + * a11y: this IS a real link, so to satisfy the axe `link-in-text-block` rule the + * bold weight is the non-color differentiator (matching the live `text-bold` and + * the Dependabot / Issues precedent — bold in-text links pass; only non-bold + * ones need `inline`). + */ +const UserActor = ({login = 'monalisa', src = MONALISA_AVATAR}: {login?: string; src?: string}) => ( + <> + + + {login} + + +) + // Muted relative timestamp. The shared `timeline_item_component` renders a plain // `time_ago_in_words_js` (no link wrapper) — muted text only. const Time = ({date}: {date: string}) => ( @@ -121,17 +155,22 @@ const Time = ({date}: {date: string}) => ( ) +/** + * Subtle gray monospace pill — live `Primer::Beta::Truncate.new(bg: :subtle, + * font_family: :mono, font_size: 6, border_radius: 1)`. Used both inline mid-copy + * (the Config-deleted `{category}`) and after "in configuration" (`ConfigPill`). + */ +const MonoPill = ({children}: {children: React.ReactNode}) => {children} + /** * "in configuration {category}" inline pill — live `show_analysis_origin?` rows - * render `Primer::Beta::Truncate.new(bg: :subtle, font_family: :mono, - * font_size: 6, border_radius: 1)`: a subtle gray monospace rounded pill. Only - * shown when the alert has more than one analysis category - * (`has_more_than_one_category?`). + * render the `MonoPill` after the literal " in configuration". Only shown when + * the alert has more than one analysis category (`has_more_than_one_category?`). */ const ConfigPill = ({category}: {category: string}) => ( <> {' in configuration'} - {category} + {category} ) @@ -144,6 +183,19 @@ const ConfigPill = ({category}: {category: string}) => ( */ const SubRow = ({children}: {children: React.ReactNode}) =>
    {children}
    +/** + * Note sub-row — live `show_resolution_note?` / `show_reviewer_comment?` render a + * condensed row with a `note` octicon + the comment text below the event. Used + * by Closed-by-user / Dismissal-requested (`resolution_note`) and + * Dismissal-reviewed (`reviewer_comment`). + */ +const NoteSubRow = ({children}: {children: React.ReactNode}) => ( + + + {children} + +) + export default { title: 'Components/Timeline/Events/Code Scanning', component: Timeline, @@ -275,3 +327,319 @@ export const EventDetected = () => (
) + +/** + * The Fixed / Config-deleted event group — `CodeScanTimeline.eventFixed` + * (audit § 2). + * + * Source: the `ALERT_CLOSED_BECAME_FIXED` and `ALERT_CLOSED_BECAME_OUTDATED` + * cases. Both are SYSTEM events (no actor — bold body text only) and both share + * the SAME conditional badge: when the event's ref is the currently selected ref + * (`timeline_event.ref_name_bytes == @selected_ref`) the badge is + * `shield-check` on `done_emphasis` (SOLID purple → `Timeline.Badge + * variant="done"`); otherwise it is a plain `check` icon on the default badge + * (BARE gray). Both variants are shown below for each event to demonstrate the + * conditional. + * + * Config-deleted renders the analysis category as an inline subtle mono pill + * (`MonoPill`); when the category is empty the live ERB substitutes "API + * Upload". + */ +export const EventFixed = () => ( +
+ {/* Fixed — selected ref → SOLID purple shield-check */} +
+

Fixed in branch (current ref)

+ + + + + + + Fixed in branch + + + +
+ + {/* Fixed — non-selected ref → bare default check */} +
+

Fixed in branch (other ref)

+ + + + + + + Fixed in branch + + + +
+ + {/* Config deleted — selected ref → SOLID purple shield-check */} +
+

Configuration deleted (current ref)

+ + + + + + + Closed as java{' '} + configuration was deleted in branch + + + +
+ + {/* Config deleted — non-selected ref → bare default check */} +
+

Configuration deleted (other ref)

+ + + + + + + Closed as java{' '} + configuration was deleted in branch + + + +
+
+) + +/** + * The Closed-by-user event group — `CodeScanTimeline.eventClosedByUser` + * (audit § 3). + * + * Source: the `ALERT_CLOSED_BY_USER` case. Badge: `shield-x` on + * `danger_emphasis` (SOLID red → `Timeline.Badge variant="danger"`). This is a + * USER event — a circle avatar + bold login profile link. Copy: "closed this" + * plus, when `resolution != :NO_RESOLUTION`, " as {bold reason}". The reason set + * (`alert_closure_reasons`, downcased) is exactly three values: "false + * positive", "used in tests", "won't fix". When `resolution_note` is present the + * shared row appends a `note` sub-row (`show_resolution_note?`). + */ +export const EventClosedByUser = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Closed as false positive — with a resolution-note sub-row */} +
+

Closed as false positive

+ + + + + + + + {'closed this as '} + false positive + + +
+ + {/* Closed as used in tests */} +
+

Closed as used in tests

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

Closed as won't fix

+ + + + + + + + {'closed this as '} + won't fix + + +
+ + {/* Closed with no resolution — ERB omits the " as {reason}" clause when + `resolution == :NO_RESOLUTION`. */} +
+

Closed (no resolution)

+ + + + + + + + {'closed this '} + + + +
+
+) + +/** + * The Reopened event group — `CodeScanTimeline.eventReopened` (audit § 4). + * + * Source: the `ALERT_REOPENED_BY_USER` case. Badge: `dot-fill` on + * `success_emphasis` (SOLID green → `Timeline.Badge variant="success"`). USER + * event — circle avatar + bold login. Copy is a fixed "reopened this". Single + * variant. + */ +export const EventReopened = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > +
+

Reopened

+ + + + + + + + {'reopened this '} + + + +
+
+) + +/** + * The Dismissal-requested event group — `CodeScanTimeline.eventDismissalRequested` + * (audit § 5). + * + * Source: the `ALERT_DISMISSAL_REQUESTED` case. Badge: `comment` icon on the + * default badge (BARE gray — no `background:` passed). USER event — circle + * avatar + bold login. Copy: "requested to dismiss this as {reason}" where the + * reason is plain (NOT bold) `alert_closure_reason_description` text. A + * `requester_comment` renders as a `note` sub-row (`show_resolution_note?`). + * + * FEATURE-GATED: this whole event only renders when `delegated_dismissal_enabled?` + * is true for the repo, and the Review-request button is further gated on + * `timeline_event.show_dismissal_actions` (the live button is a + * `Primer::Alpha::Dialog` show-button, `scheme: :primary, size: :small`, label + * "Review request"). It is mapped here to the `Timeline.Actions` right slot. + */ +export const EventDismissalRequested = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > +
+

Requested to dismiss

+ + + + + + + + {'requested to dismiss this as false positive '} + + + + + + +
+
+) + +/** + * The Dismissal-reviewed event group — `CodeScanTimeline.eventDismissalReviewed` + * (audit § 6). + * + * Source: the `ALERT_DISMISSAL_REVIEWED` case. Badge icon comes from + * `exemption_evaluation_icon`: `check` when the request was approved, `x` when + * denied — both on the default badge (BARE gray). USER event — circle avatar + + * bold login. Copy comes from `dismissal_resolution_msg`: "approved dismissal" + * or "denied dismissal". A `reviewer_comment` renders as a `note` sub-row + * (`show_reviewer_comment?`). + * + * FEATURE-GATED: like the request event, this only renders when + * `delegated_dismissal_enabled?` is true for the repo. + */ +export const EventDismissalReviewed = () => ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Approved — check icon + reviewer-comment sub-row */} +
+

Approved dismissal

+ + + + + + + + {'approved dismissal '} + + + +
+ + {/* Denied — x icon */} +
+

Denied dismissal

+ + + + + + + + {'denied dismissal '} + + + +
+
+) From 9b8a258e2c0d8bb6e14d0feb30ed5c9e24510f02 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:02:08 -0700 Subject: [PATCH 3/3] Update Code Scanning story doc comments per review Correct three stale/inaccurate inline comments flagged in review: the file header no longer claims to be a Detected-only proof-of-pattern (all six groups ship), the accessibility note reflects that the user-actor groups now render in-text profile links, and the tool-version Label comment states the faithful 'Label:'-prefixed title from the live ERB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...imeline.code-scanning.features.stories.tsx | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx index 4756a8c35df..d1971966d80 100644 --- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx @@ -53,11 +53,10 @@ import classes from './Timeline.code-scanning.features.stories.module.css' * 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. * - * THIS FILE IS A PROOF-OF-PATTERN: it ships only the **Detected** event group - * (alert created / appeared / reappeared) plus the file scaffold + helpers. The - * remaining groups (Fixed / Config-deleted, Closed by user, Reopened, Dismissal - * requested, Dismissal reviewed) are deliberately deferred to a follow-up so the - * conventions below can be reviewed first. + * This file ships all six Code Scanning event groups: **Detected** (alert + * created / appeared / reappeared), **Fixed / Config-deleted**, **Closed by + * user**, **Reopened**, **Dismissal requested**, and **Dismissal reviewed**. + * Each renders as its own story export. * * AUTHORITATIVE EVENT LIST (live `timeline_component.html.erb` dispatch), for * planning the remaining groups: @@ -103,18 +102,21 @@ import classes from './Timeline.code-scanning.features.stories.module.css' * which render a CIRCLE 20px avatar + bold `display_login` profile link. The * Detected group below is system-only, so no actor and no in-text link appears. * - * ACCESSIBILITY NOTE: the Detected group renders no in-text `` in the main - * body (system events are bold text only). The only links are in the workflow - * sub-row, where the bold weight is the non-color differentiator required by the - * axe `link-in-text-block` rule. Any in-text link added when the USER-actor - * groups are built must likewise use `inline`/bold styling. + * ACCESSIBILITY NOTE: the SYSTEM events (Detected, Fixed, Config-deleted) render + * no in-text `` in the main body (bold text only); their only links are in + * the workflow sub-row. The USER events (Closed by user, Reopened, both Dismissal + * events) DO render an in-text profile `` via `UserActor` — it carries the + * bold weight as its non-color differentiator, which is what the axe + * `link-in-text-block` rule (WCAG 1.4.1) requires in high-contrast themes. Any + * further in-text link added here must likewise use bold or `inline` styling. * * RIGHT CONTROLS (`Timeline.Actions`): the shared row renders a right-aligned * tool-version `Primer::Beta::Label(scheme: :secondary)` whenever the event - * carries a `tool_version` (the title reads "Tool version X" or "Tool upgraded - * to X"). It is `margin-left: auto` inside the body in the ERB; we map it to the - * `Timeline.Actions` right-controls slot. The "First detected" variant below - * demonstrates it. + * carries a `tool_version` (the live title is + * `"Label: #{tool_version_prefix} #{tool_version}"`, e.g. "Label: Tool version + * 2.15.0" or "Label: Tool upgraded to X"). It is `margin-left: auto` inside the + * body in the ERB; we map it to the `Timeline.Actions` right-controls slot. The + * "First detected" variant below demonstrates it. */ /** @@ -267,7 +269,10 @@ export const EventDetected = () => ( {/* Tool-version Label — `Primer::Beta::Label(scheme: :secondary)`, - right-aligned, title "Tool version 2.15.0". */} + right-aligned. The live ERB sets the title to + `"Label: #{tool_version_prefix} #{tool_version}"`, so the faithful + title is "Label: Tool version 2.15.0" (the "Label:" prefix is part + of dotcom's tooltip text, kept verbatim). */}