# mdx-formatter > AST-based markdown and MDX formatter --- # Architecture Overview > Source: /pj/mdx-formatter/docs/architecture # Architecture Overview mdx-formatter uses a **hybrid formatter** approach: it parses markdown/MDX into an AST for structural analysis, then applies targeted line-based operations to the original source text. This design preserves formatting details that AST round-trip serialization would destroy. ## Why Not AST Round-Trip? Most formatters follow a parse → transform → serialize pipeline. The problem with this approach for markdown/MDX is that serialization (AST back to text) is lossy: - Inline formatting choices (e.g., `*italic*` vs `_italic_`) may be normalized - Whitespace inside code blocks, template literals, and JSX expressions can be altered - Japanese text line breaks and spacing get mangled - Docusaurus admonitions (`:::note`) aren't part of the standard AST By keeping the original source lines and only modifying specific locations, mdx-formatter avoids these problems entirely. ## The Hybrid Approach The formatter works in three phases: ``` Phase 1: Parse Phase 2: Analyze Phase 3: Apply ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Source text │ │ Walk AST │ │ Sort operations │ │ ↓ │ │ ↓ │ │ (reverse order) │ │ markdown-rs │───>│ Collect line- │───>│ ↓ │ │ parser │ │ based operations │ │ Apply to source │ │ ↓ │ │ (insert, replace,│ │ lines │ │ mdast AST │ │ indent, fix) │ │ ↓ │ └─────────────────┘ └──────────────────┘ │ Normalize empty │ │ lines │ └─────────────────┘ ``` The key insight: the AST is used **only for analysis** (finding positions, understanding structure), never for output generation. All modifications happen on the original source lines. ## The Convergence Loop Some formatting operations can create new formatting issues. For example, inserting an empty line after a heading might create three consecutive empty lines that need to be collapsed. The formatter handles this with a convergence loop: ``` Input → format_once() → Output₁ format_once() → Output₂ format_once() → Output₃ (max) ``` The loop runs `format_once()` up to **3 iterations**, stopping early when the output stabilizes (i.e., `format(x) === x`). This guarantees idempotency: formatting an already-formatted file produces identical output. ## Formatting Operations Instead of mutating the AST or directly editing strings, the formatter collects a list of **operations** — structured instructions for modifying specific lines: | Operation | Description | | --- | --- | | `insertLine` | Insert a new line at a given position | | `replaceLines` | Replace a range of lines with new content | | `indentLine` | Add indentation to a specific line | | `fixListIndent` | Correct list item indentation | | `replaceHtmlBlock` | Replace an HTML block with properly indented output | Operation names use camelCase in TypeScript (`insertLine`, `replaceLines`) and PascalCase in the Rust port (`InsertLine`, `ReplaceLines`) — both implement identical semantics. Operations are collected from all rules, then applied in reverse line order (bottom-up) so that earlier line numbers remain valid as modifications are made. ### Conflict Resolution When multiple operations target overlapping line ranges, the formatter resolves conflicts: 1. **Overlapping replacements**: If a child JSX element and its parent both produce `ReplaceLines` operations, only the wider (parent) replacement is kept 2. **Operations inside replaced ranges**: Any `insertLine`, `indentLine`, or `fixListIndent` that falls inside an already-replaced range is dropped 3. **Deduplication**: Operations with identical type and line range are deduplicated via a string key ## The 10 Formatting Rules Each rule is independently toggleable via `.mdx-formatter.json` configuration: | # | Rule | Default | Description | | --- | --- | --- | --- | | 1 | `addEmptyLineBetweenElements` | on | Add single empty line between markdown elements | | 2 | `formatMultiLineJsx` | on | Format multi-line JSX with proper indentation | | 3 | `formatHtmlBlocksInMdx` | on | Format HTML blocks with proper indentation | | 4 | `expandSingleLineJsx` | off | Expand single-line JSX with multiple props | | 5 | `indentJsxContent` | off | Indent content inside JSX container components | | 6 | `addEmptyLinesInBlockJsx` | on | Add empty lines after opening/before closing tags | | 7 | `formatYamlFrontmatter` | on | Format YAML frontmatter | | 8 | `preserveAdmonitions` | on | Preserve Docusaurus `:::` syntax | | 9 | `errorHandling` | off | Throw on parse errors instead of returning original | | 10 | `autoDetectIndent` | off | Auto-detect indentation style from file content | Rules are applied in order during the operation collection phase. After all operations are collected, conflict resolution and deduplication happen before the operations are applied to the source lines. ## No Plugin System Needed The TypeScript formatter originally used 10 remark plugins to work around AST round-trip issues (`preserve-jsx.ts`, `preserve-image-alt.ts`, `fix-autolink-output.ts`, etc.). The hybrid approach — which uses the AST only for analysis, never for output — eliminates the need for these plugins. The Rust implementation validates that 9 of 10 plugins are unnecessary (see [Rust Rewrite: Plugin Validation](/docs/architecture/rust-rewrite#plugin-validation)). The formatting logic lives in the Rust engine (`crates/mdx-formatter-core/src/formatter.rs`). The TypeScript code in `src/` is a thin wrapper that loads the native module. ## Key Source Files | File | Role | | --- | --- | | `crates/mdx-formatter-core/src/formatter.rs` | Core Rust formatter: convergence loop, all formatting rules | | `crates/mdx-formatter-core/src/parser.rs` | markdown-rs AST parsing (MDX, GFM, frontmatter) | | `crates/mdx-formatter-core/src/html_formatter.rs` | HTML block indentation formatter | | `crates/mdx-formatter-core/src/config.rs` | Config file loading (3-layer merge) | | `src/index.ts` | Public npm API (`format()`, `formatFile()`, `checkFile()`) | | `src/rust-formatter.ts` | Loads the Rust napi module | | `src/settings.ts` | Default settings for all 10 rules | | `src/cli.ts` | CLI entry point | | `src/load-config.ts` | Loads `.mdx-formatter.json` and merges with defaults | --- # Changelog > Source: /pj/mdx-formatter/docs/changelog Release history for @takazudo/mdx-formatter. --- # Formatting Rules > Source: /pj/mdx-formatter/docs/formatting mdx-formatter uses a hybrid approach: it parses your markdown/MDX into an AST to understand structure, then applies targeted line-based operations to the original source text. This preserves formatting that pure AST round-tripping would destroy. This section describes how each type of content is formatted. ## How It Works The `format()` function runs up to 3 iterations until output stabilizes. Most files converge in a single pass. If parsing fails, the original content is returned unchanged. --- # Options > Source: /pj/mdx-formatter/docs/options Every option can be set via [config file](/docs/overview/configuration) or programmatic API. Each rule has an `enabled` flag that can be toggled independently. ## Full Configuration Example ```json { "exclude": ["generated/**"], "addEmptyLineBetweenElements": { "enabled": true }, "formatMultiLineJsx": { "enabled": true, "indentSize": 2, "ignoreComponents": ["CodeBlock"], "preserveTemplateLiteralIndent": true }, "formatHtmlBlocksInMdx": { "enabled": true, "formatterConfig": { "parser": "html", "tabWidth": 2, "useTabs": false } }, "expandSingleLineJsx": { "enabled": false, "propsThreshold": 2 }, "indentJsxContent": { "enabled": false, "indentSize": 2, "containerComponents": [] }, "addEmptyLinesInBlockJsx": { "enabled": true, "blockComponents": ["Outro", "InfoBox"] }, "formatYamlFrontmatter": { "enabled": true, "indent": 2, "lineWidth": 100, "quotingType": "\"", "forceQuotes": false, "noCompatMode": true, "fixUnsafeValues": true }, "preserveAdmonitions": { "enabled": true }, "autoDetectIndent": { "enabled": false, "fallbackIndentSize": 2, "fallbackIndentType": "space", "minConfidence": 0.7 }, "errorHandling": { "throwOnError": false } } ``` --- # Overview > Source: /pj/mdx-formatter/docs/overview AST-based markdown and MDX formatter with Japanese text support. Powered by a Rust engine (via napi-rs) that parses markdown/MDX into an AST, then applies targeted line-based operations to the original source text. ## Features - **Rust-powered** - Native Rust engine via napi-rs, 3-7x faster than pure JS - **Hybrid formatter** - Parses AST for analysis, applies edits to original source lines (no lossy round-trip) - **MDX support** - Full support for MDX syntax including JSX components - **Japanese text formatting** - Special handling for Japanese punctuation and URLs - **Docusaurus support** - Preserves Docusaurus admonitions (:::note, :::tip, etc.) - **HTML block formatting** - Proper indentation for HTML blocks (dl, table, ul, div, etc.) - **GFM features** - Tables, strikethrough, task lists - **Frontmatter preservation** - YAML frontmatter support - **WASM support** - Browser-compatible via `@takazudo/mdx-formatter-wasm` - **CLI and API** - Use as command-line tool or import as library - **Configurable** - 10 independently toggleable rules via config file or API --- # Playground > Source: /pj/mdx-formatter/docs/playground Try the formatter directly in your browser. Paste any markdown or MDX content in the input area and click **Format** to see the result. --- # Rust Engine > Source: /pj/mdx-formatter/docs/architecture/rust-rewrite # Rust Engine mdx-formatter's formatting engine is written in Rust. It uses the hybrid approach (AST analysis + line operations) and is the sole engine — the TypeScript code in `src/` is a thin wrapper that loads the native module via napi-rs. ## Why Rust? Three main motivations: ### Performance Processing hundreds of markdown files in a large documentation project is CPU-bound work. Rust's compiled native code is significantly faster than Node.js for this type of string-heavy, AST-walking computation. This matters most for CI pipelines and editor integrations where formatting runs on every save. ### Standalone Binary A Rust binary can be compiled for any platform without requiring a Node.js runtime. This opens the door to: - Embedding in desktop apps via Tauri - Running as a pre-commit hook without Node.js - Distribution as a single binary download ### npm Distribution via napi-rs Using napi-rs, the Rust formatter compiles to a native Node.js addon (`.node` file) that can be called directly from JavaScript. This gives the best of both worlds: native speed with a familiar npm install experience. ## Technology Choices ### markdown-rs for Parsing The Rust implementation uses [markdown-rs](https://github.com/wooorm/markdown-rs) (the `markdown` crate) by Titus Wormer — the same author who created the unified/remark ecosystem that the TypeScript version depends on. This means: - The parser outputs **mdast** (the same AST format as remark) - MDX, GFM, and frontmatter extensions are all supported - The AST structure is close enough to the TypeScript version that formatting rules can be ported directly When MDX parsing fails, the parser falls back to basic markdown parsing with GFM extensions, and finally to plain CommonMark — ensuring the formatter never crashes on malformed input. ### napi-rs for Node.js Bindings napi-rs compiles Rust code into platform-specific `.node` binaries that Node.js can load as native addons. The binding layer in `crates/mdx-formatter-napi/src/lib.rs` is minimal: ```rust #[napi] pub fn format(content: String, settings_json: Option) -> napi::Result { let settings = if let Some(json) = settings_json { let partial: serde_json::Value = serde_json::from_str(&json)?; FormatterSettings::from_partial_json(&partial) } else { FormatterSettings::default() }; Ok(mdx_formatter_core::format(&content, &settings)) } ``` Settings are passed as a JSON string and deserialized on the Rust side, keeping the JavaScript/Rust boundary simple. ## Crate Structure ``` crates/ ├── mdx-formatter-core/ ← Core Rust library │ ├── src/ │ │ ├── lib.rs ← Public exports │ │ ├── formatter.rs ← Hybrid formatter (convergence loop + all rules) │ │ ├── html_formatter.rs ← HTML block indentation formatter │ │ ├── config.rs ← Config file loading (3-layer merge) │ │ ├── parser.rs ← markdown-rs integration (mdast parsing) │ │ └── types.rs ← Settings, operations, type definitions │ └── tests/ │ ├── cross_platform.rs ← 165 cross-platform validation tests │ ├── plugin_validation.rs ← 42 plugin validation tests │ └── spacing_recursion.rs ← 11 spacing recursion tests │ ├── mdx-formatter-cli/ ← Standalone CLI binary │ └── src/ │ └── main.rs ← clap-based CLI (--write, --check, --config) │ ├── mdx-formatter-napi/ ← napi-rs Node.js bindings │ ├── src/ │ │ └── lib.rs ← format() function exposed to JavaScript │ └── build.rs ← napi-rs build configuration │ └── mdx-formatter-wasm/ ← WASM bindings for browser use ├── src/ │ └── lib.rs ← format() and format_with_defaults() for WASM └── Cargo.toml ``` The core library (`mdx-formatter-core`) has no Node.js dependency and can be used from any Rust project. The napi bridge (`mdx-formatter-napi`) is a thin wrapper that adds the JavaScript interface. ## Implemented Formatting Rules All formatting rules from the TypeScript version are implemented in Rust: | Rule | Status | Notes | | --- | --- | --- | | Heading/JSX spacing | Done | Works at all AST depths (blockquotes, JSX containers) | | Block-level spacing | Done | Paragraph ↔ heading, list, code block transitions | | List indentation | Done | Normalizes nesting to 2-space indent | | JSX multi-line formatting | Done | Attribute indentation, standalone `/>` fix | | Block JSX empty lines | Done | Opening/closing tag spacing for configured components | | JSX content indentation | Done | For configured container components | | YAML frontmatter formatting | Done | Parse, reformat, unsafe value quoting | | HTML block formatting | Done | Minimal indentation formatter (replaces Prettier) | | Settings deserialization | Done | All 10 fields via serde with camelCase JSON | ### Plugin Validation 9 of 10 TypeScript plugins are **not needed** in Rust. The hybrid approach preserves original text (no AST round-tripping), eliminating the bugs that the TS plugins work around: - `preserve-jsx` — JSX never mangled - `preserve-image-alt` — Colons in alt text preserved - `fix-autolink-output` — No angle brackets added to URLs - `preprocess-japanese` / `japanese-text` — No backslash insertion or punctuation escaping - `fix-formatting-issues` — No bold spacing or entity issues - `docusaurus-admonitions` — `:::` syntax preserved as-is - `normalize-lists` / `html-definition-list` — Content preserved as-is ## Infrastructure | Feature | Status | Notes | | --- | --- | --- | | Config file loading | Done | `.mdx-formatter.json`, `package.json` key, 3-layer merge | | CLI binary | Done | `mdx-formatter-cli` crate with `--write`, `--check`, `--config`, glob patterns | | napi-rs Node.js bindings | Done | Sole engine for the npm package | | napi-rs CI pipeline | Done | Cross-platform binary generation (macOS/Linux/Windows), published to npm | | Browser/WASM | Done | wasm-pack, web + bundler targets, used by doc site playground | ## Test Coverage | Suite | Count | Description | | --- | --- | --- | | Rust cargo tests | 342 | Unit + cross-platform + plugin validation + spacing + HTML + config | | TS passthrough | 85/85 | Rust matches TS behavior for all formatting | | Rust-specific TS | 29/29 | Tests via napi bridge | | Existing TS tests | 207/207 | All TypeScript formatting tests | ## npm Distribution Plan For production distribution, napi-rs publishes platform-specific binaries as optional npm dependencies: ``` @takazudo/mdx-formatter ← main package @takazudo/mdx-formatter-darwin-arm64 ← macOS Apple Silicon @takazudo/mdx-formatter-darwin-x64 ← macOS Intel @takazudo/mdx-formatter-linux-x64-gnu ← Linux @takazudo/mdx-formatter-win32-x64-msvc ← Windows ``` When a user runs `npm install @takazudo/mdx-formatter`, npm's optional dependency resolution automatically downloads only the binary matching their platform. This is the same approach used by SWC, Biome, and Lightning CSS. ## Status All infrastructure is complete. The formatting engine, WASM support, napi-rs CI pipeline, and platform binary publishing are all operational. Published packages: - `@takazudo/mdx-formatter` — main package (v1.0.1) - `@takazudo/mdx-formatter-{darwin-arm64,darwin-x64,linux-x64-gnu,win32-x64-msvc}` — platform binaries (v1.0.0) - `@takazudo/mdx-formatter-wasm` — browser WASM (v1.0.0) --- # /CLAUDE.md > Source: /pj/mdx-formatter/docs/claude-md/root **Path:** `CLAUDE.md` # @takazudo/mdx-formatter AST-based markdown and MDX formatter powered by a Rust engine (via napi-rs). Published as a scoped npm package (ESM-only). ## Tech Stack - **Language**: TypeScript (strict mode, ES2022 target, Node16 module resolution) - **Runtime**: Node.js >= 18 - **Package manager**: pnpm - **Test framework**: vitest - **Linting**: ESLint (flat config) + Prettier + lefthook (pre-commit hooks) - **Build**: `tsc` (output to `dist/`) - **Doc site**: zudo-doc / Astro (workspace in `doc/`) - **Rust implementation**: Production-ready Rust engine in `crates/` (markdown-rs + napi-rs + WASM) ## Commands ```bash pnpm build # Compile TypeScript to dist/ pnpm test # Run tests (vitest run) pnpm test:watch # Watch mode pnpm test:coverage # Coverage report pnpm lint # ESLint check pnpm lint:fix # ESLint autofix pnpm check # Prettier + ESLint check pnpm check:fix # Prettier + ESLint autofix ``` ## Conventions - **Commits**: Start with a scope prefix, then a short description: - `[formatter] ` - main formatter script (src/, test/, build, CLI) - `[doc] ` - documentation site related updates (doc/) - `[claude] ` - Claude Code related tweaks (.claude/, CLAUDE.md) - `[misc] ` - other things (CI, dependencies, config, etc.) - **Unused vars**: Prefix with `_` (enforced by ESLint `argsIgnorePattern: '^_'`) - **Imports**: Always use `.js` extension in TypeScript imports (required for ESM with Node16 resolution) - **Console**: `no-console` is `warn` everywhere except `src/cli.ts` and `format-stdin.js` ## Package Publishing - Scoped package: `@takazudo/mdx-formatter` - `files` field limits published content to: `dist/`, `format-stdin.js`, `README.md`, `LICENSE` - `prepublishOnly` runs `tsc && vitest run` automatically - Use `/l-version-increment` for stable releases - Use `/l-version-next` for prerelease (`@next` dist-tag) - Use `/l-version-promote` to promote a next version to stable --- # Headings > Source: /pj/mdx-formatter/docs/formatting/headings The formatter ensures blank lines after headings when followed by non-heading content. ## Rules - A blank line is added after a heading when followed by non-heading content (paragraphs, lists, JSX, etc.) - Consecutive headings are left as-is without inserting blank lines between them - Blank lines are added *after* headings, not before them ## Examples ### Heading followed by content Before: ````markdown # Heading Content here ```` After: ````markdown # Heading Content here ```` ### Heading followed by JSX Before: ````markdown ## Section Title ```` After: ````markdown ## Section Title ```` ### Heading followed by list Before: ````markdown ## Features - Item 1 - Item 2 - Item 3 ```` After: ````markdown ## Features - Item 1 - Item 2 - Item 3 ```` --- # addEmptyLineBetweenElements > Source: /pj/mdx-formatter/docs/options/add-empty-line-between-elements Add a single empty line between markdown block elements (headings, paragraphs, lists, code blocks, JSX components, etc.). ## Options | Property | Type | Default | Description | | --------- | --------- | ------- | ------------------------ | | `enabled` | `boolean` | `true` | Enable/disable this rule | ## Config ```json { "addEmptyLineBetweenElements": { "enabled": true } } ``` ## Examples ### Headings and paragraphs Before: ````markdown ## Heading Content here Another paragraph ```` After: ````markdown ## Heading Content here Another paragraph ```` ### JSX components and text Before: ````markdown Next paragraph ```` After: ````markdown Next paragraph ```` ### Lists and code blocks Before: ````markdown Some text - Item 1 - Item 2 ```js const x = 1; ``` More text ```` After: ````markdown Some text - Item 1 - Item 2 ```js const x = 1; ``` More text ```` --- # Installation > Source: /pj/mdx-formatter/docs/overview/installation ```bash npm install @takazudo/mdx-formatter ``` Or use directly with npx: ```bash npx @takazudo/mdx-formatter --write "**/*.md" ``` --- # Rust Crate > Source: /pj/mdx-formatter/docs/architecture/rust-crate # Rust Crate The core formatting engine is available as a standalone Rust crate (`mdx-formatter-core`) for direct integration in Rust applications. It has no Node.js dependency and can be embedded in CLI tools, desktop apps (Tauri), or any Rust project. ## Cargo Dependency Since the crate is not yet published to crates.io, use one of these dependency forms: ```toml [dependencies] # Option 1: git dependency (Cargo finds the workspace member by crate name) mdx-formatter-core = { git = "https://github.com/Takazudo/mdx-formatter" } # Option 2: local path (for development) mdx-formatter-core = { path = "../mdx-formatter/crates/mdx-formatter-core" } ``` ## Basic Usage ```rust use mdx_formatter_core::{format, FormatterSettings}; let content = "# Hello\nSome text"; let settings = FormatterSettings::default(); let formatted = format(content, &settings); // formatted == "# Hello\n\nSome text" ``` The `format` function runs a convergence loop (up to 3 iterations) to ensure idempotent output. It returns the formatted string directly -- no `Result` wrapping, no async. ## FormatterSettings `FormatterSettings` mirrors the TypeScript settings structure. Each formatting rule is a separate field: ```rust use mdx_formatter_core::FormatterSettings; let mut settings = FormatterSettings::default(); // Disable the spacing rule settings.add_empty_line_between_elements.enabled = false; let formatted = mdx_formatter_core::format(content, &settings); ``` ### Available Settings Fields | Field | Type | Default | Description | | --- | --- | --- | --- | | `add_empty_line_between_elements` | `AddEmptyLineBetweenElementsSetting` | enabled | Add empty lines between elements | | `format_multi_line_jsx` | `FormatMultiLineJsxSetting` | enabled | Format multi-line JSX | | `format_html_blocks_in_mdx` | `FormatHtmlBlocksInMdxSetting` | enabled | Format HTML blocks | | `expand_single_line_jsx` | `ExpandSingleLineJsxSetting` | disabled | Expand single-line JSX | | `indent_jsx_content` | `IndentJsxContentSetting` | disabled | Indent JSX content | | `add_empty_lines_in_block_jsx` | `AddEmptyLinesInBlockJsxSetting` | enabled | Empty lines in block JSX | | `format_yaml_frontmatter` | `FormatYamlFrontmatterSetting` | enabled | Format YAML frontmatter | | `preserve_admonitions` | `PreserveAdmonitionsSetting` | enabled | Preserve Docusaurus admonitions | | `error_handling` | `ErrorHandlingSetting` | off | Throw on parse errors | | `auto_detect_indent` | `AutoDetectIndentSetting` | disabled | Auto-detect indentation | ### Loading from JSON Settings can be created from a partial JSON value, merging with defaults: ```rust use mdx_formatter_core::FormatterSettings; let json: serde_json::Value = serde_json::json!({ "addEmptyLineBetweenElements": { "enabled": false }, "formatMultiLineJsx": { "indentSize": 4 } }); let settings = FormatterSettings::from_partial_json(&json); ``` JSON field names use camelCase (matching the TypeScript/config file convention), while Rust struct fields use snake_case. All 10 settings fields are supported via serde deserialization with camelCase JSON keys. ## Public Exports The crate re-exports through `lib.rs`: | Symbol | Module | Description | | --- | --- | --- | | `format` | `formatter` | The main formatting function (panics on error if `throw_on_error` is true) | | `try_format` | `formatter` | Returns `Result` instead of panicking | | `FormatterSettings` | `types` | Settings struct | | `format_html_block` | `html_formatter` | HTML block indentation formatter | | `load_config` | `config` | Load and merge config from file | | `load_full_config` | `config` | Load config + exclude patterns | | `load_full_config_from` | `config` | Load config from a specific directory | | `load_exclude_patterns` | `config` | Load only exclude patterns | | `FullConfig` | `config` | Settings + exclude patterns struct | All symbols are re-exported at the crate root, so `use mdx_formatter_core::{format, FormatterSettings}` works directly. The `parser`, `types`, and `html_formatter` modules are also public for consumers who need lower-level access. ## Status The Rust crate implements all formatting rules from the TypeScript version. See [Rust Rewrite](/docs/architecture/rust-rewrite) for the full status and remaining infrastructure work. --- # Testing Strategy > Source: /pj/mdx-formatter/docs/architecture/testing-strategy # Testing Strategy mdx-formatter uses a multi-layered testing approach. The TypeScript test suite in `test/` defines the formatting contract and runs all tests through the Rust napi engine. The Rust test suite in `crates/` validates the engine independently. ## TypeScript Test Suite The tests in `test/` exercise the public `format()` API, which calls the Rust engine via napi-rs. ### Test Files | File | Focus | Description | | --- | --- | --- | | `formatter.test.ts` | Core formatting | Heading spacing, list indentation, JSX handling, Japanese text | | `mdx-formatter.test.ts` | Advanced formatting | JSX, block components, indentation, edge cases | | `idempotency.test.ts` | Stability | `format(format(x)) === format(x)` for all inputs | | `html-blocks.test.ts` | HTML formatting | HTML block indentation in MDX | | `load-config.test.ts` | Configuration | `.mdx-formatter.json` loading and merging | | `url-autolink.test.ts` | URL handling | Autolink behavior and URL preservation | | `rust-formatter.test.ts` | Additional tests | Extra Rust engine coverage | | `rust-passthrough.test.ts` | Behavior validation | 85 formatting behavior tests | All tests use vitest and can be run with: ```bash pnpm test # Run all 207 tests once pnpm test:watch # Watch mode pnpm test:coverage # Coverage report ``` ## Rust Test Suite The Rust tests in `crates/mdx-formatter-core/` validate the formatting engine independently. There are 342 tests across multiple test files. ### Test Files | File | Count | Focus | | --- | --- | --- | | Inline unit tests (formatter, html_formatter, config, parser) | 124 | Unit tests for all formatting rules, YAML, settings, config, HTML | | `tests/cross_platform.rs` | 165 | Cross-platform validation | | `tests/plugin_validation.rs` | 42 | Validates that original TS plugins are not needed | | `tests/spacing_recursion.rs` | 11 | Spacing at all AST depths (blockquotes, JSX) | ```bash cargo test # Run all 342 Rust tests cargo test --test cross_platform # Just cross-platform tests ``` ## How the Test Bridge Works The napi-rs bridge enables running the TypeScript test suite against the Rust formatter: ``` vitest ↓ src/rust-formatter.ts ← JS wrapper (loads .node binary) ↓ napi-rs binding ← platform-specific native module ↓ mdx-formatter-core ← Rust formatting engine ↓ formatted output ← returned to vitest for assertion ``` ## Idempotency Testing A core property of the formatter is idempotency: formatting an already-formatted file must produce identical output. The convergence loop (max 3 iterations) in the Rust engine guarantees this: ```typescript const first = await format(input); const second = await format(first); expect(first).toBe(second); ``` ## Testing New Rules When adding a new formatting rule: 1. Write tests in `test/` — these define the contract 2. Implement the rule in Rust (`crates/mdx-formatter-core/src/formatter.rs`) 3. Build the napi module: `pnpm build:rust` 4. Verify tests pass: `pnpm test` 5. Add Rust-level tests in `crates/mdx-formatter-core/tests/` 6. Verify Rust tests pass: `cargo test` --- # /crates/CLAUDE.md > Source: /pj/mdx-formatter/docs/claude-md/crates **Path:** `crates/CLAUDE.md` # crates/ — Rust Formatting Engine The sole formatting engine for mdx-formatter. Built with markdown-rs and napi-rs. ## Workspace Structure - `mdx-formatter-core/` — Pure Rust library: parser, formatter, config, types - `mdx-formatter-cli/` — Standalone CLI binary (clap-based) - `mdx-formatter-napi/` — napi-rs bindings for Node.js - `mdx-formatter-wasm/` — WASM bindings for browser use ## Building and Testing ```bash . "$HOME/.cargo/env" # Source Rust environment cargo build # Build all crates cargo test # Run all Rust tests (342 tests) cargo build -p mdx-formatter-napi # Build just the napi module ``` ## Architecture Hybrid formatter approach: 1. Parse markdown/MDX into mdast via `markdown::to_mdast()` (with MDX, GFM, frontmatter) 2. Walk AST to collect line-based `FormatterOperation` values 3. Apply operations to original source lines (not AST round-trip) 4. Convergence loop: repeat up to 3 times until output stabilizes Key modules in `mdx-formatter-core/src/`: - `formatter.rs` — Hybrid formatter (convergence loop + all rules) - `html_formatter.rs` — HTML block indentation formatter - `config.rs` — Config file loading (3-layer merge) - `parser.rs` — markdown-rs integration (mdast parsing) - `types.rs` — Settings, operations, type definitions ## Status - All formatting rules implemented and tested - 342 tests passing (124 unit + 165 cross-platform + 42 plugin validation + 11 spacing recursion) - CLI binary working (`--write`, `--check`, `--config`, glob patterns) - Browser/WASM support implemented (`mdx-formatter-wasm` crate, web + bundler targets) - napi-rs CI build pipeline — done (cross-platform binary generation, published to npm) --- # Lists > Source: /pj/mdx-formatter/docs/formatting/lists The formatter normalizes list indentation. Spacing around lists is preserved as-is. ## Rules - List markers (`-`, `*`, `+`, `1.`) are preserved as-is - Leading spaces on top-level list items are removed - Nested lists are indented consistently ## Examples ### Remove leading spaces Before: ````markdown - First item - Second item - Third item ```` After: ````markdown - First item - Second item - Third item ```` ### Nested lists Before: ````markdown - Parent item - Nested item 1 - Nested item 2 - Another parent ```` After: ````markdown - Parent item - Nested item 1 - Nested item 2 - Another parent ```` ### Numbered lists Before: ````markdown 1. First item 2. Second item 3. Third item ```` After: ````markdown 1. First item 2. Second item 3. Third item ```` --- # formatMultiLineJsx > Source: /pj/mdx-formatter/docs/options/format-multi-line-jsx Format multi-line JSX/HTML components with proper indentation. The closing `/>` is appended to the last attribute line. ## Options | Property | Type | Default | Description | | --------------------------------- | ---------- | ------- | -------------------------------------------------------------------------------- | | `enabled` | `boolean` | `true` | Enable/disable this rule | | `indentSize` | `number` | `2` | Number of spaces for indentation | | `ignoreComponents` | `string[]` | `[]` | Component names to skip (preserve their formatting as-is) | | `preserveTemplateLiteralIndent` | `boolean` | `true` | Preserve indentation inside template literal JSX attributes (backtick strings) | ## Config ```json { "formatMultiLineJsx": { "enabled": true, "indentSize": 2, "ignoreComponents": ["CodeBlock", "RawHTML"], "preserveTemplateLiteralIndent": true } } ``` ## Examples ### Basic multi-line JSX formatting Before: ````markdown ```` After: ````markdown ```` ### Inconsistent indentation Before: ````markdown ```` After: ````markdown ```` ### Ignoring specific components With `ignoreComponents: ["CodeBlock"]`, the component is left untouched: ````markdown ```` This stays exactly as-is — no reformatting applied. ### Template literal indentation preservation When `preserveTemplateLiteralIndent` is `true` (the default), indentation inside template literal JSX attributes is preserved as-is. This is important for components like `CssPreview` where the `html` and `css` props contain meaningful indentation: ````markdown Title Content `} css={`.card { border: 1px solid hsl(0 0% 80%); .card__header { padding: 8px 16px; } }`} height={300} /> ```` Without this option, the formatter would flatten all lines inside the template literals to a uniform indent depth, destroying the HTML nesting structure and CSS formatting. --- # Usage > Source: /pj/mdx-formatter/docs/overview/usage ## CLI ```bash # Check files (exit with error if formatting needed) mdx-formatter --check "**/*.{md,mdx}" # Format files in place mdx-formatter --write "**/*.{md,mdx}" # Preview what would be changed (default) mdx-formatter "**/*.{md,mdx}" # Ignore specific patterns mdx-formatter --write "**/*.md" --ignore "node_modules/**,dist/**" # Use a custom config file mdx-formatter --write "**/*.md" --config ./my-config.json ``` ## API ```javascript // Format a string const formatted = await format('# Hello\nWorld'); console.log(formatted); // '# Hello\n\nWorld' // Format with custom settings const formatted2 = await format(content, { settings: { addEmptyLinesInBlockJsx: { blockComponents: ['Outro', 'InfoBox'], }, indentJsxContent: { containerComponents: ['Outro', 'InfoBox'], }, formatMultiLineJsx: { ignoreComponents: ['CodeBlock'], }, }, }); ``` ## Stdin ```bash cat file.md | ./format-stdin.js > formatted.md ``` ## Integration with lint-staged Add to your `package.json`: ```json { "lint-staged": { "*.{md,mdx}": ["mdx-formatter --write"] } } ``` --- # /doc/CLAUDE.md > Source: /pj/mdx-formatter/docs/claude-md/doc **Path:** `doc/CLAUDE.md` # doc/ — zudo-doc Documentation Site Documentation site built with zudo-doc (Astro-based). ## Dev Commands ```bash pnpm --dir doc dev # Dev server on port 3518 pnpm --dir doc dev:network # Dev server on 0.0.0.0:3518 (network accessible) pnpm --dir doc build # Production build ``` ## Structure - `src/content/docs/` — MDX documentation content - `overview/` — Installation, usage, API, configuration - `formatting/` — Formatting rules documentation - `options/` — Per-rule configuration options - `architecture/` — Architecture and Rust rewrite docs - `changelog/` — Version release notes (descending sort) - `claude/`, `claude-md/`, `claude-skills/` — Auto-generated Claude Code resources - `src/config/settings.ts` — Site configuration (nav, footer, features, color scheme) - `src/styles/global.css` — Theme tokens and styles (Futura headings, Noto Sans body) - `src/integrations/claude-resources/` — Auto-generates docs from `.claude/` directory - `public/img/` — Static assets (logo SVG) ## Features Enabled - Search (Pagefind), sidebar filter, light/dark theme - Claude resources integration (CLAUDE.md, skills) - llms.txt generation, doc history, versioning (empty) - Futura + Noto Sans JP font stack ## Styling Before writing or modifying any CSS, components, or layouts in `doc/`, invoke `/l-design-system` to load the design token reference. The doc site uses a **tight token strategy** (Tailwind v4 with no default theme) — default Tailwind classes like `h-3`, `w-4`, `text-sm` do not exist. Always use the project's semantic tokens (`text-caption`, `px-hsp-md`, `bg-surface`, etc.). ## Adding Documentation - Each category needs `_category_.json` with `label` and `position` - All MDX files require `title` in frontmatter (schema enforced) - Changelog uses `sortOrder: "desc"` — higher `sidebar_position` = newer = first - Header nav is configured in `src/config/settings.ts` → `headerNav` --- # Code Blocks > Source: /pj/mdx-formatter/docs/formatting/code-blocks Code blocks are preserved exactly as written. The formatter does not modify content, language identifiers, or meta strings inside code blocks. ## Rules - Fenced code blocks (` ``` `) are preserved as-is - Language identifiers are preserved - Content inside code blocks is never modified - Meta strings (e.g., `title="example.js"`) are preserved ## Examples ### JSX content inside code blocks is not formatted Code blocks containing JSX are left as-is — the JSX formatting rules do not apply inside fences: Before: ````markdown ```jsx ``` ```` After (unchanged): ````markdown ```jsx ``` ```` ### Code block with title (MDX) Before: ````markdown ```js title="example.js" function hello() { console.log('world'); } ``` ```` After (unchanged — content and meta string preserved): ````markdown ```js title="example.js" function hello() { console.log('world'); } ``` ```` --- # formatHtmlBlocksInMdx > Source: /pj/mdx-formatter/docs/options/format-html-blocks-in-mdx Format HTML blocks within MDX content with proper indentation. Applies to standard HTML elements like ``, ``, ``, ``, etc. Uses a built-in indentation formatter in the Rust engine. ## Options | Property | Type | Default | Description | | -------------------------- | --------- | --------- | -------------------------------------- | | `enabled` | `boolean` | `true` | Enable/disable this rule | | `formatterConfig` | `object` | see below | Formatter configuration | | `formatterConfig.tabWidth` | `number` | `2` | Number of spaces per indentation level | | `formatterConfig.useTabs` | `boolean` | `false` | Use tabs instead of spaces | ## Config ```json { "formatHtmlBlocksInMdx": { "enabled": true, "formatterConfig": { "parser": "html", "tabWidth": 4, "useTabs": false } } } ``` ## Line Wrapping The formatter uses a very large print width internally, so HTML content is not wrapped to a specific line length. Only indentation and whitespace normalization are applied. ## Examples ### Definition list Before: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` After: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` ### Nested HTML structures Before: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` After: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` ### Table Before: ````markdown Name Value Item 1 100 ```` After: ````markdown Name Value Item 1 100 ```` --- # Configuration > Source: /pj/mdx-formatter/docs/overview/configuration The formatter looks for configuration in three layers (later layers override earlier ones): 1. **Built-in defaults** (from `settings.ts`) 2. **Config file** (`.mdx-formatter.json` or `"mdx-formatter"` key in `package.json`) 3. **Programmatic options** (passed to `format()`) ## Config File Create `.mdx-formatter.json` in your project root: ```json { "addEmptyLinesInBlockJsx": { "blockComponents": ["Outro", "InfoBox"] }, "indentJsxContent": { "containerComponents": ["Outro", "InfoBox", "LayoutDivide"] }, "formatMultiLineJsx": { "ignoreComponents": ["CodeBlock"] } } ``` Or add an `"mdx-formatter"` key to your `package.json`: ```json { "mdx-formatter": { "addEmptyLinesInBlockJsx": { "blockComponents": ["Outro", "InfoBox"] } } } ``` ## Excluding Files You can add an `exclude` array to your config file to skip files matching the given glob patterns. These patterns are merged with the CLI `--ignore` option. ```json { "exclude": ["doc/docs/claude/**", "generated/**/*.md"], "formatMultiLineJsx": { "ignoreComponents": ["CodeBlock"] } } ``` This is useful for excluding auto-generated files that don't need formatting, without adding patterns to every CLI invocation. The `exclude` key also works in `package.json`: ```json { "mdx-formatter": { "exclude": ["doc/docs/claude/**"] } } ``` --- # /src/CLAUDE.md > Source: /pj/mdx-formatter/docs/claude-md/src **Path:** `src/CLAUDE.md` # src/ — TypeScript Wrapper for Rust Engine ## Architecture The formatting engine is written in Rust (`crates/mdx-formatter-core`). The TypeScript code in `src/` is a thin wrapper that loads the Rust napi module and provides the npm package API. ### Key Files - `index.ts` — Public API (`format()`, `formatFile()`, `checkFile()`, `detectMdx()`) - `cli.ts` — CLI entry point (commander-based) - `rust-formatter.ts` — Loads the Rust napi module (platform package or local build) - `settings.ts` — Default `FormatterSettings` with all rules and their defaults - `types.ts` — Settings interfaces and `FormatOptions` - `load-config.ts` — Loads `.mdx-formatter.json` config and merges with defaults - `utils.ts` — Utility functions (deep clone, deep merge with prototype pollution guard) - `detect-mdx.ts` — MDX content detection heuristic - `browser.ts` — Browser-safe export (re-exports from index.ts; for true browser use, see WASM) ## Settings All formatter rules are defined in `settings.ts` as the `formatterSettings` object. Each rule has an `enabled` flag and rule-specific options. Some rules (e.g., `indentJsxContent`, `addEmptyLinesInBlockJsx`) accept component name arrays that ship empty by default — users configure them per-project via `.mdx-formatter.json`. ## Types Settings interfaces live in `types.ts`. Key types: - `FormatterSettings` / `FormatOptions` — Configuration - `DeepPartial` — Used for partial settings overrides --- # Tables > Source: /pj/mdx-formatter/docs/formatting/tables GFM (pipe-style) tables are preserved as-is. The formatter does not modify GFM table alignment or padding. HTML tables are handled by the [formatHtmlBlocksInMdx](/docs/options/format-html-blocks-in-mdx) option. ## Rules - GFM (pipe-style) tables are preserved as-is - The formatter does not modify GFM table alignment or padding ## Examples ### HTML table indentation HTML tables in MDX are formatted by the `formatHtmlBlocksInMdx` rule: Before: ````markdown Header 1 Header 2 Data 1 Data 2 ```` After: ````markdown Header 1 Header 2 Data 1 Data 2 ```` --- # expandSingleLineJsx > Source: /pj/mdx-formatter/docs/options/expand-single-line-jsx Convert single-line JSX components with multiple props to multi-line format. Also respects `ignoreComponents` from `formatMultiLineJsx`. **Disabled by default.** This rule is disabled by default due to a known issue where inline JSX content can be lost during expansion. It will be enabled by default once the bug is resolved. ## Options | Property | Type | Default | Description | | ---------------- | --------- | ------- | --------------------------------------------------- | | `enabled` | `boolean` | `false` | Enable/disable this rule (disabled by default) | | `propsThreshold` | `number` | `2` | Expand if the component has this many props or more | ## Config ```json { "expandSingleLineJsx": { "enabled": true, "propsThreshold": 3 } } ``` ## Examples ### Default threshold (2 props) With `enabled: true` and `propsThreshold: 2`: Before: ````markdown ```` After: ````markdown ```` ### Higher threshold With `propsThreshold: 3`, a component with only 2 props stays on one line: Before: ````markdown ```` After (unchanged — only 2 props, threshold is 3): ````markdown ```` But a component with 3 or more props gets expanded: Before: ````markdown ```` After: ````markdown ```` ### Single prop (never expanded) Components with a single prop are never expanded regardless of settings: ````markdown ```` This stays as-is. --- # /test/CLAUDE.md > Source: /pj/mdx-formatter/docs/claude-md/test **Path:** `test/CLAUDE.md` # test/ — Test Suite ## Framework Tests use vitest with `globals: true` (no need to import `describe`/`it`/`expect`). ## Test Helpers `test-helpers.ts` exports `testSettings` — a `DeepPartial` with component names that tests were written against. The library ships with empty component arrays by default, so **always pass `{ settings: testSettings }` when testing component-specific behavior** (e.g., block JSX, container indentation, ignore components). ## Test Files - `formatter.test.ts` — Core formatting: headings, paragraphs, spacing, frontmatter, lists, MDX/JSX - `html-blocks.test.ts` — HTML block formatting within MDX - `mdx-formatter.test.ts` — Advanced formatting: JSX, block components, indentation, edge cases - `url-autolink.test.ts` — URL autolink handling - `idempotency.test.ts` — Single-pass stability and convergence tests - `load-config.test.ts` — Config file loading and merging - `rust-formatter.test.ts` — Additional Rust engine tests - `rust-passthrough.test.ts` — Formatting behavior validation (85 tests) ## Patterns - Test files are named `{feature}.test.ts` - Tests use inline markdown strings with the `format()` API - All tests run through the Rust napi engine - Typical pattern: `expect(await format(input, { settings: testSettings })).toBe(expected)` --- # Japanese Text > Source: /pj/mdx-formatter/docs/formatting/japanese-text ## The problem with general formatters Most markdown formatters apply Western typographic rules that do not work well with Japanese (CJK) text. Common issues include: - **Line reflow** — Formatters reflow paragraphs to fit a line width. Since Japanese does not use spaces between words, the formatter cannot determine where to break lines, resulting in inappropriate joins or splits. - **Unwanted backslashes** — Some formatters insert `\` at line ends to preserve hard line breaks, cluttering Japanese text with unnecessary escape characters. - **Space insertion** — Formatters may add spaces around emphasis markers (`**bold**`), punctuation (。、), or between Japanese and ASCII characters where none should exist. - **HTML entity encoding** — Characters like `を` can get encoded to `を`, making the source unreadable. These issues make general-purpose formatters difficult to use in projects that contain Japanese markdown content. ## How mdx-formatter handles Japanese text Japanese text is preserved as-is by the formatter. The formatter does not insert or remove spaces around Japanese punctuation. This works through the formatter's text-preservation approach -- the hybrid line-based formatter preserves original text for content it does not specifically transform. - Japanese punctuation spacing is preserved - No extra spaces are inserted around Japanese characters - Bold/emphasis markers work correctly with Japanese text - URLs and parenthetical expressions in Japanese text are preserved - Line breaks within Japanese paragraphs are not altered ## Examples ### Japanese headings and content Before: ````markdown # 日本語の見出し 内容です。 ```` After: ````markdown # 日本語の見出し 内容です。 ```` ### Bold text with Japanese Japanese bold text is preserved without unwanted space changes: Before: ````markdown この値を **×2** します ```` After (unchanged): ````markdown この値を **×2** します ```` ### Image alt text with Japanese and colons Before: ````markdown ![図:VCAによるオーディオシグナルの減衰処理](/images/p/vca-exp-2) ```` After (unchanged — colons and Japanese in alt text preserved): ````markdown ![図:VCAによるオーディオシグナルの減衰処理](/images/p/vca-exp-2) ```` ### URLs in parenthetical expressions Before: ````markdown 詳細は (https://example.com) をご覧ください ```` After (unchanged — parentheses around URL preserved): ````markdown 詳細は (https://example.com) をご覧ください ```` ### Multi-line Japanese paragraphs Line breaks within Japanese paragraphs are preserved as-is: Before: ````markdown こんにちは、Takazudo Modularです。 Takazudo Modular Highlightsなどというメルマガの名前にしてみました。 そんな年中送るかは分かりませんが……。 ```` After (unchanged): ````markdown こんにちは、Takazudo Modularです。 Takazudo Modular Highlightsなどというメルマガの名前にしてみました。 そんな年中送るかは分かりませんが……。 ```` --- # indentJsxContent > Source: /pj/mdx-formatter/docs/options/indent-jsx-content Add indentation to content inside JSX container components. **Disabled by default.** You must specify which components to indent via `containerComponents`. ## Options | Property | Type | Default | Description | | --------------------- | ---------- | ------- | ------------------------------------------------ | | `enabled` | `boolean` | `false` | Enable/disable this rule | | `indentSize` | `number` | `2` | Number of spaces for indentation | | `containerComponents` | `string[]` | `[]` | Component names whose content should be indented | ## Config ```json { "indentJsxContent": { "enabled": true, "indentSize": 2, "containerComponents": ["Outro", "InfoBox", "LayoutDivide"] } } ``` ## Examples ### Basic content indentation With `containerComponents: ["Outro"]`: Before: ````markdown This is the outro content. Multiple lines here. ```` After: ````markdown This is the outro content. Multiple lines here. ```` ### List content indentation With `containerComponents: ["LayoutDivideItem"]`: Before: ````markdown - List item 1 - List item 2 - List item 3 ```` After: ````markdown - List item 1 - List item 2 - List item 3 ```` ### Non-listed components are not indented Components not in `containerComponents` are left alone: ````markdown Content stays as-is. ```` This stays unchanged. ## Caution with markdown content **Do not use this option on components that contain markdown content** (admonitions like ``, ``, ``, ``, ``, etc.). In standard markdown, **4 or more spaces of indentation creates a code block**. If you indent markdown content inside a JSX component, the markdown parser may misinterpret the content. For example, with `containerComponents: ["Note"]` and `indentSize: 4`: ````markdown This looks like a code block to the markdown parser. - This list won't render as a list either. ```` Even with `indentSize: 2`, combining with already-indented content (like nested lists or code blocks) can push lines past the 4-space threshold. ### What to use instead For admonition-like components, use [`addEmptyLinesInBlockJsx`](./add-empty-lines-in-block-jsx) instead. It adds the blank lines needed for MDX to parse content as markdown, without changing indentation: ````markdown **This bold text** renders correctly. - Lists work too ```` ### When indentJsxContent is safe This option works well for components whose children are **not parsed as markdown** — for example, layout wrappers that contain other JSX components rather than prose: ````markdown Content here Content here ```` --- # MDX / JSX > Source: /pj/mdx-formatter/docs/formatting/mdx-jsx The formatter preserves MDX-specific syntax including JSX components, import/export statements, and expressions. Several configurable options control JSX formatting — see the [Options](/docs/options) section. ## Rules - JSX components are preserved - Import/export statements are maintained - Self-closing tags remain self-closing - A blank line is added after a JSX component when followed by text content - Consecutive JSX components are not automatically separated unless configured as [`blockComponents`](/docs/options/add-empty-lines-in-block-jsx) - Multi-line JSX formatting is handled by the [formatMultiLineJsx](/docs/options/format-multi-line-jsx) option ## Examples ### Self-closing JSX component Before: ````markdown ```` After (unchanged): ````markdown ```` ### Import statements Before: ````markdown # Content ```` After (unchanged — imports preserved): ````markdown # Content ```` ### Export statements Before: ````markdown # Content ```` After (unchanged — exports preserved): ````markdown # Content ```` ### JSX after text Before: ````markdown そんなわけで、以下がVol.1の内容となります。 ```` After: ````markdown そんなわけで、以下がVol.1の内容となります。 ```` ### Multi-line JSX formatting Controlled by the [formatMultiLineJsx](/docs/options/format-multi-line-jsx) option: Before: ````markdown ```` After: ````markdown ```` --- # addEmptyLinesInBlockJsx > Source: /pj/mdx-formatter/docs/options/add-empty-lines-in-block-jsx Add empty lines after opening tags and before closing tags in block JSX components for better readability. You must specify which components are "block" components via `blockComponents`. ## Options | Property | Type | Default | Description | | ----------------- | ---------- | ------- | -------------------------------------------------- | | `enabled` | `boolean` | `true` | Enable/disable this rule | | `blockComponents` | `string[]` | `[]` | Component names that should have empty lines added | ## Config ```json { "addEmptyLinesInBlockJsx": { "enabled": true, "blockComponents": ["Outro", "InfoBox", "Sidebar"] } } ``` ## Examples ### Basic block component With `blockComponents: ["Outro"]`: Before: ````markdown This is the outro content. Multiple lines here. ```` After: ````markdown This is the outro content. Multiple lines here. ```` ### InfoBox with list content With `blockComponents: ["InfoBox"]`: Before: ````markdown Important information - Point 1 - Point 2 ```` After: ````markdown Important information - Point 1 - Point 2 ```` ### Admonition-like components (recommended use case) This option is particularly useful for admonition-style components (``, ``, ``, etc.) commonly used in documentation frameworks like Docusaurus and Astro. In MDX, content inside JSX tags **requires blank lines** to be parsed as markdown. Without them, the content may be treated as raw text — headings, lists, bold, and other markdown syntax won't render. With `blockComponents: ["Note", "Warning", "Danger"]`: Before (markdown not parsed): ````markdown **Important:** This text won't be bold. - This won't render as a list ```` After (markdown parsed correctly): ````markdown **Important:** This text will be bold. - This renders as a list ```` The blank line after `` is what tells the MDX parser to treat the content as markdown. ### Single-line component expansion When a component listed in `blockComponents` is written on a single line, the formatter expands it to multi-line with empty lines added. With `blockComponents: ["Note"]`: Before: ````markdown Content here ```` After: ````markdown Content here ```` ### Non-listed components are not affected Components not in `blockComponents` are left alone: ````markdown Content without extra empty lines. ```` This stays unchanged. ## Caution with indentJsxContent If you also use [`indentJsxContent`](./indent-jsx-content) on the same components, be careful. Indenting markdown content inside admonitions can break rendering — in standard markdown, 4 or more spaces of indentation creates a code block. See the [indentJsxContent caution section](./indent-jsx-content#caution-with-markdown-content) for details. --- # API Reference > Source: /pj/mdx-formatter/docs/overview/api ## CLI Options | Option | Description | | --------------------- | ----------------------------------------------------------------------------------------------------- | | `-w, --write` | Write formatted files in place | | `-c, --check` | Check if files need formatting (for CI) | | `--config ` | Path to config file | | `--ignore ` | Comma-separated patterns to ignore (default: `node_modules/**,dist/**,build/**,.git/**,worktrees/**`) | The `--ignore` patterns are merged with [`exclude`](/docs/overview/configuration#excluding-files) patterns from the config file. ## `format(content, options?)` Format markdown/MDX content. - `content` (`string`) - Content to format - `options.config` (`string`) - Path to config file - `options.settings` (`object`) - Direct settings overrides (see [Options Reference](/docs/options)) - Returns `Promise` - Formatted content (returns original on error) > The `exclude` config option is CLI-only. When using the programmatic API (`format`, `formatFile`, `checkFile`), handle file filtering in your own code. ## `formatFile(filePath, options?)` Format a file and write it back if changed. - `filePath` (`string`) - Path to the file - `options` - Same as `format()` - Returns `Promise` - `true` if file was changed ## `checkFile(filePath, options?)` Check if a file needs formatting without modifying it. - `filePath` (`string`) - Path to the file - `options` - Same as `format()` - Returns `Promise` - `true` if file needs formatting ## `formatSync(content)` Synchronous format with default settings. API-compatible with `@takazudo/mdx-formatter-wasm`'s `format_with_defaults()`. Useful for mocking the WASM module in Node.js test environments. - `content` (`string`) - Content to format - Returns `string` - Formatted content ```typescript const formatted = formatSync("# Hello\nSome text"); ``` ### Test mock example ```typescript vi.mock("@takazudo/mdx-formatter-wasm", async () => { const { formatSync } = await import("@takazudo/mdx-formatter"); return { default: async () => {}, format_with_defaults: formatSync, }; }); ``` ## `detectMdx(content)` Check if content is likely MDX (has imports, exports, JSX components, or frontmatter). - `content` (`string`) - Content to check - Returns `boolean` --- # HTML Blocks > Source: /pj/mdx-formatter/docs/formatting/html-blocks The formatter properly indents HTML blocks within MDX content. This is controlled by the [formatHtmlBlocksInMdx](/docs/options/format-html-blocks-in-mdx) option using a built-in indentation formatter. ## Rules - HTML blocks (`dl`, `table`, `div`, `ul`, `ol`, `form`, etc.) are properly indented - Content whitespace in `dt`/`dd` elements is trimmed - Nested HTML structures maintain correct indentation hierarchy ## Examples ### Definition list Before: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` After: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` ### Definition list with div wrappers Before: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` After: ````markdown Term 1 Definition 1 Term 2 Definition 2 ```` ### Whitespace trimming in dt/dd Before: ````markdown Term with spaces Definition with spaces ```` After: ````markdown Term with spaces Definition with spaces ```` ### Nested HTML structures Before: ````markdown Outer Term Inner Term Inner Definition ```` After: ````markdown Outer Term Inner Term Inner Definition ```` ### Unordered list Before: ````markdown Item 1 Item 2 Item 3 ```` After: ````markdown Item 1 Item 2 Item 3 ```` ### Nested divs Before: ````markdown Content ```` After: ````markdown Content ```` --- # formatYamlFrontmatter > Source: /pj/mdx-formatter/docs/options/format-yaml-frontmatter Format YAML frontmatter using proper YAML formatting rules. ## Options | Property | Type | Default | Description | | -------------- | --------- | ------- | --------------------------------------- | | `enabled` | `boolean` | `true` | Enable/disable this rule | | `indent` | `number` | `2` | Number of spaces for YAML indentation | | `lineWidth` | `number` | `100` | Maximum line width for folded strings | | `quotingType` | `string` | `"\""` | Quote type for strings: `"\""` or `"'"` | | `forceQuotes` | `boolean` | `false` | Force quotes on all string values | | `noCompatMode` | `boolean` | `true` | Use YAML 1.2 spec (not 1.1) | | `fixUnsafeValues` | `boolean` | `true` | Pre-process YAML to quote values containing special characters (colons, hash signs) before parsing | ## Config ```json { "formatYamlFrontmatter": { "enabled": true, "indent": 2, "lineWidth": 80, "quotingType": "'", "forceQuotes": false, "noCompatMode": true } } ``` ## Examples ### Remove unnecessary quotes and extra blank lines Before: ````markdown --- title: "Test Article" description: "A long description" tags: - tag1 - tag2 --- # Content ```` After: ````markdown --- title: Test Article description: A long description tags: - tag1 - tag2 --- # Content ```` ### Normalize inline arrays to block style Before: ````markdown --- title: "Article Title" tags: [tag1, tag2, tag3] --- ```` After: ````markdown --- title: Article Title tags: - tag1 - tag2 - tag3 --- ```` ### Complex frontmatter Before: ````markdown --- title: "My Post" date: 2024-01-15 author: "John Doe" categories: [blog, tech] draft: false --- ```` After: ````markdown --- title: My Post date: 2024-01-15 author: John Doe categories: - blog - tech draft: false --- ```` ## Unsafe Value Pre-processing When `fixUnsafeValues` is enabled (default), the formatter pre-processes YAML values before parsing to prevent data corruption. Values containing `: ` (colon-space), ` #` (space-hash), or starting with special characters (`!`, `&`, `*`, `%`, `@`, backtick) are automatically wrapped in double quotes. This prevents issues like: - `title: Acme: Best Products` being parsed as nested YAML - `note: See section #3` being truncated at the `#` comment marker ## Known Caveats ### Date values lose quotes after formatting When `forceQuotes` is `false` (the default), the formatter strips unnecessary quotes from string values. This means quoted date strings like `"2024-08-03"` become unquoted `2024-08-03`. Before: ````markdown --- createdAt: "2024-08-03" --- ```` After: ````markdown --- createdAt: 2024-08-03 --- ```` The mdx-formatter itself handles this correctly — it uses `JSON_SCHEMA` internally, so the value stays as a string `"2024-08-03"` throughout the parse/dump cycle and is never converted to a JavaScript Date object. However, **downstream YAML parsers that use the default YAML schema** (such as `gray-matter`) will parse unquoted `2024-08-03` as a JavaScript `Date` object instead of a string. This can cause runtime errors or unexpected behavior in frameworks that read frontmatter. **Workarounds:** - **Set `forceQuotes: true`** in the formatter config to keep all string values quoted - **Normalize date fields** in your application code after parsing frontmatter (convert Date objects back to `YYYY-MM-DD` strings) - **Disable YAML formatting** with `formatYamlFrontmatter.enabled: false` if the quote stripping causes issues --- # Browser Usage > Source: /pj/mdx-formatter/docs/overview/browser-usage # Browser Usage For browser environments, use the dedicated WASM package. The main `@takazudo/mdx-formatter` package uses a native Rust module (napi-rs) that only works in Node.js. ## Installation ```bash npm install @takazudo/mdx-formatter-wasm ``` ## Usage ```javascript // Initialize the WASM module first await init(); // Format with default settings const formatted = format_with_defaults("# Hello\nSome text"); // Format with custom settings (JSON string) const settings = JSON.stringify({ addEmptyLineBetweenElements: { enabled: true }, formatMultiLineJsx: { indentSize: 4 }, }); const formatted2 = format("# Hello\nSome text", settings); ``` The WASM module provides the same Rust formatting engine that powers the Node.js package, compiled to WebAssembly. See the [Playground](/docs/playground) for a live example. ## Detecting MDX The `detectMdx` utility is available from the main package's `/browser` subpath export (no Node.js `fs`/`path` dependencies): ```typescript if (detectMdx(content)) { console.log("Content has MDX syntax"); } ``` Note: The `/browser` export only provides `detectMdx`. For formatting, use the WASM package above. ## Limitations Config file loading (`.mdx-formatter.json`) is not supported in browser/WASM mode because it requires Node.js file system access. Pass settings directly to the format function instead. ## Package Summary | Package | Provides | Environment | | --- | --- | --- | | `@takazudo/mdx-formatter` | `format()`, `formatFile()`, `checkFile()`, CLI | Node.js | | `@takazudo/mdx-formatter/browser` | `detectMdx()` | Browser-safe (no Node.js deps) | | `@takazudo/mdx-formatter-wasm` | `format()`, `format_with_defaults()` | Browser (WASM) | --- # Contribution > Source: /pj/mdx-formatter/docs/overview/contribution This project is a Markdown/MDX formatter. I originally built it because I needed this functionality for one of my projects. It started as a sub-package within that project, but since I use a documentation tool for all of my projects, I found myself copying and pasting the package into every project directory. So I published it to npm for consistency and easier maintenance. Most of the source code in this project was written by [Claude Code](https://claude.ai/). While this is publicly available as an open-source project, I'm not sure whether I'll actively maintain the package going forward. Feel free to fork it or open pull requests, but I can't guarantee that I'll be able to review or merge them. Since we're already in the AI era, I'd recommend forking the repo and making whatever tweaks you need on your own. -- [@Takazudo](https://x.com/Takazudo) --- # Docusaurus Admonitions > Source: /pj/mdx-formatter/docs/formatting/docusaurus-admonitions The formatter preserves [Docusaurus admonition](https://docusaurus.io/docs/markdown-features/admonitions) syntax (`:::note`, `:::tip`, `:::warning`, etc.) intact during formatting. This is controlled by the [preserveAdmonitions](/docs/options/preserve-admonitions) option. Without this feature, the `:::` syntax would be parsed as regular text and potentially broken by the formatter. Admonition syntax is preserved as-is. The formatter does not modify content inside admonitions. This works because the formatter's line-based approach does not modify content it does not specifically target. ## Rules - Admonition fences (`:::`) are preserved exactly as written - Content inside admonitions is not reformatted - Admonition titles (`:::tip[Title]`) are preserved - All Docusaurus admonition types are supported: `note`, `tip`, `info`, `warning`, `danger`, `caution` ## Examples ### Simple admonition ````markdown :::note This is a note admonition. ::: ```` Preserved as-is after formatting. ### Admonition with title ````markdown :::tip[Pro Tip] This is a professional tip with a custom title. ::: ```` Preserved as-is after formatting. ### Admonition with rich content ````markdown :::warning Be careful with this operation: - Item 1 - Item 2 ```js dangerousOperation(); ``` ::: ```` The entire block is preserved, including nested lists and code blocks. ### All admonition types ````markdown :::note Note content ::: :::tip Tip content ::: :::info Info content ::: :::warning Warning content ::: :::danger Danger content ::: :::caution Caution content ::: ```` All six types are preserved. --- # preserveAdmonitions > Source: /pj/mdx-formatter/docs/options/preserve-admonitions Keep Docusaurus admonitions (`:::note`, `:::tip`, `:::warning`, etc.) intact during formatting. The formatter preserves admonition syntax (`:::note`, `:::tip`, etc.) because its line-based approach does not modify content that isn't specifically targeted by a formatting rule. The admonition blocks pass through the AST processing via a dedicated plugin that recognizes them as container directives and preserves their structure. ## What is Docusaurus? [Docusaurus](https://docusaurus.io/) is a static site generator built by Meta for documentation websites. It uses MDX as its content format and provides a special "admonition" syntax using `:::` fences to render callout boxes (notes, tips, warnings, etc.) in documentation pages. This syntax is not part of standard markdown or MDX -- it is a Docusaurus-specific extension, so generic markdown formatters would break it. ## Options | Property | Type | Default | Description | | --------- | --------- | ------- | ------------------------ | | `enabled` | `boolean` | `true` | Enable/disable this rule | The `enabled` toggle exists for forward compatibility, but currently the preservation behavior is always active regardless of this setting. The admonition plugin runs during AST processing independently of this flag. ## Config ```json { "preserveAdmonitions": { "enabled": true } } ``` ## Examples ### Simple admonition The following admonition is preserved exactly as written: ````markdown :::note This is a note admonition. ::: ```` Stays as-is after formatting. ### Admonition with title ````markdown :::tip[Pro Tip] This is a professional tip with a custom title. ::: ```` Stays as-is after formatting. ### Admonition with rich content ````markdown :::warning Be careful with this operation: - Item 1 - Item 2 ```js dangerousOperation(); ``` ::: ```` The entire admonition block is preserved, including nested lists and code blocks. ### Multiple admonition types All Docusaurus admonition types are supported: ````markdown :::note Note content ::: :::tip Tip content ::: :::info Info content ::: :::caution Caution content ::: :::warning Warning content ::: :::danger Danger content ::: ```` --- # autoDetectIndent > Source: /pj/mdx-formatter/docs/options/auto-detect-indent Automatically detect indentation style (spaces vs tabs, indent size) from the file content and apply it consistently across all formatting rules. **Disabled by default.** When enabled, detected indentation overrides `indentSize` settings in other rules. ## Options | Property | Type | Default | Description | | -------------------- | --------- | --------- | ---------------------------------------------------------- | | `enabled` | `boolean` | `false` | Enable/disable auto-detection | | `fallbackIndentSize` | `number` | `2` | Default indent size if detection fails | | `fallbackIndentType` | `string` | `"space"` | Default indent type: `"space"` or `"tab"` | | `minConfidence` | `number` | `0.7` | Minimum confidence score (0-1) to use detected indentation | ## Config ```json { "autoDetectIndent": { "enabled": true, "fallbackIndentSize": 2, "fallbackIndentType": "space", "minConfidence": 0.7 } } ``` ## Examples ### 4-space indentation detected If a file primarily uses 4-space indentation, the formatter preserves it: Before: ````markdown ```` After (detected 4-space indent): ````markdown ```` ### Tab indentation detected If a file uses tabs, HTML block formatting also uses tabs: Before: ````markdown Term Definition ```` After (detected tab indent): ````markdown Term Definition ```` ### Fallback when detection fails If the file has no clear indentation pattern, the `fallbackIndentSize` and `fallbackIndentType` values are used (default: 2 spaces). --- # errorHandling > Source: /pj/mdx-formatter/docs/options/error-handling Configure how the formatter handles parsing errors. By default, the formatter returns the original content unchanged when a parsing error occurs. The main `format()` function wraps all processing in a try/catch block, so errors during formatting never propagate to callers. ## Options | Property | Type | Default | Description | | -------------- | --------- | ------- | ---------------------------------------------------------------- | | `throwOnError` | `boolean` | `false` | Intended to throw on errors, but see note below | ## Config ```json { "errorHandling": { "throwOnError": false } } ``` ## Current Behavior ### Default (throwOnError: false) When formatting encounters a parsing error, the original content is returned unchanged. This is the safe default for production use. Input: ````markdown # Heading Content without closing tag ```` Output: the original content is returned unchanged, no error thrown. ### throwOnError: true The behavior of `throwOnError` differs between the TypeScript and Rust implementations: - **Rust (napi/WASM/CLI)**: When `throwOnError` is `true` and parsing fails, the Rust formatter returns an error (or throws via napi). The Rust CLI uses `try_format()` for graceful error reporting. - **TypeScript**: The main `format()` function in `src/index.ts` catches all errors unconditionally and returns the original content. This means that with the TS engine, `throwOnError: true` does not propagate errors to the caller. ### CI Validation with checkFile() For CI pipelines where you need to detect formatting issues, use the `checkFile()` API instead of relying on `throwOnError`. The `checkFile()` function returns `true` if the file needs formatting (content differs after formatting), which you can use to fail a CI check: ```javascript const needsFormatting = await checkFile('path/to/file.mdx'); if (needsFormatting) { console.error('File needs formatting: path/to/file.mdx'); process.exit(1); } ``` This approach reliably detects files that haven't been formatted, which covers the most common CI use case. --- # v0.1.0 > Source: /pj/mdx-formatter/docs/changelog/v0.1.0 # v0.1.0 Released: 2026-02-07 ## Features - Add /version-increment skill for release workflow ([6e18b0d](https://github.com/Takazudo/mdx-formatter/commit/6e18b0dd968f44ce43d99e414853aaac74730649)) ## Bug Fixes - Avoid bare backtick-exclamation in version-increment skill ([8911989](https://github.com/Takazudo/mdx-formatter/commit/89119899a5fb70ad4947831033962bae1ad0c9f8)) - Exclude docs/ from Prettier check and fix package.json formatting ([2a6872d](https://github.com/Takazudo/mdx-formatter/commit/2a6872d4cd3067864e972e8be020a866d3b6d0d7)) - Fix bugs found in code review and remove dead code ([1785c0e](https://github.com/Takazudo/mdx-formatter/commit/1785c0ef5bc98c6c3c06e0ce5bab1d13fa54e828), [1b73e1a](https://github.com/Takazudo/mdx-formatter/commit/1b73e1a5b686b0b25c0d18c7ec0a10198825dff5)) ## Other Changes - Add Docusaurus doc site with Overview, Options, and Formatting sections ([736d03c](https://github.com/Takazudo/mdx-formatter/commit/736d03c3dc8c368f076460ada6fc91bd08e84bf4)) - Add Changelog category to doc site ([1a3a71b](https://github.com/Takazudo/mdx-formatter/commit/1a3a71b4c0ee6388db903f69a052da644bc48064)) - Add CLAUDE.md files for project, src, test, and doc directories ([4d031c3](https://github.com/Takazudo/mdx-formatter/commit/4d031c347ac1af26c10e8a7292a6f7ba962d3c63)) - Use npm/npx instead of pnpm in user-facing install examples ([ab36cc1](https://github.com/Takazudo/mdx-formatter/commit/ab36cc169df7abc8b8f1dc4dc3fe7033b290d56f)) - Slim down README, move detailed content to doc site ([102b914](https://github.com/Takazudo/mdx-formatter/commit/102b914a4a8120fd48d9cd299041caad478b48a9)) - Add complete options reference to README ([fd027f8](https://github.com/Takazudo/mdx-formatter/commit/fd027f80ce66ec168b2e41f535740503b227a5b0)) - Add Docusaurus build/deploy workflow and project config updates ([6ffb730](https://github.com/Takazudo/mdx-formatter/commit/6ffb730b237f8845302b632dab8e5f57c573b3b1)) - Consolidate test files by feature instead of development process ([a80e0e8](https://github.com/Takazudo/mdx-formatter/commit/a80e0e8c50aded7717ffc672d8decc9dc8be3fd6)) - Consolidate shared types, add access modifiers, clean up casts ([cc91904](https://github.com/Takazudo/mdx-formatter/commit/cc91904e2fbf3b9cc8c2c74b11d7c822d9ace1a1)) - Migrate entire codebase from JavaScript to TypeScript ([4afb5d8](https://github.com/Takazudo/mdx-formatter/commit/4afb5d823d511b308ca3bbb3769f803bf99e893b)) --- # v0.1.1 > Source: /pj/mdx-formatter/docs/changelog/v0.1.1 # v0.1.1 Released: 2026-02-07 ## Bug Fixes - Correct URLs and add icons to doc site links ([f2f2d20](https://github.com/Takazudo/mdx-formatter/commit/f2f2d208a5a4458b0ffc1cc4a112b619c85963ef)) - Format SKILL.md and add .claude/ to lint-staged ([f14e411](https://github.com/Takazudo/mdx-formatter/commit/f14e4117dae89ac5a696aa2d76ed7ccee4fd77d9)) --- # v0.1.2 > Source: /pj/mdx-formatter/docs/changelog/v0.1.2 # v0.1.2 Released: 2026-02-09 ## Bug Fixes - Add CI wait step to version-increment and cover doc/src in lint-staged ([709df1c](https://github.com/Takazudo/mdx-formatter/commit/709df1c71986ca0e4ed1be5d67d486af5d5dab98)) --- # v0.2.0 > Source: /pj/mdx-formatter/docs/changelog/v0.2.0 # v0.2.0 Released: 2026-02-09 ## Features - Add exclude config support for persistent file exclusion patterns ([44d5eed](https://github.com/Takazudo/mdx-formatter/commit/44d5eed7664dfac55898a24b7844af27b330d893)) - Add doc:start script to launch Docusaurus from root ([50cd24b](https://github.com/Takazudo/mdx-formatter/commit/50cd24b820d26a8cc6285b0ea8c03493a50fc5fb)) ## Other Changes - Consolidate config loading into loadFullConfig to avoid double file read ([7563f7a](https://github.com/Takazudo/mdx-formatter/commit/7563f7aa0c260f1bb8cdd08326715adf8d365b93)) - Improve API reference for --ignore and document exclude as CLI-only ([ea3979e](https://github.com/Takazudo/mdx-formatter/commit/ea3979e36099faffa873764f8d0a324184c962cc)) - Add build step and update pnpm to v10 ([b3a172e](https://github.com/Takazudo/mdx-formatter/commit/b3a172e25c543a09e3f3055ab95546cda3ad90b6)) - Add GitHub commit links to changelog entries ([1f6acf0](https://github.com/Takazudo/mdx-formatter/commit/1f6acf03b7cf8e261f8733bcad5fd71e64b76262)) - Add Contribution page to Overview section ([ea172f4](https://github.com/Takazudo/mdx-formatter/commit/ea172f4d2c68b3d7b9f91992a94758821edaf983)) --- # v0.2.1 > Source: /pj/mdx-formatter/docs/changelog/v0.2.1 # v0.2.1 Released: 2026-02-16 ## Bug Fixes - Fix broken sitemap links and upgrade DocsSitemap component ([ae5fd46](https://github.com/Takazudo/mdx-formatter/commit/ae5fd46)) - Generate data files before typecheck in build action ([58cac76](https://github.com/Takazudo/mdx-formatter/commit/58cac76)) - Use require() for auto-generated JSON in CategoryNav ([8d5fdd1](https://github.com/Takazudo/mdx-formatter/commit/8d5fdd1)) - Revert require() to import, generate data before CI checks ([5c0c3cf](https://github.com/Takazudo/mdx-formatter/commit/5c0c3cf)) ## Other Changes - Gitignore auto-generated doc data JSON files ([459c68d](https://github.com/Takazudo/mdx-formatter/commit/459c68d)) - Add b4push pre-push validation script and skill ([8a889e1](https://github.com/Takazudo/mdx-formatter/commit/8a889e1)) - Update commit message convention to scope prefixes ([9ead1ec](https://github.com/Takazudo/mdx-formatter/commit/9ead1ec)) --- # v0.3.0 > Source: /pj/mdx-formatter/docs/changelog/v0.3.0 # v0.3.0 Released: 2026-03-02 ## Bug Fixes - Fix YAML frontmatter handling: auto-quote unsafe values, prevent date corruption (2046086) - Fix review issues: block scalar guard, regex fix, new tests (61f5cf1) ## Other Changes - Add tests for YAML indicator quoting and fixUnsafeValues option (d1cca4c) - Rename project skills to l- prefix (e765918) --- # v0.4.0 > Source: /pj/mdx-formatter/docs/changelog/v0.4.0 # v0.4.0 Released: 2026-03-05 ## Features - Add `preserveTemplateLiteralIndent` option (c6b710d) - Preserve indentation inside template literal JSX attributes (9e00e3a) ## Other Changes - Extract DRY helper, tighten backtick heuristic, add edge-case tests (9b08b3a) - Document date quote-stripping caveat in YAML formatting (40c6b43) --- # v0.4.1 > Source: /pj/mdx-formatter/docs/changelog/v0.4.1 # v0.4.1 Released: 2026-03-16 ## Bug Fixes - Fix table breakage caused by inline JSX elements in table cells (60805d5) --- # v0.4.2 > Source: /pj/mdx-formatter/docs/changelog/v0.4.2 # v0.4.2 Released: 2026-03-16 ## Bug Fixes - Fix closing tag duplication and trailing blank line oscillation (`3ff1fea`) - Address review findings in hybrid-formatter.ts (`0b71512`) ## Other Changes - Test: improve test quality in hybrid-formatter.test.ts (`8009d70`) - Docs: add admonition usage notes and cautions for block JSX options (`05886db`) - Claude: Add GitHub release creation to version increment skill (`d4e40ce`) --- # v0.4.3 > Source: /pj/mdx-formatter/docs/changelog/v0.4.3 # v0.4.3 Released: 2026-03-16 ## Bug Fixes - fix: harden opening tag detection and document convergence loop (e0a31ce) - fix: resolve multi-line JSX tag oscillation in block components (afe2fbe) - fix: make formatter idempotent in a single format() call (eb7cfe4) ## Other Changes - [claude] Fix macOS-incompatible awk in version increment skill (b5f8727) --- # Claude > Source: /pj/mdx-formatter/docs/claude Claude Code configuration reference. ## Resources --- # v1.0.0 > Source: /pj/mdx-formatter/docs/changelog/v1.0.0 # v1.0.0 Released: 2026-03-23 ## Breaking Changes - Switch to Rust-only formatting engine — the TypeScript formatter (MdxFormatter class, html-block-formatter, indent-detector) has been removed entirely (2fc80ab) - Rust napi native module is now required — no fallback to TypeScript if unavailable - Removed production dependencies: unified, remark-parse, remark-mdx, remark-frontmatter, unist-util-visit, prettier (moved to devDependency) - Removed exported types: FormatterOperation, MdxJsxElement, MdxJsxAttribute, IndentDetectorLike, PositionMapEntry, and AST re-exports (Node, Parent, Position, Root) - CI now requires Rust toolchain to build the napi module before running tests ## Features - Rust engine with 3-7x performance improvement over TypeScript (bf95183) - Standalone Rust CLI binary (`mdx-formatter`) with `--write`, `--check`, `--config` (crates/mdx-formatter-cli) - WASM support for browser environments via `mdx-formatter-wasm` crate (041b4df) - Interactive playground on doc site using WASM engine (041b4df) - napi-rs cross-platform binaries: macOS (arm64, x64), Linux (x64), Windows (x64) - Config file loading in Rust: `.mdx-formatter.json`, `package.json` key, 3-layer merge - All 10 formatting settings fully implemented in Rust with serde deserialization - 342 Rust tests + 85 passthrough tests confirming feature parity ## Bug Fixes - Add empty frontmatter guard to prevent reversed-range corruption in YAML formatting (bf95183) - Use `settings.indentJsxContent.indentSize` instead of hardcoded 2-space indent (bf95183) - Add JSON config shape validation — reject arrays/primitives (bf95183) - Fix all numeric fallbacks: `||` → `??` to prevent overriding explicit 0 values (14f48e8) - Rust CLI: use `try_format()` instead of `format()` to avoid panic on parse failure (bf95183) - Rust: deduplicate `parse()`/`try_parse()` — `parse()` now delegates to `try_parse()` (bf95183) - Rust: avoid String allocation in block_components/container_components lookup (bf95183) - Fix napi CI: replace deprecated macos-13 with macos-latest (e6f9d58) ## Other Changes - Remove dead code: originalContent, positionMap, buildPositionMap(), getLineAtPosition(), getEnabledRules() (14f48e8) - Remove 6 unused type exports and unreachable estree block (14f48e8, 83ca23b) - Remove 4 unused production dependencies: remark, remark-directive, remark-gfm, remark-stringify (d9068bb) - Move HTML_ELEMENTS Set to module-level constant for performance (bf95183) - CI: add timeout-minutes and concurrency group (d9068bb) - CI: add Rust toolchain and wasm-pack build steps (d778ba2, 29dca44) - Fix CLAUDE.md: husky → lefthook (d9068bb) - Update all docs to reflect Rust-only engine (87bf605, 7a022df) - Doc site: playground with WASM engine, settings panel, JSX text inputs (6100b54, 8c25ed7) - Doc site versioning: snapshot v0.x docs for TypeScript engine era - Reorder header navigation --- # l-design-system > Source: /pj/mdx-formatter/docs/claude-skills/l-design-system ## File Structure ``` l-design-system/ ├── SKILL.md └── references/ └── tokens.md ``` - [references/tokens.md](./ref-tokens) # Doc Site Design System Tight token strategy: Tailwind v4 with NO default theme. Only project tokens via `@theme` in `doc/src/styles/global.css`. Never use raw pixel/rem values or default Tailwind classes like `h-3`, `w-4`, `text-sm` — they don't exist. For full token values, read `references/tokens.md`. ## Colors (semantic — auto light/dark) | Token | Use | | ----------------------------------------- | -------------------------------------------------------------------- | | `bg` / `fg` | Page background / main text | | `surface` | Panels, cards, elevated backgrounds | | `muted` | Borders, secondary text, disabled. Use `muted/30` for subtle borders | | `accent` / `accent-hover` | Links, CTAs, focus rings, hover | | `code-bg` / `code-fg` | Code blocks and inline code | | `success` / `danger` / `warning` / `info` | Status colors | ## Spacing **Horizontal (hsp):** `2xs`(2px) `xs`(6px) `sm`(8px) `md`(12px) `lg`(16px) `xl`(24px) `2xl`(32px) **Vertical (vsp):** `2xs`(7px) `xs`(14px) `sm`(20px) `md`(24px) `lg`(28px) `xl`(40px) `2xl`(56px) Usage: `px-hsp-md`, `py-vsp-sm`, `gap-hsp-xs`, `gap-vsp-md`, `gap-x-hsp-md`, `gap-y-vsp-xs` ## Typography | Token | Size | Use | | ----------------- | ------ | --------------------------------- | | `text-caption` | 14px | Labels, small UI, buttons, inputs | | `text-small` | 16px | Nav, table headers, code blocks | | `text-body` | 19.2px | Paragraphs, main content | | `text-subheading` | 22.4px | Card titles, h3 | | `text-heading` | 48px | Page headings | | `text-display` | 60px | Hero text | **Fonts:** `font-sans` (Noto Sans JP), `font-futura` (headings/nav), `font-mono` (code) **Line heights:** `leading-tight`(1.25) `leading-snug`(1.375) `leading-normal`(1.5) `leading-relaxed`(1.625) **Weights:** `font-normal`(400) `font-medium`(500) `font-semibold`(600) `font-bold`(700) ## Radius & Breakpoints **Radius:** `rounded`(4px) `rounded-lg`(8px) `rounded-full`(pill) **Breakpoints:** `sm:`(640px) `lg:`(1024px, sidebar) `xl:`(1280px) ## Common Patterns ``` // Button (primary) rounded-lg bg-accent px-hsp-xl py-hsp-xs text-caption font-semibold text-bg // Panel/card rounded-lg border border-muted/30 bg-surface p-hsp-md // Input field rounded border border-muted/30 bg-code-bg px-hsp-xs py-0.5 text-caption text-fg // Label text-caption font-semibold text-muted // Code textarea bg-code-bg p-hsp-md font-mono text-caption leading-relaxed text-code-fg // Section gap gap-vsp-sm (between sections), gap-vsp-2xs (within section) ``` ## SVG Icons Always use explicit `width`/`height` attributes — Tailwind size classes (`h-N`/`w-N`) don't exist. Add `shrink-0` in flex. ```tsx ``` ## Key Source File All tokens are defined in `doc/src/styles/global.css`. When in doubt, read it. --- # l-version-increment > Source: /pj/mdx-formatter/docs/claude-skills/l-version-increment # /l-version-increment Bump the version of `@takazudo/mdx-formatter`, generate a changelog doc page, commit, tag, and publish to npm. ## Preconditions Before doing anything else, verify ALL of the following. If any check fails, stop and tell the user. 1. Current branch is `main` 2. Working tree is clean (`git status --porcelain` returns empty) 3. At least one `v*` tag exists (`git tag -l 'v*'`). If no tag exists, tell the user to create the initial tag first (e.g. `git tag v0.1.0 && git push --tags`). Find the latest version tag: ```bash git tag -l 'v*' --sort=-v:refname | head -1 ``` ## Analyze changes since last tag Run: ```bash git log ..HEAD --oneline ``` and ```bash git diff ..HEAD --stat ``` Categorize each commit by its conventional-commit prefix: - **Breaking Changes**: commits with an exclamation mark suffix (e.g. `feat!:`) or BREAKING CHANGE in body - **Features**: `feat:` prefix - **Bug Fixes**: `fix:` prefix - **Other Changes**: everything else (`docs:`, `chore:`, `refactor:`, `ci:`, `test:`, `style:`, `perf:`, etc.) ## Propose version bump Based on the changes: - If there are breaking changes → propose **major** bump - If there are features (no breaking) → propose **minor** bump - Otherwise → propose **patch** bump If the user passed an argument (`major`, `minor`, or `patch`), use that directly instead of proposing. Present the proposal to the user: ``` Proposed bump: {current} → {new} ({type}) Breaking Changes: - description (hash) Features: - description (hash) Bug Fixes: - description (hash) Other Changes: - description (hash) ``` Only show sections that have entries. **Wait for user confirmation before proceeding.** ## Create changelog doc Create `doc/src/content/docs/changelog/v{VERSION}.mdx` with this format: ```mdx --- title: 'v{VERSION}' sidebar_position: { computed } --- # v{VERSION} Released: {YYYY-MM-DD} ## Breaking Changes - Description (commit-hash) ## Features - Description (commit-hash) ## Bug Fixes - Description (commit-hash) ## Other Changes - Description (commit-hash) ``` Rules: - Only include sections that have entries - `sidebar_position` = `MAJOR * 1000 + MINOR * 100 + PATCH` — the changelog category uses `sortOrder: "desc"`, so higher values appear first (newer versions on top) - Use today's date for the release date - Each entry should be the commit subject with the short hash in parentheses ## Commit changelog ```bash git add doc/src/content/docs/changelog/v{VERSION}.mdx git commit -m "docs: Add changelog for v{VERSION}" ``` ## Bump version in package.json Update the `version` field in `package.json` to the new version (without the `v` prefix). ```bash git add package.json git commit -m "chore: Bump version to v{VERSION}" ``` ## Build and test Run the full build and test suite to make sure everything is good: ```bash pnpm build && pnpm test ``` If anything fails, stop and tell the user. Do not proceed with tagging or publishing. ## Push and wait for CI Push the commits first (without the tag) and wait for CI to pass: ```bash git push ``` Then check CI status with `gh run list --branch main --limit 2`. Poll every 30 seconds until both CI and Production Deploy show `completed success`. If CI fails, fix the issue, commit, and push again before proceeding. **Do not tag or publish until CI is green.** ## Tag, push tag, and create GitHub release **Ask the user for confirmation before tagging.** ```bash git tag v{VERSION} git push --tags ``` After pushing the tag, create a GitHub release using the changelog content (with YAML frontmatter and `# v{VERSION}` heading stripped, since the release title already shows the version): ```bash NOTES=$(sed -n '/^Released:/,$ p' doc/src/content/docs/changelog/v{VERSION}.mdx) gh release create v{VERSION} --title "v{VERSION}" --notes "$NOTES" ``` ## Publish to npm **Ask the user for confirmation before publishing.** The user will run `npm publish` manually (it requires browser-based 2FA). Tell the user to run: ```bash npm publish ``` After publishing, verify the package page: `https://www.npmjs.com/package/@takazudo/mdx-formatter` --- # l-version-next > Source: /pj/mdx-formatter/docs/claude-skills/l-version-next # /l-version-next Publish a prerelease version of `@takazudo/mdx-formatter` under the npm `next` dist-tag. Users install with `npm install @takazudo/mdx-formatter@next`. ## Version Format `X.Y.Z-next.N` where: - `X.Y.Z` is the target stable version (e.g., `0.5.0`) - `N` is an incrementing prerelease number (1, 2, 3, ...) ## Preconditions Before doing anything else, verify ALL of the following. If any check fails, stop and tell the user. 1. Working tree is clean (`git status --porcelain` returns empty) 2. `gh` CLI is authenticated ## Determine Version Read the current version from `package.json`. ### If current version is already a `-next.N` prerelease: Increment the prerelease number: - `0.5.0-next.1` → `0.5.0-next.2` - `0.5.0-next.2` → `0.5.0-next.3` ### If current version is a stable release (e.g., `0.4.3`): Determine the base version for the next release: - If the user passed `major`: bump major (e.g., `0.4.3` → `1.0.0-next.1`) - If the user passed `minor` or no argument: bump minor (e.g., `0.4.3` → `0.5.0-next.1`) - If the user passed `patch`: bump patch (e.g., `0.4.3` → `0.4.4-next.1`) Present the version to the user and **wait for confirmation**. ## Build and Test ```bash pnpm build && pnpm test ``` If anything fails, stop and tell the user. Do not proceed. ## Update Version Update the `version` field in `package.json` to the new prerelease version. ```bash git add package.json git commit -m "chore: Bump version to v{VERSION}" ``` ## Push ```bash git push ``` ## Publish with `next` Tag **Ask the user for confirmation before publishing.** The user will run `npm publish --tag next` manually (it requires browser-based 2FA). Tell the user to run: ```bash npm publish --tag next ``` After publishing, verify: ```bash npm view @takazudo/mdx-formatter dist-tags ``` This should show both `latest` (stable) and `next` (prerelease) tags. ## Notes - No changelog doc is created for prerelease versions - No git tag is created for prerelease versions - No GitHub release is created for prerelease versions - To promote a next version to stable, use `/l-version-promote` - To install the next version: `npm install @takazudo/mdx-formatter@next` --- # l-version-promote > Source: /pj/mdx-formatter/docs/claude-skills/l-version-promote # /l-version-promote Promote a `@takazudo/mdx-formatter@next` prerelease to a stable release. ## Preconditions 1. Current branch is `main` 2. Working tree is clean 3. Current version in `package.json` is a `-next.N` prerelease (e.g., `0.5.0-next.3`) If the current version is NOT a prerelease, stop and tell the user. ## Determine Stable Version Strip the `-next.N` suffix: - `0.5.0-next.3` → `0.5.0` - `1.0.0-next.1` → `1.0.0` Present to the user and **wait for confirmation**. ## Create Changelog Doc Create `doc/src/content/docs/changelog/v{VERSION}.mdx` using the same format as `/l-version-increment`. Analyze all commits since the last stable tag (`git tag -l 'v*' --sort=-v:refname` excluding prerelease tags) and categorize them. Rules: - `sidebar_position` = `MAJOR * 1000 + MINOR * 100 + PATCH` - Include `title` in frontmatter - Only include sections that have entries ```bash git add doc/src/content/docs/changelog/v{VERSION}.mdx git commit -m "docs: Add changelog for v{VERSION}" ``` ## Update Version Update `package.json` version to the stable version (without `-next.N`). ```bash git add package.json git commit -m "chore: Bump version to v{VERSION}" ``` ## Build and Test ```bash pnpm build && pnpm test ``` If anything fails, stop. ## Push and Wait for CI ```bash git push ``` Wait for CI to pass. ## Tag and Release **Ask for confirmation.** ```bash git tag v{VERSION} git push --tags ``` Create GitHub release: ```bash NOTES=$(sed -n '/^Released:/,$ p' doc/src/content/docs/changelog/v{VERSION}.mdx) gh release create v{VERSION} --title "v{VERSION}" --notes "$NOTES" ``` ## Publish to npm (stable) **Ask for confirmation.** The user runs manually: ```bash npm publish ``` This publishes under the `latest` tag (default). The `next` tag automatically becomes stale — users who had installed `@next` will stay on the prerelease version until they explicitly update. After publishing, verify: ```bash npm view @takazudo/mdx-formatter dist-tags ``` Both `latest` and `next` should show the correct versions.