From 04f26fcbfb0a460a5bbb9382df008fe95073b90e Mon Sep 17 00:00:00 2001
From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com>
Date: Wed, 22 Jul 2026 11:36:49 -0700
Subject: [PATCH 1/2] Timeline: tag License Compliance event stories with
taxonomy data-* attributes
Spread the event data-* contract from the taxonomy module onto every
Timeline.Item in the License Compliance feature stories. This is the
proof-of-pattern pilot for Phase 3 taxonomy tagging (github/primer#6664).
Storybook-only, no consumer-facing change.
Copilot-Session: ac5d1407-e965-4994-b6d5-fd1b8ec0cdef
---
...ne.license-compliance.features.stories.tsx | 87 +++++++++++++------
1 file changed, 62 insertions(+), 25 deletions(-)
diff --git a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx
index 36d8dda8015..20c4acca04b 100644
--- a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx
+++ b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx
@@ -19,6 +19,13 @@ import {Button} from '../Button'
import Link from '../Link'
import Octicon from '../Octicon'
import {EventSubRow, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers'
+import {
+ actorTypeForLogin,
+ LICENSE_COMPLIANCE_SCOPE,
+ LICENSE_COMPLIANCE_TAXONOMY,
+ toEventDataAttributes,
+ type LicenseComplianceEventType,
+} from './taxonomy'
import classes from './Timeline.license-compliance.features.stories.module.css'
/**
@@ -67,10 +74,22 @@ import classes from './Timeline.license-compliance.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.
*
- * 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.
+ * TAXONOMY `data-*` CONTRACT (proof-of-pattern pilot): every `Timeline.Item`
+ * below now carries the event `data-*` attributes projected from the merged
+ * taxonomy module (`./taxonomy`, primer/react#8180) — the single source of truth
+ * for Timeline event categorization (github/primer#6664, docs github/primer#6888).
+ * Each row spreads the output of `toEventDataAttributes` via the local `lcAttrs`
+ * helper, which derives `category` / `visibility` FROM the catalog entry
+ * (`LICENSE_COMPLIANCE_TAXONOMY`) so the stories stay in sync with the catalog,
+ * and resolves `data-actor-type` at runtime from each row's rendered actor login
+ * (`actorTypeForLogin`). The contract per rendered `
`: `data-event-scope`,
+ * `data-event-type` (the UNSCOPED leaf), `data-event-category`,
+ * `data-event-visibility` (defaults `primary`), and `data-actor-type` (OMITTED
+ * when the row renders no actor, e.g. the synthetic `appeared_in_branch`).
+ * License Compliance is fully cataloged (all nine leaves), so nothing here is
+ * left untagged — there are no parked or shared events on this surface. This is
+ * the proof-of-pattern surface; the same approach fans out to the other four
+ * timeline surfaces next.
*
* PROOF-OF-PATTERN HISTORY: this file landed first as an `opened`-only
* scaffold to validate the template, then grew to the full nine-group set. All
@@ -140,6 +159,24 @@ const PolicyLink = ({href = '../../settings/security_analysis'}: {href?: string}
)
+/**
+ * Local projection of the taxonomy `data-*` contract for this surface. Given a
+ * License Compliance leaf `type` (and, when the row renders an actor, that
+ * actor's `login`), it returns the `data-*` attribute set to spread on the
+ * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so
+ * the stories track `LICENSE_COMPLIANCE_TAXONOMY`; `data-actor-type` is resolved
+ * at runtime from the login and omitted when no login is passed (actor-less
+ * rows). See github/primer#6664 and the taxonomy docs (github/primer#6888).
+ */
+const lcAttrs = (type: LicenseComplianceEventType, login?: string) =>
+ toEventDataAttributes({
+ scope: LICENSE_COMPLIANCE_SCOPE,
+ type,
+ category: LICENSE_COMPLIANCE_TAXONOMY[type].category,
+ visibility: LICENSE_COMPLIANCE_TAXONOMY[type].visibility,
+ actorType: login ? actorTypeForLogin(login) : undefined,
+ })
+
export default {
title: 'Components/Timeline/Events/License Compliance',
component: Timeline,
@@ -186,7 +223,7 @@ export const EventOpened = () => (
{/* Opened — license-compliance system bot, ShieldIcon on success (green) */}
-
+
@@ -222,7 +259,7 @@ export const EventAppearedInBranch = () => (
badge, with a BranchName pill. PR sub-row is dormant (see group doc). */}
-
+
@@ -262,7 +299,7 @@ export const EventReviewRequested = () => (
{/* Requested to close — no reason, no PR, no comment */}
-
+
@@ -278,7 +315,7 @@ export const EventReviewRequested = () => (
{/* Requested to close as {reason}, with a requester comment sub-row */}
-
+
@@ -296,7 +333,7 @@ export const EventReviewRequested = () => (
"Review request" button (latest request only) in Timeline.Actions. */}
-
+
@@ -330,7 +367,7 @@ export const EventReviewApproved = () => (
{/* Approved closure request */}
-
+
@@ -346,7 +383,7 @@ export const EventReviewApproved = () => (
{/* Approved closure request — with reviewer comment */}
-
+
@@ -374,7 +411,7 @@ export const EventReviewDenied = () => (
{/* Denied closure request */}
-
+
@@ -390,7 +427,7 @@ export const EventReviewDenied = () => (
{/* Denied closure request — with reviewer comment */}
-
+
@@ -422,7 +459,7 @@ export const EventReviewExpired = () => (
{/* Request to close expired — license-compliance system bot, automatic expiry */}
-
+
@@ -452,7 +489,7 @@ export const EventExceptionAdded = () => (
{/* Full shape — package + policy link + repo name */}
-
+
@@ -470,7 +507,7 @@ export const EventExceptionAdded = () => (
{/* Fallback shape — body missing package info */}
-
+
@@ -499,7 +536,7 @@ export const EventLicensesAdded = () => (
{/* Full shape — licenses list + policy link + repo name */}
-
+
@@ -517,7 +554,7 @@ export const EventLicensesAdded = () => (
{/* Fallback shape — body missing licenses array */}
-
+
@@ -550,7 +587,7 @@ export const EventClosed = () => (
{/* Closed as amendment — with a closing comment */}
-
+
@@ -567,7 +604,7 @@ export const EventClosed = () => (
{/* Closed as private package */}
-
+
@@ -583,7 +620,7 @@ export const EventClosed = () => (
{/* Closed as inaccurate license */}
-
+
@@ -599,7 +636,7 @@ export const EventClosed = () => (
{/* Closed as policy edited */}
-
+
@@ -615,7 +652,7 @@ export const EventClosed = () => (
{/* Closed as fixed */}
-
+
@@ -631,7 +668,7 @@ export const EventClosed = () => (
{/* Closed as outdated — resolution Outdated */}
-
+
@@ -647,7 +684,7 @@ export const EventClosed = () => (
{/* Closed this alert — default (no reason / resolution) */}
-
+
From 620253e5b56a8656f1cc6c530cd21cac94978483 Mon Sep 17 00:00:00 2001
From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com>
Date: Wed, 22 Jul 2026 15:06:40 -0700
Subject: [PATCH 2/2] Timeline: tag Code Scanning, Dependabot, Secret Scanning,
and Issues event stories with taxonomy data-* attributes
Extend the taxonomy data-* tagging from License Compliance to the
remaining four surfaces, so all five now carry the contract. Same
approach: each Timeline.Item spreads toEventDataAttributes(), deriving
category and visibility from that surface's catalog and resolving
data-actor-type from the rendered login. Parked and shared events, and
variants with no catalog leaf, are left untagged with inline comments.
Storybook-only; no visual change.
---
...imeline.code-scanning.features.stories.tsx | 61 ++++--
.../Timeline.dependabot.features.stories.tsx | 102 +++++++---
.../Timeline.issues.features.stories.tsx | 184 ++++++++++--------
...eline.secret-scanning.features.stories.tsx | 123 +++++++++---
4 files changed, 323 insertions(+), 147 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 9e03994be10..1edbc345fb2 100644
--- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx
+++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx
@@ -26,6 +26,7 @@ import {
UserActor,
VariantSection,
} from './internal/timelineStoryHelpers'
+import {CODE_SCANNING_TAXONOMY, actorTypeForLogin, toEventDataAttributes, type CodeScanningEventType} from './taxonomy'
import classes from './Timeline.code-scanning.features.stories.module.css'
/**
@@ -172,6 +173,26 @@ const ConfigPill = ({category}: {category: string}) => (
*/
const SubRow = ({children}: {children: React.ReactNode}) => {children}
+/**
+ * Per-row taxonomy `data-*` attributes (Phase 3 tagging, github/primer#6664,
+ * epic #6654). Projects a Code Scanning leaf event type through the shared
+ * `toEventDataAttributes` serializer from the taxonomy module (primer/react#8180),
+ * mirroring the License Compliance pilot (primer/react#8216). `category` and
+ * `visibility` are derived FROM `CODE_SCANNING_TAXONOMY` so the story never
+ * hand-maintains them. Pass the row's rendered `UserActor` login for USER events
+ * (resolves `data-actor-type` via `actorTypeForLogin`); omit it for SYSTEM rows
+ * so `data-actor-type` is left off entirely. Spread the result onto each
+ * `Timeline.Item`.
+ */
+const codeScanningAttrs = (type: CodeScanningEventType, login?: string) =>
+ toEventDataAttributes({
+ scope: 'code-scanning',
+ type,
+ category: CODE_SCANNING_TAXONOMY[type].category,
+ visibility: CODE_SCANNING_TAXONOMY[type].visibility,
+ actorType: login ? actorTypeForLogin(login) : undefined,
+ })
+
export default {
title: 'Components/Timeline/Events/Code Scanning',
component: Timeline,
@@ -219,7 +240,7 @@ export const EventDetected = () => (
sub-row, and a right-aligned tool-version Label (Timeline.Actions). */}
-
+
@@ -253,7 +274,7 @@ export const EventDetected = () => (
card — `show_timeline_commit?` is false for this event). */}
-
+
@@ -283,7 +304,7 @@ export const EventDetected = () => (
{category}" pill (rendered when the alert has more than one category). */}
-
+
@@ -314,13 +335,20 @@ export const EventDetected = () => (
* Config-deleted renders the analysis category as an inline subtle mono pill
* (`MonoPill`); when the category is empty the live ERB substitutes "API
* Upload".
+ *
+ * TAXONOMY (github/primer#6664): the two "Fixed in branch" rows are the
+ * ALERT_CLOSED_BECAME_FIXED wire event and carry `data-event-type="fixed"`. The
+ * two "Configuration deleted" rows are visually grouped under Fixed here, but the
+ * taxonomy catalog folds their ALERT_CLOSED_BECAME_OUTDATED wire event into
+ * `closed` (a SYSTEM closure), so those rows carry `data-event-type="closed"`.
+ * Both are system events with no actor, so neither emits `data-actor-type`.
*/
export const EventFixed = () => (
{/* Fixed — selected ref → SOLID purple shield-check */}
-
+
@@ -335,7 +363,7 @@ export const EventFixed = () => (
{/* Fixed — non-selected ref → bare default check */}
-
+
@@ -348,9 +376,12 @@ export const EventFixed = () => (
{/* Config deleted — selected ref → SOLID purple shield-check */}
+ {/* Config-deletion is the ALERT_CLOSED_BECAME_OUTDATED wire event, which the
+ taxonomy folds into `closed` (system closure), not `fixed` — so this row
+ carries data-event-type="closed" despite its visual "Fixed" grouping. */}
-
+
@@ -366,7 +397,7 @@ export const EventFixed = () => (
{/* Config deleted — non-selected ref → bare default check */}
-
+
@@ -398,7 +429,7 @@ export const EventClosedByUser = () => (
{/* Closed as false positive — with a resolution-note sub-row */}
-
+
@@ -415,7 +446,7 @@ export const EventClosedByUser = () => (
{/* Closed as used in tests */}
-
+
@@ -431,7 +462,7 @@ export const EventClosedByUser = () => (
{/* Closed as won't fix */}
-
+
@@ -448,7 +479,7 @@ export const EventClosedByUser = () => (
`resolution == :NO_RESOLUTION`. */}
-
+
@@ -475,7 +506,7 @@ export const EventReopened = () => (
-
+
@@ -510,7 +541,7 @@ export const EventDismissalRequested = () => (
-
+
@@ -550,7 +581,7 @@ export const EventDismissalReviewed = () => (
{/* Approved — check icon + reviewer-comment sub-row */}
-
+
@@ -567,7 +598,7 @@ export const EventDismissalReviewed = () => (
{/* Denied — x icon */}
-
+
diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx
index 7768c168dff..b756a162b32 100644
--- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx
+++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx
@@ -29,6 +29,7 @@ import {
UserActor,
VariantSection,
} from './internal/timelineStoryHelpers'
+import {actorTypeForLogin, DEPENDABOT_TAXONOMY, toEventDataAttributes, type DependabotEventType} from './taxonomy'
/**
* Dependabot alert Timeline event examples (Phase 2 of github/primer#6663).
@@ -56,10 +57,26 @@ import {
* base `Timeline` component's own stories, and any docs-site representation is a
* Phase 3 consideration via base-component story changes, out of scope here.
*
- * FUTURE FILTERING (taxonomy still open — github/primer#6663): category
- * `data-*` attributes (e.g. `data-event-category="opened"`) will attach to each
- * `Timeline.Item` below so stories can be filtered/grouped by event family. We
- * intentionally do NOT add them yet to avoid baking in a taxonomy.
+ * TAXONOMY `data-*` CONTRACT: every cataloged `Timeline.Item` below carries the
+ * event `data-*` attributes projected from the merged taxonomy module
+ * (`./taxonomy`, primer/react#8180) — the single source of truth for Timeline
+ * event categorization (github/primer#6664, docs github/primer#6888). Each row
+ * spreads the output of `toEventDataAttributes` via the local `dependabotAttrs`
+ * helper, which derives `category` / `visibility` FROM the catalog entry
+ * (`DEPENDABOT_TAXONOMY`) so the stories stay in sync with the catalog, and
+ * resolves `data-actor-type` at runtime from each row's rendered actor login
+ * (`actorTypeForLogin`): Dependabot-authored rows resolve to `bot`, user-driven
+ * rows to `user`. The contract per rendered ``: `data-event-scope`,
+ * `data-event-type` (the UNSCOPED leaf), `data-event-category`,
+ * `data-event-visibility` (defaults `primary`), and `data-actor-type`. This
+ * applies the same contract proven by the License Compliance pilot
+ * (primer/react#8216).
+ *
+ * SHARED / PARKED EVENTS (left untagged): the Assignment and Copilot-work groups
+ * are cross-surface SHARED events, deliberately kept OUT of the per-surface
+ * Dependabot catalog (github/primer#6888), so they intentionally carry NO
+ * `data-*` attributes. See the code comments above `EventAssignment` and
+ * `EventCopilotWork`.
*
* SLOT USAGE (Phase 1 slots — same convention as the Issues group):
* - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar.
@@ -120,6 +137,25 @@ const PushPill = ({sha}: {sha: string}) => (
)
+/**
+ * Local projection of the taxonomy `data-*` contract for this surface. Given a
+ * Dependabot leaf `type` (and, when the row renders an actor, that actor's
+ * `login`), it returns the `data-*` attribute set to spread on the
+ * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so
+ * the stories track `DEPENDABOT_TAXONOMY`; `data-actor-type` is resolved at
+ * runtime from the login (Dependabot-authored rows pass a `…[bot]` login so they
+ * resolve to `bot`; user-driven rows pass the rendered user login). See
+ * github/primer#6664 and the taxonomy docs (github/primer#6888).
+ */
+const dependabotAttrs = (type: DependabotEventType, login?: string) =>
+ toEventDataAttributes({
+ scope: 'dependabot',
+ type,
+ category: DEPENDABOT_TAXONOMY[type].category,
+ visibility: DEPENDABOT_TAXONOMY[type].visibility,
+ actorType: login ? actorTypeForLogin(login) : undefined,
+ })
+
export default {
title: 'Components/Timeline/Events/Dependabot',
component: Timeline,
@@ -160,7 +196,7 @@ export const EventOpened = () => (
{/* Opened — no source */}
-
+
@@ -176,7 +212,7 @@ export const EventOpened = () => (
{/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */}
-
+
@@ -192,7 +228,7 @@ export const EventOpened = () => (
{/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */}
-
+
@@ -225,7 +261,7 @@ export const EventFixed = () => (
{/* Fixed — no source */}
-
+
@@ -241,7 +277,7 @@ export const EventFixed = () => (
{/* FixedViaPR — bold `#123` pull-request link */}
-
+
@@ -257,7 +293,7 @@ export const EventFixed = () => (
{/* FixedViaPush — blue push-pill */}
-
+
@@ -290,7 +326,7 @@ export const EventDismissed = () => (
{/* Manual — risk is tolerable (with an optional dismissal note) */}
-
+
@@ -309,7 +345,7 @@ export const EventDismissed = () => (
{/* Manual — fix started */}
-
+
@@ -325,7 +361,7 @@ export const EventDismissed = () => (
{/* Manual — no bandwidth to fix this */}
-
+
@@ -341,7 +377,7 @@ export const EventDismissed = () => (
{/* Manual — vulnerable code is not actually used */}
-
+
@@ -357,7 +393,7 @@ export const EventDismissed = () => (
{/* Manual — inaccurate */}
-
+
@@ -373,7 +409,7 @@ export const EventDismissed = () => (
{/* Auto — rule-based, no source (with optional rule comment) */}
-
+
@@ -396,7 +432,7 @@ export const EventDismissed = () => (
{/* Auto — from a pull request */}
-
+
@@ -412,7 +448,7 @@ export const EventDismissed = () => (
{/* Auto — from a push */}
-
+
@@ -441,7 +477,7 @@ export const EventReopened = () => (
{/* Manual reopen — user actor */}
-
+
@@ -457,7 +493,7 @@ export const EventReopened = () => (
{/* Reintroduced — no source */}
-
+
@@ -473,7 +509,7 @@ export const EventReopened = () => (
{/* Reintroduced — from a pull request */}
-
+
@@ -489,7 +525,7 @@ export const EventReopened = () => (
{/* Reintroduced — from a push */}
-
+
@@ -505,7 +541,7 @@ export const EventReopened = () => (
{/* Auto-reopened — rule change (with optional rule comment) */}
-
+
@@ -560,7 +596,7 @@ export const EventDismissalRequest = () => (
place that right-aligned control in the `Timeline.Actions` slot. */}
-
+
@@ -584,7 +620,7 @@ export const EventDismissalRequest = () => (
{/* Dismissal approved — circle user actor, success/check badge */}
-
+
@@ -601,7 +637,7 @@ export const EventDismissalRequest = () => (
{/* Dismissal denied — circle user actor, danger/x badge */}
-
+
@@ -619,7 +655,7 @@ export const EventDismissalRequest = () => (
"x")` (no bg) → a bare default badge, not a danger/emphasis one. */}
-
+
@@ -651,6 +687,12 @@ export const EventDismissalRequest = () => (
* The five variants mirror the Secret scanning assignment story: self-assign,
* assign another, self-unassign, unassign another, and a combined assign +
* unassign example.
+ *
+ * SHARED / PARKED — NO `data-*` TAXONOMY: assignment is a cross-surface SHARED
+ * event, deliberately kept OUT of the per-surface Dependabot catalog
+ * (`DEPENDABOT_TAXONOMY` has no assignment leaf), per the taxonomy docs
+ * (github/primer#6888). It has no per-surface taxonomy leaf yet, so these rows
+ * intentionally carry NO `data-*` attributes.
*/
export const EventAssignment = () => (
@@ -758,6 +800,12 @@ export const EventAssignment = () => (
* button above.
* - Work finished (`with_badge(icon: "repo-push")`): default (gray) badge, no
* right control.
+ *
+ * SHARED / PARKED — NO `data-*` TAXONOMY: Copilot-agent work is a cross-surface
+ * SHARED event, deliberately kept OUT of the per-surface Dependabot catalog
+ * (`DEPENDABOT_TAXONOMY` has no copilot leaf), per the taxonomy docs
+ * (github/primer#6888). It has no per-surface taxonomy leaf yet, so these rows
+ * intentionally carry NO `data-*` attributes.
*/
export const EventCopilotWork = () => (
diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx
index 20324b0b6ff..82d1c27d627 100644
--- a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx
+++ b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx
@@ -40,6 +40,8 @@ import Octicon from '../Octicon'
import Token from '../Token'
import classes from './Timeline.issues.features.stories.module.css'
import {BoldLink, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers'
+import {actorTypeForLogin, ISSUE_TAXONOMY, toEventDataAttributes} from './taxonomy'
+import type {IssueEventType} from './taxonomy'
/**
* Issue Timeline event examples (Phase 2 of github/primer#6663).
@@ -104,6 +106,23 @@ export default {
],
} as Meta>
+/**
+ * Serialize the taxonomy `data-*` contract (github/primer#6664) for one Issues
+ * event row. `type` is an ISSUE_TAXONOMY leaf; `category` + `visibility` are read
+ * from the catalog, so metadata leaves (labels, assignees, milestones, project
+ * fields, issue types, rename) pick up `visibility: 'auditOnly'` automatically.
+ * Pass the row's rendered actor login so `data-actor-type` resolves (every Issues
+ * event carries an actor).
+ */
+const issueAttrs = (type: IssueEventType, login?: string) =>
+ toEventDataAttributes({
+ scope: 'issue',
+ type,
+ category: ISSUE_TAXONOMY[type].category,
+ visibility: ISSUE_TAXONOMY[type].visibility,
+ actorType: login ? actorTypeForLogin(login) : undefined,
+ })
+
/**
* The Closed event group — `IssueTimeline.eventClosed` (audit § 2).
*
@@ -118,7 +137,7 @@ export const EventClosed = () => (
{/* Closed as completed */}
-
+
@@ -139,7 +158,7 @@ export const EventClosed = () => (
(portable for docs copy-paste; matches production `TimelineRow`). */}
-
+
(
{/* Closed via pull request */}
-
+