Parent
#328. Related: #347
Problem
read_document shows media as a placeholder (redactMedia.ts). replace_section deletes the whole section body. So an AI rewrite of a section with a picture deletes the picture for everyone. An upload in progress (mediaUploadPlaceholder) is also deleted.
What to do
In replace_section (documentTools.ts), after the rev and heading-level checks and before deps.content.apply, count the media in content.slice(section.start, section.end). If the count is above 0, refuse. Media means image, any EMBED_NODE_TYPES, mediaUploadPlaceholder, or a node with a media-route mark (isMediaLink), at any depth. Put isMediaNode and countMediaNodes in redactMedia.ts. Walk with a stack, as mapNodes does.
Text: "This section holds N media item(s), such as pictures or videos. A rewrite would delete them, so docs.plus refused it. Make this change in docs.plus yourself."
Count it as the new ToolOutcome 'refused-section-media', through an optional outcome on refuse(). #347's gate reads this count. In toToolRows (admin-dashboard/src/pages/mcp.tsx), put it under "Refused".
Acceptance
Out of scope
Keeping unchanged blocks (#347). Formatting loss.
Parent
#328. Related: #347
Problem
read_documentshows media as a placeholder (redactMedia.ts).replace_sectiondeletes the whole section body. So an AI rewrite of a section with a picture deletes the picture for everyone. An upload in progress (mediaUploadPlaceholder) is also deleted.What to do
In
replace_section(documentTools.ts), after therevand heading-level checks and beforedeps.content.apply, count the media incontent.slice(section.start, section.end). If the count is above 0, refuse. Media meansimage, anyEMBED_NODE_TYPES,mediaUploadPlaceholder, or a node with a media-route mark (isMediaLink), at any depth. PutisMediaNodeandcountMediaNodesinredactMedia.ts. Walk with a stack, asmapNodesdoes.Text: "This section holds N media item(s), such as pictures or videos. A rewrite would delete them, so docs.plus refused it. Make this change in docs.plus yourself."
Count it as the new
ToolOutcome'refused-section-media', through an optional outcome onrefuse(). #347's gate reads this count. IntoToolRows(admin-dashboard/src/pages/mcp.tsx), put it under "Refused".Acceptance
revstill answers the conflict text first.countMediaNodes, proved by sabotage: remove the placeholder branch, then the child walk.replace_sectiondescription, indocs/mcp/reference.md(row and Errors), and inAPI.md§MCP connector Writes and Usage counts.Out of scope
Keeping unchanged blocks (#347). Formatting loss.