umd 0.2.0

Universal Markdown - A post-Markdown superset with semantic HTML and extensible syntax
Documentation
# 既知の問題(実装と設計意図の乖離・未解決の制限)

**最終更新**: 2026年9月10日

このドキュメントは、UMDの実装のうち「設計意図と異なる既知のバグ」および
「意図的に未対応としている制限事項」をまとめたものです。個々の機能仕様は
[runtime-features.md](runtime-features.md)・[spec/](spec/)以下の各仕様書を
参照してください。ここでは仕様の説明は行わず、既知の問題・制限とその理由・
回避策・修正方針のみを扱います。

## 目次

- [プラグイン構文内の脚注参照は非対応(制限事項)]#プラグイン構文内の脚注参照は非対応制限事項
- [プラグイン構文の深いネスト・content/argsの扱い]#プラグイン構文の深いネストcontentargsの扱い
- [見出しID処理が設計意図と異なる(未修正バグ)]#見出しid処理が設計意図と異なる未修正バグ

---

## プラグイン構文内の脚注参照は非対応(制限事項)

インラインプラグイン(`&function(args){content};`)・ブロックプラグイン
(`@function(args){{content}}`)の`content`内に書かれた`[^N]`は、脚注として
機能しません。

```markdown
&color(red){text[^1]};

[^1]: この定義は出力に含まれません
```

**理由**: プラグイン構文の`content`はcomrakによるMarkdown解析の対象外です
(Markdown解析の前に一時マーカーへ変換され、解析後に生テキストとして復元
される実装のため)。comrakの脚注採番はパース1回分のAST全体に対する単一パス
処理で、複数回のパース呼び出しをまたいで採番を共有する手段がなく、この
制限を解消するには comrak にパッチを当てる規模の実装が必要になるため、
**現時点では**対応しない方針としています(将来的にcomrak依存を外す、または
forkする実装に移行する際、あるいはプラグイン`content`を通常のインライン
Markdown解析パイプラインに乗せる、より根本的な設計変更を検討する際に
合わせて再評価します)。

`[^N]`の定義がプラグイン構文内の参照からしか参照されていない場合、その
定義自体が出力から欠落します(comrakは参照が一つもない脚注定義をレンダリング
しません)。これにより、文書内の他の(プラグイン外の)脚注の通し番号が、
ラベルの見た目の数字とずれる場合があります。

**アンカーの安定性について**: 脚注のアンカーID・表示番号は文書中の出現順に
よって決まるため、脚注の追加・削除・上記の欠落によって既存の脚注の番号や
アンカー(`#fn-*`)は変わり得ます。UMDは脚注アンカーへの外部からの
恒久的なディープリンクを想定していません(ページ内リンクとしての利用を
前提としています)。

## プラグイン構文の深いネスト・content/argsの扱い

インラインプラグイン(`&function(args){content};`)・ブロックプラグイン
(`@function(args){{content}}`)を、別のプラグインの`content`や`args`の
中に書いた場合の挙動です。

```markdown
&color(blue){外側 &abbr(text){中側 &size(lg){内側};};};
```

**2026年9月に修正済みの問題**: 上記のように3階層以上ネストした場合や、
引数にMarkdownリンクのような未エスケープの`)`が含まれる場合
(`&abbr([HTML](exp.html)){Hyper Text Markup Language};`)、以前の実装
(`protect_inline_plugins`/`protect_block_plugins`が複数の正規表現を
文字列全体に順次適用する方式だったもの)では、マーカー文字列が破損し、
`};`のような断片が出力に漏れ出したり、該当箇所自体が全くプラグインとして
認識されなかったりしました。ブロックプラグインの`{{...}}`記法でも、
兄弟または入れ子のブロックプラグインが同じ`{{...}}`内にあると、片方の
lazy正規表現がもう片方の`}}`を自分の閉じ括弧と誤認識し、両方が壊れる
症状がありました。現在は`parse_inline_plugin_at`/`parse_block_plugin_at`
(`src/extensions/plugins/inline.rs`/`block.rs`)による、開き括弧・閉じ
括弧の深さを追跡する左から右への単一スキャン実装(共有ヘルパー
`scan_balanced`/`scan_balanced_double`、`src/extensions/plugins/mod.rs`)
に置き換わっており、上記のようなネストや括弧を含む引数は正しく1つの
プラグイン呼び出しとして認識されます。詳細はCHANGELOGを参照してください。

