docs/solutions/logic-errors/2026-04-06-footnote-duplicate-definitions-must-keep-first-definition-canonical.md
Footnote duplicate definitions were only half-modeled.
The registry already sorted definitions by document order, so the first definition quietly won for preview and navigation. But later duplicate definitions still rendered like valid definitions, including backlink behavior, which made the UI lie about which definition references actually resolved to.
Make the first definition canonical and treat later duplicates as explicit invalid siblings.
At the package layer:
api.footnote.duplicateDefinitions({ identifier }) returns later duplicate
definitions onlyapi.footnote.isDuplicateDefinition({ path }) answers whether a specific
definition is one of those later duplicatestf.footnote.normalizeDuplicateDefinition({ path, identifier? }) renumbers a
later duplicate definition to an explicit new identifierAt the app layer:
That keeps the real resolution model visible instead of hiding it behind a warning-only surface.
References for one identifier cannot meaningfully belong to multiple different definition blocks at once.
So the runtime needs one winner.
The registry already had the right implicit winner: first definition in document order. The fix was to stop letting the rest of the system act like all duplicates were equivalent.
Once the package exposes canonical-vs-duplicate semantics directly, the UI can render later duplicates as invalid and the repair flow can be explicit instead of magical.
The follow-up UI fix mattered too: duplicate-warning chrome cannot rely on
useNodePath when sibling edits can shift the surviving definition to a new
path. useNodePath does not update for that case. The warning state needs to
derive from the current editor path via editor.api.findPath(element) inside a
live selector.
These checks passed:
pnpm install
pnpm brl
bun test packages/footnote/src/lib/BaseFootnotePlugins.spec.ts packages/footnote/src/lib/queries/footnoteRegistry.spec.ts packages/footnote/src/lib/transforms/insertFootnote.spec.ts apps/www/src/registry/ui/footnote-node.spec.tsx
pnpm turbo build --filter=./packages/footnote --filter=./apps/www
pnpm turbo typecheck --filter=./packages/footnote --filter=./apps/www
pnpm lint:fix
Browser verification on http://localhost:3000/docs/footnote:
Renumber to [^4] actionuseNodePath for validity state that must survive sibling
insertions, removals, or merges; derive current path from the live editor
state instead