# Cohesion

> Visual + structural design-cohesion audit. Detects every place a page drifts from the project's design system — different button styles, missing nav/footer, hard-coded colors that should be tokens, parallel CSS classes recreating canonical components, inline `<style>` blocks redefining variables. Cross-page consistency check across the whole site, not just changed code. Use when the site looks visually inconsistent across pages, or as part of a launch-ready audit pass.

- Skill: `surfrrosa/cohesion` (Agent Skill)
- Install (CLI): `npx skillmds@latest add surfrrosa/cohesion`
- Raw SKILL.md: https://api.skillmd.com/api/skills/surfrrosa/cohesion/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: Surfrrosa (https://skillmd.com/u/surfrrosa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/surfrrosa/cohesion

---


# Cohesion Audit

Find every place a page drifts from the project's design system. The system has a canonical layer (base templates + base stylesheet + shared components + design-system docs); this skill scans everything else against that layer and reports drift, ranked by severity.

This is **not** a code-quality audit. It's specifically about visual + structural consistency. Different from:
- Dead-code audits (dead code / duplication)
- UX coverage audits (missing-feature gaps)
- Reuse / quality audits on changed code
- Accessibility audits
- Security review

## Arguments

- No args: audit current working directory
- A project name or path (e.g., `myapp`)
- `--live`: also fetch live URLs and compare rendered output across pages
- `--fix`: auto-fix safe categories (rare; most fixes need design judgment — default off)

## Project Registry

| Alias | Path | Canonical layer |
|-------|------|-----------------|
| `myapp` | (add your projects here) | (describe your canonical files: base templates, base stylesheet, components, design system docs) |

**For React Native / Expo projects:** the cohesion model is different (StyleSheet objects, Theme provider, native components). Skip this skill or adapt it heavily — flag and stop.

## Process

### Step 0 — Identify the source of truth

Resolve the project. Read in order:
1. `CLAUDE.md` — any documented design conventions
2. The canonical base template(s) — typically a `base.html` (Jinja), `app/layout.tsx` (Next.js), `Layout.astro` (Astro), or `index.html` for static sites
3. The canonical stylesheet — typically `base.css`, `globals.css`, or a theme file
4. Any shared components / macros — `components/ui/`, `src/components/shared/`, `_macros/`, etc.
5. Design-system docs — `DESIGN_SYSTEM.md`, `BRAND_VOICE.md`, design contract files

From these, **extract the canonical inventory**:

| Token type | What to extract |
|---|---|
| Color tokens | All `--*` CSS variables defined in the canonical stylesheet's `:root` block (or Tailwind theme.colors, or equivalent) |
| Font tokens | `--font-display`, `--font-body`, etc. — and the actual font families they map to |
| Component classes | Every class defined in the canonical stylesheet that's meant to be reused: `.btn-primary`, `.btn-secondary`, `.card`, `.form-group`, `.empty-state`, etc. |
| Allowed base templates / layouts | Which template / layout names child pages are allowed to extend |
| Allowed `<link>` stylesheets | Which external stylesheets are sanctioned (typically just one font provider for the canonical families) |
| Page-shell elements | What every page is expected to have: nav, footer, main landmark, lang attr, etc. |

Write this inventory down (mentally or in a scratch buffer); the audit compares against it.

### Step 1 — Page-shape violations (RED)

Highest-severity drift. A page that doesn't extend the base shell shows up looking like a different site entirely.

For every page-level template / route (the ones a route handler actually renders), check:

#### 1a. Extends an allowed base

