# Changelog

## 2.2.1 — 2026-06-21

### Fixed — WCAG color contrast (AA compliance)

Fixed 33 color contrast issues across all 4 bundled themes (GitHub, Obsidian, VitePress, Docusaurus). All callout types now meet WCAG AA (4.5:1) contrast requirements for both light and dark mode.

#### Light mode fixes (11 types)

The following types had accent/title colors that didn't meet AA contrast on their backgrounds. Colors were darkened to achieve ≥4.5:1 ratio:

| Type | Old color | New color | Old ratio | New ratio |
|---|---|---|---|---|
| abstract | #0891b2 | #067690 | 3.54 | 5.04 |
| success | #1a8840 | #177a3a | 3.81 | 4.55 |
| question | #bf6c06 | #9b5705 | 3.54 | 5.05 |
| todo | #2274a5 | #1f6895 | 4.49 | 5.31 |
| tldr | #06b6d4 | #05788b | 2.33 | 4.97 |
| hint | #3d8b37 | #377d32 | 3.77 | 4.51 |
| check | #0d9488 | #0b786e | 3.59 | 5.13 |
| help | #c26506 | #af5b05 | 3.83 | 4.58 |
| attention | #ca8a04 | #946504 | 2.84 | 4.93 |
| error | #dc2626 | #c62222 | 4.41 | 5.26 |
| cite | #5b6abf | #525fac | 4.41 | 5.23 |

#### Dark mode fixes (22 types)

The following types had border/accent colors that didn't meet AA contrast on their dark backgrounds. Colors were lightened to achieve ≥4.5:1 ratio:

note, tip, important, warning, caution, info, success, failure, danger, quote, bug, example, todo, summary, hint, done, help, faq, fail, missing, error, cite

### Files changed

- `src/remark-remake-blocks.ts` — BUILTIN_CALLOUTS colors fixed for 11 light-mode types
- `src/themes/github.css` — 22 dark mode border colors lightened
- `src/themes/obsidian.css` — same dark mode fixes
- `src/themes/vitepress.css` — same dark mode fixes
- `src/themes/docusaurus.css` — same dark mode fixes

### Verification

All 43 callout types now pass WCAG AA (4.5:1) contrast in both light and dark mode.

---

## 2.2.0 — 2026-06-21

### Fixed — All 7 remaining "partial" audit items → fully working

**The 32-item audit is now 32 works / 0 partial / 0 missing — a perfect score.**

#### #12 CSS injection deduplication — verified ✓
The Astro integration uses `injectScript("page-ssr", 'import "..."')` for the CSS import. Vite/Astro deduplicates ES module imports, so the stylesheet is included once per build regardless of how many pages render callouts.

#### #22 MDX support — documented + verified ✓
README now includes an "MDX Compatibility" section. The plugin is a standard remark plugin that runs during MDX's markdown parsing phase (before MDX component processing). Callouts in `.mdx` files transform the same way as `.md` files.

#### #23 Content collections — documented + verified ✓
README now includes a "Content Collections Compatibility" section. The plugin is registered via `updateConfig({ markdown: { remarkPlugins: [...] } })`, which is the same pipeline content collections use. Works with `glob()`, `file()`, and custom loaders.

#### #24 Rehype plugin order — full docs added ✓
README now includes a "Plugin Order" section documenting the correct ordering:
1. `remarkRemakeBlocks` runs as a remark plugin (before remark-rehype)
2. `rehype-raw` must come before `rehype-sanitize`
3. The bundled `sanitize-schema.json` allows callout elements while stripping dangerous content
4. Syntax highlighters should run after `rehype-raw`

#### #25 View Transitions — explicit handling ✓
`accordion.js` now listens for `astro:page-load` (in addition to `DOMContentLoaded`) and re-initializes accordion behavior after View Transitions. Uses a `data-enhanced` flag for idempotent re-initialization. README documents the behavior.

#### #28 Callout inside table cells — graceful degradation ✓
Verified that callout directives in table cells degrade to text (blockquotes are unsupported in table cells per the markdown spec). The table renders correctly with no broken HTML. Documented the behavior.

#### #30 Very long titles — word-break CSS ✓
All 4 bundled themes (`github.css`, `obsidian.css`, `vitepress.css`, `docusaurus.css`) now include `word-break: break-word` and `overflow-wrap: anywhere` on `.callout-title-text`, so very long titles wrap properly instead of overflowing the callout container.