**現在も残る制限(未修正)**:

1. **`content`/`args`はMarkdownとして再解析されません**。`&color(red){text**bold**here};`
   のような入力で、`**bold**`は`<strong>`にならず、生テキストの`**bold**`の
   ままHTMLに出力されます(脚注参照`[^N]`が機能しない件も同じ原因で、
   上記の「プラグイン構文内の脚注参照は非対応」を参照)。これは実装が
   「マーカーへ保護→文字列置換で復元」という設計であり、`content`/`args`が
   一度もcomrakのインライン解析パイプラインを通らないためです。根本的な
   解決にはAST操作ベースの設計への作り直しが必要な規模の変更であり、
   長期課題として扱っています([PLAN.md]../PLAN.mdの「標準インライン
   プラグインのcontentへのMarkdown解釈適用」参照)。

2. **`content`内にネストした別プラグイン呼び出しは、自動では展開されない
   場合があります**。1点目と同じ理由で`content`/`args`はプラグインとしても
   再解析されないのが原則ですが、`inline.rs`には例外的に「二次スイープ」
   (`prepare`/`expand`)という仕組みがあり、標準プラグインの呼び出しが
   別の標準プラグインの`content`に1段だけネストしているケースを、復元後の
   HTML文字列に対してもう一度正規表現で検出し直すことで救済しています。
   ただし、この二次スイープ自身が使う正規表現(`INLINE_COLOR`等)も、
   保護処理と同様に「最初の閉じ括弧で止まる」設計のままであるため、
   2段以上ネストした場合(前掲の3階層の例など)は、二次スイープでも
   完全には展開されません。2026年9月の修正が解消したのは「マーカーが
   破損して出力自体が壊れる」症状であり、このケースでの現在の結果は
   「内側の`&abbr(...)...;`が実HTMLに展開されず、生テキストのまま残る」
   (壊れた/欠落したマークアップではなく、見えるだけのプラグイン構文)
   という、1点目と同種の“未解決”に後退する形になります。ブロック
   プラグインには二次スイープの仕組み自体がないため、`{{content}}`内の
   ネストしたブロックプラグインは常にこの状態です。

いずれも、`content`/`args`を独立した文字列として保護・復元する現行の
実装方針そのものに起因する制限です。1点目に書いたAST操作ベースの再設計を
行う際に、あわせて解消される見込みです。

## 見出しID処理が設計意図と異なる(未修正バグ)

**設計意図**: `id` は見出しタグ(`<h1>`〜`<h6>`)自身の属性として付与される
(別途空の`<a>`要素は生成しない)。`h-` プレフィックスは自動採番時のみ、他の
見出しIDとの衝突を防ぐために付与するもので、ユーザーが `{#custom-id}` を
明示的に指定した場合はそのIDをプレフィックスなしでそのまま使う。

