Skip to content

[superseded by #977] feat(mermaid): WCAG 2.2 AA colour contrast validation and updated diagram standards#975

Closed
ashleyshaw wants to merge 2 commits into
developfrom
claude/relaxed-sagan-gl6f5y
Closed

[superseded by #977] feat(mermaid): WCAG 2.2 AA colour contrast validation and updated diagram standards#975
ashleyshaw wants to merge 2 commits into
developfrom
claude/relaxed-sagan-gl6f5y

Conversation

@ashleyshaw

@ashleyshaw ashleyshaw commented Jun 18, 2026

Copy link
Copy Markdown
Member

Linked issues

Closes #976

Changelog

Added

  • scripts/validation/validate-mermaid-colour-contrast.js: WCAG 2.2 AA colour contrast validator for all Mermaid style declarations across all .md files; detects missing explicit color alongside fill and calculates relative luminance contrast ratios per the WCAG 2.2 formula
  • .github/workflows/validate-mermaid-pr.yml: PR and feature-branch workflow that runs all three mermaid validators (syntax, accessibility, contrast) on changed .md files, posts a summary comment, and blocks merge on contrast failures
  • npm run validate:mermaid-contrast and npm run validate:mermaid scripts to run the contrast validator and all three validators in sequence

Changed

  • instructions/mermaid.instructions.md updated to v2.0: approved WCAG AA colour palette (7 semantic fill/color/stroke triples, all ≥ 4.5:1 in both light and dark mode), canonical emoji vocabulary, Phosphor icon limitation note, required accTitle/accDescr header block structure, and repo-wide update process

Checklist (Global DoD / PR)

  • All AC met and demonstrated
  • Tests added/updated (unit/E2E as appropriate)
  • Accessibility checklist completed (where relevant):
    • Semantic HTML and heading order verified
    • Keyboard navigation and visible focus states verified
    • ARIA used only where needed
    • Contrast and non-colour cues reviewed (WCAG 2.2 AA or higher)
  • Docs/readme/changelog updated (if user-facing)
  • Security checklist completed (where relevant):
    • Untrusted input validated and sanitised
    • Output escaped for its rendering context
    • Privileged actions enforce nonce and capability checks
    • No secrets/sensitive data introduced; OWASP risks reviewed
  • Code/design reviews approved
  • CI green; linked issues closed; release notes prepared (if shipping)

Summary

Adds a WCAG 2.2 AA colour contrast validator and automated PR workflow to catch the dark-mode contrast failures present in all existing Mermaid diagrams (36 errors on first run). Updates diagram standards to v2.0 with an approved colour palette, emoji vocabulary, and repo-wide update process.

Root cause: style X fill:#e1f5fe without explicit color — Mermaid inherits white text in dark mode, contrast ~1.1:1 against light pastel fills.

Follow-up: #976 tracks the repo-wide palette sweep to fix all 47 existing diagram files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LXjmeDonrDybfPZsH6Aa6w

…standards

Adds a new colour contrast validator, a PR-triggered workflow, and
comprehensive mermaid diagram standards to address dark-mode contrast
failures where fill-only style declarations render with white text
(~1.1:1 contrast ratio) rather than the intended dark text.

- scripts/validation/validate-mermaid-colour-contrast.js: new WCAG AA
  checker that detects fill declarations missing explicit color, calculates
  relative luminance and contrast ratio per the WCAG 2.2 formula, and
  produces a dated report under .github/reports/mermaid/
- .github/workflows/validate-mermaid-pr.yml: new workflow triggered on
  pull_request and feature branch pushes for any changed .md files;
  runs syntax, accessibility, and contrast checks; posts a summary
  comment on the PR and fails the build on contrast errors
- instructions/mermaid.instructions.md: updated to v2.0 with approved
  WCAG AA colour palette (fill+color+stroke triples, all ≥ 4.5:1),
  canonical emoji vocabulary, Phosphor icon limitation note, required
  accTitle/accDescr header block structure, and repo-wide update process
- package.json: adds validate:mermaid-contrast and validate:mermaid
  (runs all three validators in sequence)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXjmeDonrDybfPZsH6Aa6w
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a782b747-2b52-4483-a168-0c66800675ac

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/relaxed-sagan-gl6f5y

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

✅ Template check passed after update. Thanks for fixing the PR description.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🔍 Reviewer Summary for PR #975

CI Status:success
Files changed: 4
Risk Distribution: 1 critical, 1 high, 0 medium, 2 low

Recommendations

  • ⚠️ 1 critical-risk file(s) modified (workflows, secrets)
  • ⚠️ Security-sensitive files modified (review carefully)

lint-staged@17.0.7 requires Node >=22.22.1; the previous node-version: 20
caused npm ci to fail with EBADENGINE on every run of validate-mermaid-pr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXjmeDonrDybfPZsH6Aa6w

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive standards and validation tools for Mermaid diagrams, including updated guidelines in instructions/mermaid.instructions.md, new npm scripts in package.json, and a new WCAG 2.2 AA colour contrast validation script. Feedback on the validation script highlights a regex bug that could cause silent failures on non-standard hex lengths, potential false positives with hyphenated CSS properties, and misleading/duplicate reporting when text colors are missing. The reviewer also suggested calculating and reporting actual line numbers for better usability, and recommended documenting the repository-wide update process in /docs/MIGRATION.md to comply with repository rules.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

function parseColour(colour) {
if (!colour) return null;
const trimmed = colour.trim().toLowerCase();
if (/^#[0-9a-f]{3,6}$/.test(trimmed)) return trimmed;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The regular expression /^#[0-9a-f]{3,6}$/ matches hex color strings of length 3, 4, 5, or 6. However, 4-digit and 5-digit hex colors are not standard for Mermaid and will cause normaliseHex and relativeLuminance to produce NaN values (due to slicing out-of-bounds). This results in the contrast check silently passing because NaN < 4.5 evaluates to false. Restricting the match to exactly 3 or 6 hex digits prevents this issue.

Suggested change
if (/^#[0-9a-f]{3,6}$/.test(trimmed)) return trimmed;
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(trimmed)) return trimmed;

Comment on lines +221 to +227
const fillMatch = props.match(/\bfill\s*:\s*([^,;\s]+)/i);
const fill = fillMatch ? fillMatch[1].trim() : null;

// Extract text colour
const colorMatch = props.match(/\bcolor\s*:\s*([^,;\s]+)/i);
const color = colorMatch ? colorMatch[1].trim() : null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The word boundary \b matches hyphenated SVG/CSS properties like stop-color or stroke-color because - is a non-word character. This can lead to false positives where the validator incorrectly assumes an explicit text color is set. Using a negative lookbehind (?<!-) ensures that fill and color are not part of other hyphenated properties.

Suggested change
const fillMatch = props.match(/\bfill\s*:\s*([^,;\s]+)/i);
const fill = fillMatch ? fillMatch[1].trim() : null;
// Extract text colour
const colorMatch = props.match(/\bcolor\s*:\s*([^,;\s]+)/i);
const color = colorMatch ? colorMatch[1].trim() : null;
// Extract fill colour
const fillMatch = props.match(/(?<!-)\bfill\s*:\s*([^,;\s]+)/i);
const fill = fillMatch ? fillMatch[1].trim() : null;
// Extract text colour
const colorMatch = props.match(/(?<!-)\bcolor\s*:\s*([^,;\s]+)/i);
const color = colorMatch ? colorMatch[1].trim() : null;

Comment on lines +256 to +285
if (!color) {
// No explicit text colour — determine the theme default
const defaultTextHex =
MERMAID_THEME_TEXT_DEFAULTS[theme] ?? MERMAID_THEME_TEXT_DEFAULTS.default;
const ratio = contrastRatio(fillHex, defaultTextHex);

issues.push({
level: "warning",
message:
`Node "${nodeId}": fill ${fill} has no explicit color. ` +
`Against the "${theme}" theme default text (${defaultTextHex}) ` +
`the contrast ratio is ${ratio.toFixed(2)}:1 — ` +
(ratio >= WCAG_AA_NORMAL_TEXT
? `passes AA for the "${theme}" theme, but will FAIL in dark mode. Add color: explicitly.`
: `FAILS WCAG AA (${WCAG_AA_NORMAL_TEXT}:1 required). Add color: explicitly.`),
});

// Also check against dark-mode text (white) for the "missing color" case
const darkRatio = contrastRatio(fillHex, "#ffffff");
if (darkRatio < WCAG_AA_NORMAL_TEXT) {
issues.push({
level: "error",
message:
`Node "${nodeId}": fill ${fill} without explicit color FAILS in dark mode ` +
`(white text contrast: ${darkRatio.toFixed(2)}:1, minimum 4.5:1).`,
});
}

return issues;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When color is missing, the validator hardcodes the assumption that the failure will occur in dark mode, which is misleading for dark fills (which actually fail in light mode). Additionally, if the theme is already "dark", it reports both a warning and an error against white text, leading to duplicate findings. Refactoring this logic to evaluate both light and dark modes explicitly provides clearer, more accurate feedback and avoids duplicate reports.

  if (!color) {
    const lightRatio = contrastRatio(fillHex, "#333333");
    const darkRatio = contrastRatio(fillHex, "#ffffff");
    const failsLight = lightRatio < WCAG_AA_NORMAL_TEXT;
    const failsDark = darkRatio < WCAG_AA_NORMAL_TEXT;

    if (failsLight || failsDark) {
      let failMode = "";
      if (failsLight && failsDark) {
        failMode = "both light and dark modes";
      } else if (failsLight) {
        failMode = "light mode (dark text)";
      } else {
        failMode = "dark mode (white text)";
      }
      issues.push({
        level: "error",
        message: "Node \"" + nodeId + "\": fill " + fill + " without explicit color FAILS in " + failMode + " (light contrast: " + lightRatio.toFixed(2) + ":1, dark contrast: " + darkRatio.toFixed(2) + ":1). Add an explicit color: to guarantee contrast."
      });
    } else {
      issues.push({
        level: "warning",
        message: "Node \"" + nodeId + "\": fill " + fill + " has no explicit color. While it passes contrast in both light and dark modes, adding an explicit color: is highly recommended."
      });
    }
    return issues;
  }

Comment on lines +354 to +361
report.findings.push({
file: relPath,
diagramIndex: di + 1,
theme,
level: issue.level,
message: issue.message,
rawStyle: style.raw,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validator currently does not output or report the actual line number of the style declaration in the markdown file, making it harder for developers to locate and fix contrast issues in large files. Calculating the actual 1-based line number in the markdown file using diagram.startLine + style.line and including it in the findings report improves usability.

          const fileLine = diagram.startLine + style.line;
          report.findings.push({
            file: relPath,
            diagramIndex: di + 1,
            theme,
            level: issue.level,
            message: issue.message,
            rawStyle: style.raw,
            line: fileLine,
          });

Comment on lines +368 to +370
const icon = issue.level === "error" ? "❌" : "⚠️ ";
console.log(` ${icon} Diagram ${di + 1}: ${issue.message}`);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Including the actual line number of the style declaration in the console output makes it much easier for developers to locate and fix contrast issues.

          const icon = issue.level === "error" ? "❌" : "⚠️ ";
          const fileLine = diagram.startLine + style.line;
          console.log("   " + icon + " Diagram " + (di + 1) + " (line " + fileLine + "): " + issue.message);

Comment on lines +452 to +458
.map(
(f) =>
`### ${f.level.toUpperCase()}: \`${f.file}\` — Diagram #${f.diagramIndex}\n\n` +
`- **Theme**: ${f.theme}\n` +
`- **Style**: \`${f.rawStyle}\`\n` +
`- **Issue**: ${f.message}\n`,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Updating the markdown report generator to include the calculated line number ensures that the generated reports are highly actionable and precise.

Suggested change
.map(
(f) =>
`### ${f.level.toUpperCase()}: \`${f.file}\` — Diagram #${f.diagramIndex}\n\n` +
`- **Theme**: ${f.theme}\n` +
`- **Style**: \`${f.rawStyle}\`\n` +
`- **Issue**: ${f.message}\n`,
)
.map(
(f) =>
"### " + f.level.toUpperCase() + ": " + String.fromCharCode(96) + f.file + String.fromCharCode(96) + " — Diagram #" + f.diagramIndex + " (line " + f.line + ")\n\n" +
"- **Theme**: " + f.theme + "\n" +
"- **Style**: " + String.fromCharCode(96) + f.rawStyle + String.fromCharCode(96) + "\n" +
"- **Issue**: " + f.message + "\n",
)


## Repository-wide Update Process

When diagrams across the repository need to be updated (new palette, new structural requirements):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In accordance with the repository's general rules, repository-wide update and migration steps should also be documented in the central /docs/MIGRATION.md file. This ensures that contributors can easily follow migration rules across the repository.

References
  1. Document migration maps and notes in a central /docs/MIGRATION.md file to ensure contributors can follow migration rules mentioned in README files across the repository.

@ashleyshaw ashleyshaw changed the title feat(mermaid): WCAG 2.2 AA colour contrast validation and updated diagram standards [superseded by #977] feat(mermaid): WCAG 2.2 AA colour contrast validation and updated diagram standards Jun 18, 2026
@ashleyshaw ashleyshaw closed this Jun 18, 2026
@ashleyshaw ashleyshaw deleted the claude/relaxed-sagan-gl6f5y branch June 18, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: repository-wide Mermaid diagram colour palette sweep (WCAG AA follow-up)

2 participants