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}) =>
`). 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
+
+ {/* Path sub-row — live `show_path?` (CREATED / REAPPEARED): the dir
+ prefix (plain) + bold `{file_name}:{start_line} on branch` + bold
+ ref. */}
+
+ {'src/components/'}
+ Button.tsx:42 on branch
+
+
+
+ {/* 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
+
+ {/* Workflow-run sub-row — live `show_path?` is false here, so the
+ ERB renders the workflow branch: a conclusion octicon + bold run
+ link + a mono commit-SHA link. */}
+
+
+
+ CodeQL #128:
+ {' '}
+ Commit
+
+ adfc29a
+
+
+
+
+
+
+
+ {/* 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 = () => (
+
+)
+
+/**
+ * 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
+ Verified this pattern only appears in generated fixtures.
+
+
+
+
+
+ {/* 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 '}
+
+ This finding is a test-only helper, safe to dismiss.
+
+
+
+
+
+
+
+
+)
+
+/**
+ * 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 = () => (
+
+
+
+
+
+
+
+
+ {'approved dismissal '}
+
+ Agreed — this rule does not apply to test fixtures.
+
+
+
+
+
+ {/* 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). */}