この設計意図は、UMDの前身であるLukiWiki
([app/LukiWiki](https://github.com/logue/LukiWiki/tree/master/app/LukiWiki)。
初期の[LukiWiki-core](https://github.com/logue/LukiWiki-core)はファサード設計が
うまくいかず、現行ではこちらが最新実装)の`Element/Heading.php`・
`Rules/HeadingAnchor.php`の挙動と一致する。同実装は`id`を`<hN>`タグ自身の
属性として付与し(`$this->wrap(..., 'h'.$this->level, ['id' => $id], false)`)、
固定アンカー指定時は自動採番ID(`content_{ページID}_{連番}`)を丸ごと固定値に
置き換える(`$id = empty($fixed_anchor) ? $this->id : $fixed_anchor;`)。

なお、LukiWikiの固定アンカー構文は`[#id]`(角括弧)だが、UMDでは`{#custom-id}`
(波括弧)に変更している。これは`[^1]`のような脚注参照記法と見た目・構文的に
紛らわしく、見出し内に脚注を含めたいケースで処理・視認性の両面で混乱を招き
やすいための意図的な変更。

LukiWikiの自動採番(`content_{ページID}_{連番}`)はレンダリング毎の一時的な
フォールバックに過ぎず、本来はページ保存時に`HeadingAnchor::set()`で固定アンカー
(`[#` + ランダム7文字 + `]`)を本文へ直接書き込み、以後は永続的なIDとして
`get()`側で読み取る、という2段構えの設計が意図されていた(保存という状態を
持つWikiシステムならではの、編集を跨いでもIDが変わらないようにするための工夫)。
このアンカー書き込み処理は未完成のまま終わっている。umd-coreはページ保存という
概念を持たないステートレスなパーサーライブラリのため、この「保存時に本文へ
焼き込む」という選択肢自体が存在せず、レンダリングのたびに出現順で決定的に
連番を振る(=同じ入力なら常に同じ`h-N`になる)方式で十分という、前提の違いに
基づく単純化になっている。

```markdown
# 見出し1
## 見出し2
### アンカーつき見出し {#anchor}
## 見出し3
```

意図する出力:

```html
<h1 id="h-1">見出し1</h1>
<h2 id="h-2">見出し2</h2>
<h3 id="anchor">アンカーつき見出し</h3>
<h2 id="h-3">見出し3</h2>
```

(`h-3` は実装の数え方次第で `h-4` でも構わない)

**実際の実装**(`src/extensions/conflict_resolver.rs`)は、この意図と以下の
点で異なります。

1. `id` を `<hN>` タグ自体の属性にはせず、常に見出しテキストの先頭に空の
   `<a href="#..." aria-hidden="true" class="anchor" id="..."></a>` 要素を
   挿入する実装になっている
2. 自動採番かユーザー指定かに関わらず、常に `h-` プレフィックスを付与して
   しまう(`{#anchor}` を指定しても `id="anchor"` にはならず `id="h-anchor"`
   になる)
3. 上記の実装はHTML変換後の見出しに対して
   `<h([1-6])>([^<]+)</h([1-6])>`(`[^<]+`が`<`を一切含まないことを要求)と
   いう正規表現でマッチさせる方式のため、見出し内に `<strong>`・`<a>` など
   インライン記法由来の入れ子タグがあると、この処理自体が丸ごとマッチせず
   スキップされる。例えば `## **見出し**テキスト {#anchor}` は
   `<h2><strong>見出し</strong>テキスト</h2>` のままアンカーが一切付与され
   ない
4. さらにこの処理は「正規表現がマッチした回数」を見出し番号のカウンターと
   して使っているため、3でスキップされた見出しの分だけ、それ以降の見出し
   全ての採番が1つずつずれ、`{#custom-id}` の指定が別の見出しに付いてしまう
   ことがある
5. 同種のカウンターのずれは生Markdown側の抽出処理にも存在する。こちらは
   「`{#id}` を持つ見出しが見つかった回数」を見出し番号として使っているため、
   `{#id}` を持たない見出しが `{#id}` 付き見出しより前にある場合、インライン
   記法の有無に関わらずそこでも採番がずれる

つまり1・2は常に発生する(意図通りの出力になるケースが存在しない)、3〜5は
条件付きでさらにアンカーの完全消失や後続見出しの採番ずれを引き起こす、という
状態です。

**回避策**: `id`/`href` の実際の値には常に `h-` プレフィックスが付く前提で
外部リンクを作成してください。また、カスタムIDを使う文書では見出しにインライン
記法(強調・リンク等)を含めない運用を推奨します。

**根本修正の方向性**: `id` を `<hN>` タグ自体の属性として注入する実装に変更
すれば(`<h([1-6])>` という開きタグのみを対象にすれば済み、見出し内容を
キャプチャする必要がなくなる)、3の問題は同時に解消されます。加えて、
自動採番時のみ `h-` プレフィックスを付与するよう分岐し、見出しカウンターを
正規表現マッチ回数・`{#id}`マッチ回数ではなく実際の見出し出現順(全見出しを
対象)に基づくものへ作り直す必要があります。未修正の既知の問題として記録し、
対応は今後の課題とします。