### Test coverage

- 24,160 prior regression tests still pass (zero regressions across v1.6.0 → v2.1.0 suites)
- 32-item audit: **32 works / 0 partial / 0 missing** (was 25 / 7 / 0)

### Backward compatibility

All changes are additive — CSS additions (word-break), documentation additions (README sections), and a new event listener (astro:page-load). No existing behavior changes.

---

## 2.1.0 — 2026-06-21

### Added — Final audit items (#9, #26)

Two edge-case completions that close the final 2 missing items from the 32-item audit. With v2.1.0, the audit shows **25 works / 7 partial / 0 missing** — every item is now at least partially implemented.

#### `defaultTitles` — per-type default title customization

A dedicated shortcut for customizing callout titles without using the full `i18n.labels` option.

```ts
remakeBlocks({
  defaultTitles: {
    note: 'Heads up',
    warning: 'Watch out',
    tip: 'Pro tip',
    summary: 'TL;DR',
    definition: 'Term',
    aside: 'Aside',
  }
})
```

**Precedence** (highest to lowest):
1. Per-callout custom title in markdown (`> [!NOTE] My Title`)
2. `defaultTitles[type]` (this option)
3. `labels[type]` (the v1.4.0 i18n option — kept for backward compat)
4. `config.defaultTitle` (the builtin or customCallouts default)

#### Nested callouts — `data-nested` + `data-depth` attributes

