docs/solutions/best-practices/block-fence-input-rules-should-split-fence-matching-from-feature-apply.md
Some input rules are not really "insert text" or "insert break" rules in spirit. They are block fence rules:
Block math and fenced code blocks both followed that shape, but the shared part was buried inside separate package rules.
Add a narrow core primitive for block fences:
createBlockFenceInputRule({
fence: '```',
on: 'match',
block: KEYS.p,
isBlocked,
apply,
});
on expresses the real DX choice:
on: 'match' fires when the last delimiter makes the fence completeon: 'break' fires when the completed fence is followed by EnterCore owns the matcher:
Packages still own semantics:
It extracts the repeated hot-path matcher without flattening feature ownership into core.
@platejs/code-block can switch between typed completion and Enter-based
completion with one option.@platejs/math can use the same primitive for $$ without carrying its own
insert-break matcher.insertText vs insertBreak.on: 'match' | 'break' over names that leak
implementation details.