- Jinja: does it have `{% extends "base.html" %}` (or the project's canonical base)?
- Next.js: is it inside the App Router root layout, or does it define its own `<html>`/`<body>`?
- Astro: does it wrap content in `<Layout>` (or the project's canonical layout)?
- Static HTML: does it include a canonical shared header/footer pattern?

A page that doesn't extend anything renders without nav/footer/global styles.

#### 1b. Loads canonical stylesheet

- Does it inherit the canonical stylesheet from the base layout, OR does it load its own?
- Pages that load their own stylesheet bypass the design system.

#### 1c. Has the canonical wordmark + nav

- If the base has a canonical `<nav>`, does the page render with it?
- Pages with their own custom `<nav>` implementation = drift.

#### 1d. Has the footer (specifically: includes the partial, not its own copy)

- Is the canonical footer included? Or does the page roll its own footer markup?
- **Concrete detection:** for every page-level template, check both:
  - Does the file include the canonical footer (e.g., `{% include "_footer.html" %}` in Jinja, `<Footer />` in React, framework equivalent)? AND
  - Does the file contain a `<footer>` tag NOT inside that include?
- Either condition failing is RED. A page with its own inline `<footer>` is the most common drift in old/orphaned pages — and when the inline copy ages, it falls behind the canonical (missing newer links like Home/Blog/social, browser-default link styling, broken indentation copy-pasted from a stale snapshot).

#### 1e. Has the canonical background / hero layer

- If the base has a canonical background element (`.video-bg`, `.hero-bg`, etc.), does the page rely on it or override it?
- See Step 1.5 — overriding canonical background rules in page CSS is the load-bearing failure mode here.

#### 1f. Has the canonical viewport + lang

- `<html lang="en">`, viewport meta — usually inherited but worth checking pages with custom `<head>`.

**Red flag heuristic:** any page that defines its own `<html>`, `<head>`, `<body>`, or `<footer>` instead of inheriting from base is RED.

### Step 1.5 — Global selector overrides in page CSS (RED)

The class of bug standard cohesion checks miss: a page-specific stylesheet (or inline `<style>` block) that **redefines selectors which already exist in the canonical stylesheet**. The page extends the base correctly, includes the footer correctly, but its own CSS silently overrides global rules — producing visible drift like wrong z-index on the background, wrong link colors in the footer, layout glitches in the nav.

**The specific bug shape:**
- Canonical stylesheet defines `.video-bg { z-index: -1 }` (behind everything)
- Page stylesheet redefines `.video-bg { z-index: 0 }` (above default-z elements)
- Browser cascade: page CSS loads after canonical, so page rule wins
- Result: the global background renders ON TOP of UI elements that don't have explicit z-index, breaking the layered visual stack

**Detection (mechanical):**

For every project-level `.css` file (NOT the canonical one):
1. Extract every selector that has `display:`, `position:`, `z-index:`, or `font-family:` declarations
2. Cross-reference with the canonical stylesheet
3. **Any overlap is RED** — even if the override "looks the same," the duplication means future canonical changes don't propagate

For every inline `<style>` block in a template:
4. Run the same check on its rules
5. RED if any global selectors are touched

**Forbidden override list (extract from your canonical stylesheet):**

For each project, the audit must build this list from the canonical layer. Common categories to extract:
- Root element selectors: `body`, `html`
- Background / shell selectors: `.video-bg`, `.hero-bg`, etc.
- Navigation: the project's nav class + any nav sub-elements
- Footer: bare `footer` element selector + any footer-specific classes
- Cookie / consent banner selectors
- Skip-link selectors
- Main / landmark selectors: `main`, `#main-content`

Each hit outside the canonical stylesheet is a RED finding. The fix is one of:
- Delete the override (let canonical drive — most common)
- Rename the selector to something page-scoped (e.g., `.blueprint-video-overlay` instead of `.video-bg`)
- Move the rule into the canonical stylesheet if it should apply globally

### Step 1.6 — HTML well-formedness in templates (RED, mechanical)

The simplest-possible static check that catches a real class of catastrophic bugs: a page-level template has an unclosed `<script>` or `<style>` tag. The browser's HTML parser, while inside a `<script>` element, only exits on `</script>` — content type doesn't matter. So the parser stays in script-content mode through all subsequent `<head>` content (favicons, fonts, base stylesheet link, analytics — all swallowed as script content) until the next `</script>` it can find.

**Detection (mechanical, per template):**

```bash
# Per page-level template, count opens vs closes for tags that MUST balance.
# A mismatch is a RED finding — the page is broken HTML.
for tpl in src/templates/**/*.html; do
  for tag in script style; do
    opens=$(grep -cE "<$tag[[:space:]>]" "$tpl")
    closes=$(grep -c "</$tag>" "$tpl")
    if [ "$opens" != "$closes" ]; then
      echo "RED  $tpl: <$tag> $opens opens, $closes closes (mismatch)"
    fi
  done
done
```

Other tags worth balancing on page-level templates: `<head>`, `<body>`, `<html>`, `<main>`, `<form>`, `<table>`, `<div>` (last one is noisy — only flag mismatches > 5).

**Common false-positive shape to ignore:**
- Self-closing or void elements (`<meta>`, `<link>`, `<img>`, `<input>`, `<br>`, `<hr>`) don't need closes.
- `<script>` with `src=""` is self-contained but still needs `</script>` per HTML spec.
- Templating block boundaries inside a `<script>` tag — fine, the rendered output collapses correctly UNLESS the templating block itself never closes the open `<script>`.

**Auto-fix eligibility:** balance violations are NOT generally auto-fixable — the missing close could go in many places, and inserting it wrong creates a worse bug. Always file as a follow-up with the exact line number of the unclosed open. Human inspects + adds the closing tag.

The exception: if the unclosed tag is OBVIOUSLY at the end of a templating block and the missing close is one line, an auto-fix is safe.

### Step 1.7 — Shared UI-component class drift (RED)

The most insidious drift category — what we'll call **parallel-definition drift**:

- A canonical class like `.form-section-title` is defined in `base.css` with `color: var(--secondary)` (copper)
- The SAME class is redefined in several page-level CSS files, all with subtly different colors
- A user notices visible drift across forms: section headers reading as a different shade than field labels
- Fix requires updating all definitions in lockstep AND patching files that USED the class but didn't define it (relying on browser defaults)

This is distinct from Step 3's component drift:
- Step 3 catches: page invents `.checkout-btn` instead of using canonical `.btn-primary` (USAGE drift)
- Step 1.7 catches: page **redefines `.btn-primary` itself** with different properties (DEFINITION drift)

Definition drift is worse because each copy is a subtle variation of the canonical; updating the canonical doesn't propagate. The user perceives it as "100 different shades of white" or "buttons that almost-but-don't match." Standard cohesion checks miss it because each individual rule looks valid in isolation.

**Canonical UI-component class list (extract from your canonical stylesheet):**

For each project, build this from the actual stylesheet. Common patterns:
- Form scaffolding: `.form-section-title`, `.form-section`, `.form-group`, `.field-label`, `.field-error`, `.field-help`
- Buttons: `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-text`
- Cards: `.card`, `.card-*`
- States: `.empty-state`, `.error-state`
- Layout: `.skip-link`, `.video-bg`, `.sticky-nav`, `.nav-*`

For each class, run **three mechanical checks**:

**1.7a — Duplicate definitions (RED)**

```bash
# For each canonical class, count definitions across the project:
for cls in form-section-title field-label btn-primary btn-secondary card empty-state; do
  hits=$(rg -ln "^\\s*\\.${cls}\\s*\\{" src/)
  count=$(echo "$hits" | grep -c .)
  if [ "$count" -gt 1 ]; then
    echo "RED  .${cls} defined in $count files:"
    echo "$hits" | sed 's/^/  - /'
  fi
done
```

A canonical class should be defined ONCE, in your canonical stylesheet. Page-level redefinitions are RED, even if the values look identical — the duplication itself is the bug.

**1.7b — Property-value mismatch across definitions (RED)**

When a class IS defined in multiple places, compare property values:

```bash
# Pull every definition + its property block, diff against the canonical:
rg -A 15 "^\\s*\\.form-section-title\\s*\\{" src/
# → eyeball: do `color`, `font-family`, `font-size`, `padding`, `letter-spacing`
#   match across all hits? Mismatches are RED.
```

Especially flag: `color`, `font-family`, `font-size`, `font-weight`, `padding`, `letter-spacing`, `border`. These produce the most visible cross-page drift.

**1.7c — Used-but-undefined (RED)**

A class used in markup with NO matching CSS rule reachable on the page renders with browser defaults — silently broken until someone notices.

```bash
# For each canonical class, find templates that USE it:
for cls in form-section-title field-label btn-primary card; do
  rg -l "class=\"[^\"]*\\b${cls}\\b" src/templates/ | while read tpl; do
    # Check if the template loads a stylesheet that defines the class.
    # Simplest heuristic: does the canonical stylesheet load on this page
    # AND does it define the class?
    base_loads=$(grep -l "extends.*base.html\|/static/base.css" "$tpl")
    base_defines=$(grep -c "^\\s*\\.${cls}\\s*\\{" src/static/base.css)
    if [ -z "$base_loads" ] && [ "$base_defines" -gt 0 ]; then
      echo "RED  $tpl uses .${cls} but doesn't load base.css"
    fi
  done
done
```

**Auto-fix bundle (per Step 7 pattern):**
- **1.7a duplicates** with verbatim copy → bundle PR that deletes the duplicates, leaves only the canonical
- **1.7b property mismatch** on a single property (e.g., just `color`) → bundle PR that flips all definitions to the canonical value
- **1.7b property mismatch** on multiple properties → file as follow-up, needs design judgment
- **1.7c orphan usage** → file as follow-up; either add to canonical stylesheet or stop using

### Step 2 — Token discipline (YELLOW)

Find every place that hard-codes a value that should be a token. These look fine in isolation but produce subtle drift over time.

#### 2a. Hex color codes outside the canonical stylesheet

- Grep for `#[0-9a-fA-F]{3,6}` across all templates + static files.
- Filter out the canonical stylesheet itself + any acceptable surfaces (e.g., favicon manifest, social-card images).
- Every remaining hit is a candidate for tokenization. Cross-reference: does the hex match an existing token? If yes, it's a swap-in fix. If no, it's a new color the design system should know about.

#### 2b. `font-family:` declarations outside the canonical stylesheet

- Inline `style="font-family: ..."` and page-specific `<style>` blocks redefining font-family.

#### 2c. Inline style attributes that redefine tokens

- `style="color: #B87333"` should be `style="color: var(--accent)"` (or better, a class)
- `style="background: rgba(26, 20, 24, 0.6)"` should be a token

#### 2d. External font imports beyond the canonical set

- Drift here is silent and brand-breaking.

### Step 3 — Component drift (YELLOW)

Find every place that recreates a canonical component instead of using the macro/class.

#### 3a. Button-like elements not using `.btn-*` classes

- Every `<button class="...">` and `<a class="...">` whose visual is button-like
- Group by class. If the same visual exists under 5 different class names, that's drift to consolidate.
- Especially flag: page-scoped classes like `.checkout-btn`, `.sample-report-link`, `.primary-action` that recreate `.btn-primary` styling.

#### 3b. Card-like `<div>` clusters that should be the canonical card component

- Look for `<div class="*-card *-panel *-box">` patterns with manual border + padding + border-radius
- These should use the project's canonical card primitive.

#### 3c. Form inputs that bypass the canonical form scaffolding

- Any `<div class="form-group"><label><input></div>` markup that exactly mirrors what the canonical form macro renders should be migrated to the macro.

#### 3d. Status/empty/error states bypassing the canonical state component

- Custom "no results" markup, custom error banners — fold into the canonical component.

### Step 4 — Inline `<style>` block size (smell heuristic)

For each page template:
- Count lines inside `<style>` blocks (excluding comments and whitespace)
- A page with > ~50 lines of inline CSS is probably drifting from the system
- Rank pages by inline-CSS size; the worst offenders get top billing in the report

This isn't a violation per se — sometimes page-specific styling is correct. But it's a smell. Worth eyeballing the largest blocks for token drift, parallel components, or stuff that should be in the canonical stylesheet.

### Step 5 — Cross-page rendering compare (`--live` flag)

When `--live` is passed, fetch every public route via `WebFetch` and parse the rendered HTML.

For each page, check the rendered output (not source) for:
- `<main>` landmark present
- Canonical `<nav>` present
- `<footer>` present
- Canonical background element present
- Canonical font preconnects + font provider link present
- No additional `<link rel="stylesheet">` beyond the canonical set
- `<html lang="...">` present

Build a comparison matrix:

| Page | nav | footer | bg | base.css | extra stylesheets |
|---|---|---|---|---|---|
| `/` | yes | PASS | yes | PASS | (none) |
| `/blog` | yes | PASS | yes | PASS | (none) |
| `/checkout` | yes | no (custom) | yes | PASS | own.css |
| ... | | | | | |

Pages with any "no" are RED. Inconsistencies across the matrix are the visible drift.

### Step 6 — Ranking

Compose findings into a single prioritized report:

```
## Cohesion Audit — [project]

### RED RED — Page-shape violations
1. **[file]** — extends base but overrides the content block with full custom layout including its own nav and footer. Drift visible to users.
2. **[file]** — does not extend any base; renders with bespoke `<head>`/`<body>`. Carries its own @font-face block + footer.

### YELLOW YELLOW — Component drift
1. `.checkout-btn` recreates `.btn-primary` (used in [N] places). Migrate or alias.
2. `.sample-report-link` recreates `.btn-text`. Migrate.
3. [N] inline `style="color: #B87333"` should be `var(--accent)`.

### YELLOW YELLOW — Token discipline
1. [N] hex colors outside canonical stylesheet ([list top 5 with file:line]).
2. [N] `font-family:` declarations outside canonical stylesheet.

### ORANGE SMELL — Inline `<style>` block size
1. `tools/blueprint.html` — [N] lines of inline CSS (largest)
2. `tools/time-travel.html` — [N] lines
3. `pages/index.html` — [N] lines (may be acceptable; landing pages tend to have hero-specific styling)

### Summary
- Top priority: [the worst offender]
- Next: [the next 2-3 priorities]
- Estimated cleanup effort: [rough size]
```

Each RED finding gets a follow-up issue or note (or one umbrella issue with sub-findings) per project conventions.

## Fix Mode (`--fix` flag) and auto-fix bundle

When `--fix` is set, bundle the auto-fixable findings into a single PR.

**Safe to auto-fix (`auto_fix: true`):**
- Replace inline `<footer>` block with the canonical footer include when the inline copy is a stale partial of the canonical footer
- Delete page-CSS rules that override global selectors when the override doesn't add new behavior. If the override DOES change behavior, downgrade to `auto_fix: false` (needs design review).
- Replace inline `style="color: #B87333"` with `style="color: var(--accent)"` when the hex unambiguously matches a known token
- Add `rel="stylesheet"` reference to the canonical stylesheet on a page that doesn't load it (only if the page already extends a base — bare-`<head>` rewrites are NOT auto-fixable)
- Remove dead CSS rules (e.g., orphan selectors with no markup using them)

**Requires confirmation (`auto_fix: false`):**
- Migrating a page from custom layout to extending the canonical base (changes routing assumptions, may break tests)
- Replacing a parallel button class with `.btn-primary` (may need CSS specificity adjustments + visual review)
- Removing an inline `<style>` block in favor of canonical equivalents (extraction first, then cleanup as separate PR)
- Renaming a global-selector override to a page-scoped class (touches markup + CSS together)

**Never auto-fix:**
- Hex colors that don't match a known token (could be intentional brand exception or a new token to add)
- Font-family changes (could break ascenders/descenders/em sizing)
- Removing custom layouts (usually represents real design judgment that needs review)
- Page-CSS that overrides a global selector if the override is non-trivial (e.g., adds keyframes, redefines a media query block) — flag and file as follow-up, don't touch

**Workflow:** partition findings by `auto_fix`, build branch + commit + push + open PR with the auto-fixable set, file follow-ups for the rest. Cap at 5 fixes per PR; if more, split by category (footer-includes, override-deletions, hex-substitutions, etc.).

## Rules

- **Source of truth is the canonical layer.** Don't invent a "what should be" from thin air; read the base layout, base stylesheet, components, and design docs to compute the inventory FIRST. Then audit against THAT.
- **Severity by impact.** A page without the global nav is RED — users see a different site. A hex color that should be a token is YELLOW — invisible drift today, brand-breaking when the brand color changes tomorrow.
- **Per-token-substitution fixes are easy; layout migrations are hard.** Default scope is detection + reporting. Layout migrations need a separate ticket per page.
- **Watch for grandfathered violations.** Existing cohesion lints often have an explicit grandfathered list. Read it; don't re-flag what's already known. Do flag if items have been on the list too long.
- **Be specific.** "tools/blueprint.html drifts from the design system" is useless. "tools/blueprint.html declares its own @font-face for Cormorant Garamond at line 47, has a 480-line inline `<style>` block, uses `.checkout-btn` instead of `.btn-primary`, and includes a custom `<footer>` instead of the canonical footer include" is useful.

## Skill Run Logging

If you keep a skill-run log, append an entry after each run:

Format: `| YYYY-MM-DD | /cohesion | target (details) | one-line result |`

