fix(footnotes): address PR #232 review — fragment-safe canonicalization, plugin placement parity, dead-code removal (#228)
Must-fix: - Move canonicalizeFootnotes OUT of parseProsemirrorContent. It now runs only on FULL writes (createPage, updatePageContent operation==='replace'), never on an append/prepend fragment (a fragment would lose definition-only footnotes or synthesize a bogus empty list). Add a server binding spec. - Match the live plugin's list PLACEMENT: a single already-canonical footnotesList is left exactly where it sits (the plugin never repositions a sole correct list), so the first write no longer reorders content that follows the list. Applied to BOTH the editor-ext copy and the MCP mirror; pinned by a shared golden corpus case with content after the list. - Fix MCP tool count 38 -> 39 (README x3, AGENTS.md) and the transformJs param help (add canonicalizeFootnotes/insertInlineFootnote). Simplifications: - Remove the dead duplicate re-id mechanism (deriveFootnoteId/suffix/occurrence) from the PURE canonicalizer in both copies — references are never renamed, so the derived ids were never requested; first-wins-drop is the real behaviour. This also makes the editor-ext footnote-util note about "no cross-package copy" true again. - Remove the sentinel round-trip in insertInlineFootnote: a generalized insertNodesAfterAnchor core inserts the footnoteReference node directly. - Drop the redundant per-definition deep clone in step 4 (shallow id-normalizing copy; out is already deep-cloned). Docs / architecture: - Correct the editor-ext copy's "It exists because…" header to its real consumers (server import, page.service create/update, client paste). - Note markdownToProseMirror reuse for create/update comment in collaboration.ts. - A: shared golden JSON corpus exercised by BOTH the editor-ext copy and the MCP mirror (footnote-corpus.ts / .mjs) so "the two copies behave identically" is checkable. - C: split the MCP canonicalizer into a pure mirror + footnote-authoring.ts. - B: import services persist via a different path, so left one-line consolidation comments at the call sites rather than folding (does not fall out cleanly). Tests: insertFootnote wrapper guards + docmost_transform dryRun auto-canonicalize (MCP mock), page.service create/update + append/prepend binding (server jest), shared corpus incl. nested-container reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -504,6 +504,9 @@ export class FileImportTaskService {
|
||||
// zip-imported page's footnotes are reference-ordered, deduped, and
|
||||
// orphan-free like the editor's invariant (issue #228). Pure +
|
||||
// idempotent + shape-safe; a footnote-free doc is unchanged.
|
||||
// (Future consolidation, architecture B: like import.service, this
|
||||
// path persists directly rather than via PageService — a shared
|
||||
// "prepare JSON for persist" helper would centralize this call.)
|
||||
const prosemirrorJson = canonicalizeFootnotes(extractedJson);
|
||||
|
||||
const insertablePage: InsertablePage = {
|
||||
|
||||
@@ -91,6 +91,10 @@ export class ImportService {
|
||||
// retains orphan definitions, and is not deduped. Canonicalize before
|
||||
// persisting so the stored page matches the editor's invariant (issue #228).
|
||||
// Pure + idempotent + shape-safe: a doc with no footnotes is unchanged.
|
||||
// (Future consolidation, architecture B: this import path persists directly
|
||||
// via pageRepo.insertPage rather than through PageService.createPage, so the
|
||||
// canonicalize call lives here; folding both into one "prepare JSON for
|
||||
// persist" helper is a sensible follow-up.)
|
||||
const prosemirrorJson = canonicalizeFootnotes(extracted.prosemirrorJson);
|
||||
|
||||
const pageTitle = title || fileName;
|
||||
|
||||
Reference in New Issue
Block a user