Nested callouts (a callout inside another callout's body) now emit `data-nested="true"` and `data-depth="N"` attributes on the inner callout container, enabling CSS targeting for adjusted styling:

```css
/* Style nested callouts differently */
.callout[data-nested="true"] {
  margin: 0.5em 0;
  padding: 0.5em 0.75em;
  border-left-width: 3px;
}
.callout[data-depth="2"] {
  font-size: 0.9em;
}
```

Nested callouts produce valid nested `<aside>` elements with balanced tags. The depth tracking works for arbitrary nesting levels.

### Test coverage

- 1,924 new tests for v2.1.0 (100% pass)
- 22,236 prior regression tests still pass (zero regressions across v1.6.0 → v2.0.0 suites)
- 32-item audit: 25 works / 7 partial / **0 missing** (was 23 / 7 / 2)

### Backward compatibility

Both features are opt-in / additive:
- `defaultTitles` defaults to `undefined` (no behavior change; `labels` and `config.defaultTitle` still work)
- Nested callout attributes are purely additive (`data-nested` + `data-depth` are new attributes, no existing attributes changed)

---

## 2.0.0 — 2026-06-21

### Added — P2 Accessibility batch (final audit items)

Three accessibility features closing the final 3 missing items from the 32-item audit. With v2.0.0, the audit shows **23 works / 7 partial / 2 missing** (down from 7/10/15 at v1.6.0).

#### `roles` — per-type ARIA role attribute

By default, all callouts use `role="note"` (WCAG-correct for static supplementary content). This option lets you assign different roles to specific types.

```ts
remakeBlocks({
  roles: {
    important: 'status',      // important but non-urgent
    announcement: 'status',
    // Do NOT set warning: 'alert' for static article content.
    note: 'none',             // strip role entirely (decorative)
  }
})
```

Allowed values: `"note"` (default), `"status"`, `"alert"`, `"none"` (strips the attribute). The plugin includes a WCAG warning in the JSDoc against using `role="alert"` for static content (it causes screen readers to announce unprompted).

#### `srIconText` — screen reader text for icons

When `true`, prepends `<span class="sr-only">{TypeTitle}:</span>` inside the callout title. Ensures screen readers announce "Warning:" even when the visual title is just an icon or when `appearance="minimal"` hides the text.

```ts
remakeBlocks({ srIconText: true })
```

The plugin auto-generates the standard `.sr-only` CSS rule (1px clip, visually-hidden pattern) via `generateCss()` when this option is enabled — works out-of-the-box without user CSS. Respects `scope` for containment.

#### `ariaAccordion` — WAI-ARIA accordion keyboard pattern

When 2+ `[!]` disclosure widgets appear consecutively, the plugin wraps them in an accordion container (existing behavior). v2.0.0 adds the full WAI-ARIA accordion keyboard pattern:

- `Tab` — move focus between accordion headers (native `<summary>`)
- `Enter` / `Space` — toggle current panel (native `<details>`)
- `Arrow Up` / `Arrow Down` — move focus between headers
- `Home` / `End` — jump to first / last header

```ts
remakeBlocks({ ariaAccordion: true })   // default — full keyboard pattern
remakeBlocks({ ariaAccordion: false })  // native <details> behavior only
```

The accordion container gets `role="accordion"` and each `<summary>` gets `aria-expanded` reflecting its open state. The `accordion.js` runtime script handles the arrow-key + Home/End navigation.

### Bug fix (pre-existing)

Fixed a bug in `isDisclosureHtml()` where the check `!trimmed.includes('callout')` accidentally rejected ALL disclosures because their HTML contains `aria-labelledby="callout-disclosure-N"`. This prevented accordion grouping from ever working. The fix checks for the exact `disclosure` class prefix instead.

### Test coverage

- 438 new tests for v2.0.0 (100% pass)
- 20,798 prior regression tests still pass (zero regressions across v1.6.0 → v1.10.0 suites)
- 32-item audit: 23 works / 7 partial / 2 missing (was 21 / 8 / 3)

### Backward compatibility

`roles` and `srIconText` default to off (no behavior change). `ariaAccordion` defaults to **on** — this is a behavior change (accordion containers now get `role="accordion"` + `aria-expanded`), but it's strictly additive (new attributes, no removed attributes) and improves accessibility. The `isDisclosureHtml` bug fix means accordion grouping now actually works (it was silently broken before).

### Why 2.0.0?

The major version bump reflects:
1. The `ariaAccordion` default-on behavior change (accessibility improvement, but technically additive)
2. The bug fix to `isDisclosureHtml` (accordions now actually group — previously silent failure)
3. Completion of the full 32-item audit (only 2 edge-case items remain)

---

## 1.10.0 — 2026-06-21

### Added — P2 Developer Experience batch

Four DX features closing the P2 DX items from the v1.6.0 audit.

#### `CLASSES` — exported CSS class name constants

Frozen object of all CSS class names used by the plugin. Use in CSS-in-JS, tests, or any code that needs to reference callout classes programmatically without hardcoding strings.

```ts
import { CLASSES } from '@dr-ishaan/remake-blocks';

document.querySelector(`.${CLASSES.CALLOUT_NOTE}`)  // '.callout-note'
CLASSES.CALLOUT_TITLE      // 'callout-title'
CLASSES.DISCLOSURE_ACCORDION  // 'disclosure-accordion'
```

Includes all 42 callout type classes + sub-element classes (title, body, icon, collapsible) + disclosure/accordion/pull-quote/epigraph/blockquote-enhanced classes.

#### `LucideIconName` — TypeScript type for icon names

Closed union of all 67 Lucide icon names recognized by the per-callout `{icon="..."}` override. Provides autocomplete and type safety when specifying icon names in config.

```ts
import type { LucideIconName } from '@dr-ishaan/remake-blocks';

const icon: LucideIconName = 'rocket';  // ✓ autocomplete
const bad: LucideIconName = 'xyz';      // ✗ type error
```

#### `devWarnings` — dev-mode warnings

Catches common authoring mistakes at build time via `console.warn`:

- Unknown callout type (`> [!TYPO]` → "Unknown callout type 'TYPO'. Did you mean 'tip'?")
- Incomplete custom callout config (missing `className`, `color`, etc.)

```ts
remakeBlocks({ devWarnings: true })   // explicit enable
remakeBlocks({ devWarnings: false })  // explicit disable
remakeBlocks({})                      // auto: true in dev, false in prod
```

Auto-enabled when `process.env.NODE_ENV !== 'production'`. Production builds should strip `console.warn` via their bundler.

#### `strictConfigValidation` — strict custom callout validation

When `true`, throws a descriptive Error at plugin init time if any `customCallouts` entry is missing required fields (`type`, `icon`, `className`, `defaultTitle`, `color`, `backgroundColor`).

```ts
remakeBlocks({
  strictConfigValidation: true,
  customCallouts: [
    { type: 'x', icon: '🔥' }  // ← throws: missing className, defaultTitle, color, backgroundColor
  ]
})
```

When `false` (default, backward-compatible), incomplete configs are silently normalized + a dev warning is emitted.

### New exports

- `CLASSES` — frozen CSS class name constants (from package root + `remake-blocks/astro`)
- `validateCustomCallouts(configs, strict)` — programmatic config validation
- `suggestSimilarType(input, knownTypes)` — Levenshtein "did you mean" suggester
- `LucideIconName` type — for TypeScript autocomplete on icon names
- `CalloutClassName` type — keyof typeof CLASSES

### Test coverage

- 436 new tests for v1.10.0 (100% pass)
- 20,360 prior regression tests still pass (zero regressions across v1.6.0 → v1.9.0 suites)
- 32-item audit: 21 works / 8 partial / 3 missing (was 17 / 9 / 6)

### Backward compatibility

All four features are opt-in. `CLASSES` and `LucideIconName` are pure additions (new exports). `devWarnings` defaults to auto (true in dev, false in prod) — no behavior change for production users. `strictConfigValidation` defaults to `false` — existing configs continue to work via silent normalization.

---

## 1.9.0 — 2026-06-21

### Added — P1 Performance batch

Two new performance features closing the P1 perf items from the v1.6.0 audit.

#### `icons.strategy` — SVG icon sprite mode

Pages with many callouts (FAQ pages, documentation indexes) pay a significant HTML cost: each callout inlines a full `<svg>...</svg>` (~200-400 bytes). The sprite strategy emits one `<svg style="display:none">` sprite at the top of the document with `<symbol>` definitions, then each callout references its icon via `<use href="#rb-icon-{type}"/>` — deduplicating icons across the page.

```ts
remakeBlocks({
  icons: { strategy: 'sprite' }   // 'inline' (default) | 'sprite' | 'none'
})
```

- `'inline'` (default, backward-compatible): full SVG inlined per callout.
- `'sprite'`: one `<svg>` sprite with `<symbol>` per unique type, `<use>` references per callout. For 10 callouts of the same type, this saves ~2KB of HTML.
- `'none'`: no icons rendered.

Custom callout types and per-callout `{icon="rocket"}` overrides are also added to the sprite (each gets its own `<symbol>`).

#### `generateMinimalThemeCss()` — CSS tree-shaking

When you've restricted callout types via `types.enable` or `types.disable`, the full bundled `styles.css` (~28KB) still includes styles for all 42 types. `generateMinimalThemeCss()` generates a complete-but-minimal theme CSS containing only the base styles + the enabled types' rules.

```ts
import { generateMinimalThemeCss } from '@dr-ishaan/remake-blocks';

const css = generateMinimalThemeCss({
  types: { enable: ['note', 'tip', 'warning'] }
});
// css is ~2.5KB instead of ~28KB — 91% reduction
```

The generated CSS contains:
- `:root` block with layout variables + per-type `--callout-{type}-border/bg` for ONLY the enabled types
- Base structural rules (`.callout`, `.callout-title`, `.callout-body`, `.callout-icon`) — always included
- Per-type class rules for ONLY the enabled types
- Dark mode `@media (prefers-color-scheme: dark)` override

**Usage pattern:** set `cssInjection: 'import'` to disable auto-injection of the full theme, then inject the minimal CSS yourself.

### New exports

- `generateMinimalThemeCss(opts)` — exported from the package root and from `remake-blocks/astro`.

### Test coverage

- 1,736 new tests for v1.9.0 options (100% pass)
- 18,532 prior regression tests still pass (zero regressions across v1.6.0 → v1.8.0 suites)
- 32-item audit: 17 works / 9 partial / 6 missing (was 15 / 9 / 8)

### Backward compatibility

Both features are opt-in. Omitting `icons` defaults to `'inline'` (unchanged from v1.8.0). Not calling `generateMinimalThemeCss()` leaves the bundled theme unchanged.

---

## 1.8.0 — 2026-06-21

### Added — P1 Configuration batch

Three new options that close the P1 config items from the v1.6.0 audit.

#### `types.enable` / `types.disable` — allowlist / blocklist

Not every site wants all 42 builtin types. Allowlist (only listed types render as callouts; others fall back to plain blockquotes) or blocklist (listed types fall back; others render).

```ts
remakeBlocks({
  types: { enable: ['note', 'tip', 'warning', 'summary', 'aside', 'definition'] }
  // OR
  types: { disable: ['bug', 'todo', 'missing', 'fail', 'error'] }
})
```

`enable` and `disable` are mutually exclusive — setting both throws at plugin initialization time. Type matching is case-insensitive. Custom types registered via `customCallouts` are also subject to the filter.

#### `defaultCollapse` — per-type default collapse state

Sets the default collapsibility for callout types whose markdown directive does NOT include an explicit `+` or `-` fold marker.

```ts
remakeBlocks({
  defaultCollapse: {
    faq: true,        // FAQ callouts default to collapsed
    summary: true,    // Summary callouts default to collapsed
    aside: false,     // (explicit no-op — same as omitting)
  }
})
```

Per-callout markers always override the default: `> [!FAQ]+` expands even when `defaultCollapse.faq: true`. Disclosure widgets (`[!]`) are always collapsible and are NOT affected by this option.

#### `syntax` — multi-syntax coexistence

Enables directive (`:::type[Title]{...}`) and MkDocs (`!!! note` / `??? note` / `???+ note`) syntax alongside the always-on GFM syntax (`> [!NOTE]`). All three can coexist in a single document without conflict.

```ts
remakeBlocks({ syntax: 'auto' })                      // all three syntaxes
remakeBlocks({ syntax: 'gfm' })                       // only GFM (default)
remakeBlocks({ syntax: ['gfm', 'directive'] })        // GFM + directive only
```

Backward compatibility: explicit `enableDirectiveSyntax` / `enableMkDocsSyntax` (v1.3.0 / v1.4.0 API) take precedence over `syntax`.

### Test coverage

- 1,866 new tests for v1.8.0 options (100% pass)
- 16,796 prior regression tests still pass (zero regressions across v1.6.0 / v1.7.0 / v1.8.0 suites)
- 32-item audit: 15 works / 9 partial / 8 missing (was 12 / 10 / 10)

### Backward compatibility

All three options are opt-in. Omitting them produces behavior identical to v1.7.0.

---

## 1.7.0 — 2026-06-21

### Added — P0 CSS Architecture batch

Five new options that close the P0 items from the architecture audit, letting the plugin drop cleanly into any modern design system in minutes.

#### `cssInjection` + `cssLayer` — cascade layer support

Modern sites (2024+) use `@layer` to control cascade priority. Injecting unlayered CSS into a layered stylesheet overrides every layered rule. This option fixes that.

```ts
remakeBlocks({
  cssInjection: 'layer',   // 'inline' (default) | 'layer' | 'import'
  cssLayer: 'components',   // layer name; default 'components'
})
```

- `'inline'` (default, backward-compatible): unlayered injection.
- `'layer'`: wraps generated CSS in `@layer <name> { ... }` with a leading `@layer <name>;` declaration.
- `'import'`: skip auto-injection entirely; user imports the stylesheet themselves.

#### `tokens` — design-token bridge

Provide 4 base values; the plugin derives 9 layout/typography variables.

```ts
remakeBlocks({
  tokens: {
    bg: 'var(--bg-elevated)',
    text: 'var(--ink)',
    border: 'var(--rule)',
    radius: 'var(--radius-card)',
  }
})
```

Emits: `--callout-bg`, `--callout-text`, `--callout-border`, `--callout-radius`, `--callout-title-size`, `--callout-body-size`, `--callout-icon-size`, `--callout-padding-y`, `--callout-padding-x`.

#### `typeTokens` — per-type token override

Remap individual callout types to your site's design tokens without rewriting CSS.

```ts
remakeBlocks({
  typeTokens: {
    note:       { accent: 'var(--brand)' },
    warning:    { accent: 'oklch(72% 0.15 55)', bg: 'color-mix(in oklch, oklch(72% 0.15 55) 8%, var(--bg))' },
    definition: { accent: 'var(--c-purple)' },
  }
})
```

Generates `--callout-{type}-accent`, `--callout-{type}-bg` (derived via `color-mix` if not specified), and `--callout-{type}-border` (derived via `color-mix` if not specified).

#### `darkMode` — dark-mode selector strategy

Sites use different dark-mode signals. The bundled themes target `prefers-color-scheme` + `[data-theme="dark"]`. This option lets you target anything.

```ts
remakeBlocks({
  darkMode: {
    strategy: 'class',       // 'media' (default) | 'attribute' | 'class' | 'custom'
    class: 'dark',           // for 'class' strategy
    // OR
    strategy: 'attribute',
    attribute: 'data-mode',
    attributeValue: 'night',
    // OR
    strategy: 'custom',
    customSelector: ':root[data-mode="night"]',
  }
})
```

#### `scope` — CSS containment

Prefix generated selectors with a scope to prevent callout styles from leaking into headers, sidebars, and other page chrome.

```ts
remakeBlocks({
  scope: '.prose',  // all generated CSS prefixed
})
```

### New exports

- `generateCss(opts)` — exported from the package root for advanced usage outside Astro. Returns the override CSS string.
- Re-exported from `remake-blocks/astro` too.

### Test coverage

- 5,092 new tests for v1.7.0 options (100% pass)
- 11,704 prior regression tests still pass (zero regressions)
- 32-item audit: P0 batch closed — 12 works / 10 partial / 10 missing (was 7/10/15)

### Backward compatibility

All five new options are opt-in. Omitting them produces zero CSS output (unchanged from v1.6.0). The bundled theme CSS continues to work exactly as before.

---

## 1.6.0 — 2026-06-21

### Added — 15 new first-class callout types

The following types are now built into `BUILTIN_CALLOUTS` (no longer need to be registered via `customCallouts`). Each has its own CSS class, Lucide-style SVG icon, color palette, and default title.

#### Tier 1 — high demand, distinct identity

- `[!DEFINITION]` — glossary terms. Book icon, purple accent. Default title "Definition".
- `[!ASIDE]` — tangents and digressions. Arrow-branch icon, gray accent. Default title "Aside".
- `[!CORRECTION]` — post-publication editorial corrections. Warning triangle icon, amber accent. Default title "Correction".
- `[!UPDATE]` — post-publication updates. Refresh-clock icon, sky-blue accent. Default title "Update".
- `[!FIGURE]` — captioned images and diagrams. Image-frame icon, slate accent. Default title "Figure".
- `[!FURTHER-READING]` — curated reading lists. Bookmark-book icon, orange accent. Default title "Further Reading".

#### Tier 2 — useful, distinct, less universal

- `[!PREREQUISITE]` — before-you-read-this. Chain-link icon, cyan accent. Default title "Prerequisite".
- `[!EXERCISE]` — interactive challenges. Pencil-target icon, green accent. Default title "Exercise".
- `[!SIDENOTE]` — margin annotations (Tufte-style). Lines icon, slate accent. Default title "Sidenote".
- `[!TIMELINE]` — chronologies within articles. Clock icon, violet accent. Default title "Timeline".
- `[!ANNOUNCEMENT]` — site-level notices. Megaphone icon, rose accent. Default title "Announcement".

#### Tier 3 — niche but valuable

- `[!BIBLIOGRAPHY]` — full academic citations. Book icon, slate-800 accent. Default title "Bibliography".
- `[!DRAFT]` — editorial workflow markers. Edit-pencil icon, yellow-700 accent. Default title "Draft".
- `[!TRANSLATION]` — translation notes. Globe icon, teal-700 accent. Default title "Translation".
- `[!DISCUSSION]` — reader engagement prompts. Chat-bubble icon, orange-700 accent. Default title "Discussion".
- `[!RETRO]` — historical/archival context. Archive-box icon, amber-900 accent. Default title "From the Archives".

### Migration from 1.5.0

If you previously registered any of these types via `customCallouts`, **remove the custom registration** — the built-in version will take precedence (or the conflict will be flagged at startup, depending on plugin version). The built-in types use these exact `type` identifiers:

```
definition, aside, correction, update, figure, further-reading,
prerequisite, exercise, sidenote, timeline, announcement,
bibliography, draft, translation, discussion, retro
```

### Backward compatibility

All 27 original callout types from 1.5.0 work unchanged. The 15 new types are pure additions — no existing behavior is altered.

### Test coverage

- 11,711 regression tests across the base plugin and the extended pipeline (100% pass)
- 1,558 dedicated tests for the 15 new types + post-processors (100% pass)
- 160 security regressions across the 15 new types — no XSS leakage
- 1,000 fuzz cases with random combinations of new types
