docs/solutions/logic-errors/2026-04-17-create-rule-factory-object-defaults-must-reach-runtime-input.md
createRuleFactory accepted object-config defaults like marker: '>', but it
did not pass those defaults into the runtime input used by match, enabled,
and other factory-resolved values.
That broke real editor flows using object-config factories, even when the rule looked correct in code.
/blocks/basic-blocks-demo, creating a fresh paragraph and typing >
could leave a plain paragraph with literal > text.enabled === truegetBlockStartText() === '>'resolve() === undefinedinsertBreak()insertText('>')insertText(' ')Merge object-config defaults into the runtime factory input before resolving factory values.
The buggy shape only merged options and context:
getMergedInput(context, options)
That dropped defaults defined on the factory config object itself.
The fix introduces factoryOptions and feeds that merged object into runtime
resolution:
const factoryOptions =
typeof configOrBuilder === 'function'
? options
: { ...(configOrBuilder as Record<string, unknown>), ...options };
const getFactoryInput = <TContext extends object>(context: TContext) =>
getMergedInput(context, factoryOptions);
Then use getFactoryInput(...) everywhere factory values are resolved.
Object-config factories have two sources of data:
The old implementation only passed the second one through. Once the default
config values reach the runtime input again, matchers like
({ marker }) => marker work in real editor flows instead of silently
resolving to undefined.
marker, variant, or
fence, verify that field exists in the runtime input, not just in the type
signature.insertBreak() or another state-changing transform.