The backend APIs for content block mutations already exist — no new backend code needed.
| Method | Endpoint | Action | Handler |
|---|---|---|---|
| POST | /inventory-models/<cuid>/documents/<slug>/blocks |
Add block | InventoryModel.add_block() |
| PATCH | /inventory-models/<cuid>/documents/<slug>/blocks |
Remove block | InventoryModel.remove_block() |
| POST | /inventory-models/<cuid>/documents/<slug>/blocks/from-library |
Add from library | InventoryModel.add_block() + Metadata.log_from_json() |
| Function | Line | Signature |
|---|---|---|
AddBlockToSection |
2132 | (inventoryModel, documentSlug, templateSection, index, content) |
RemoveBlockFromSection |
2147 | (inventoryModel, documentSlug, templateSection, index, content) |
Add:
{ "templateSection": { "id": "...", "title": "...", ... }, "index": 0, "content": { "content_id": "...", "content_type": "text|guideline|assessment_summary" } }Remove:
{ "templateSection": { "id": "...", "title": "...", ... }, "index": 0, "content": { "content_id": "..." } }File: src/components/TemplateEditor/Accordion/index.tsx
Replace outlineOnly prop with content override handlers (same pattern as section ops):
interface AccordionProps {
// ... existing props ...
onAddContentBlock?: (sectionId: string, content: TemplateSectionContents, index: number) => void;
onRemoveContentBlock?: (sectionId: string, content: TemplateSectionContents) => void;
}Gate the 6 content handlers with override check:
handleAddBlocks(line ~388) → ifonAddContentBlock, call it per block, else localhandleAddGuidelines(line ~269) → samehandleAddAssessmentSummary(line ~333) → samehandleRemoveBlock(line ~426) → ifonRemoveContentBlock, call it, else localhandleRemoveGuideline(line ~303) → samehandleRemoveAssessmentSummary(line ~358) → same
Remove outlineOnly prop (no longer needed — content menu can show when handlers are wired).
File: src/pages/ModelInventoryOverview/Overview/Main/index.tsx
Add two useMutation hooks calling existing API functions:
const addBlock = useMutation(async (params: { section: TemplateSectionTree, content: TemplateSectionContents, index: number }) => {
return API.AddBlockToSection(inventoryModel, documentSlug, params.section, params.index, params.content);
}, { onSuccess: () => refetchDocument() });
const removeBlock = useMutation(async (params: { section: TemplateSectionTree, content: TemplateSectionContents }) => {
return API.RemoveBlockFromSection(inventoryModel, documentSlug, params.section, 0, params.content);
}, { onSuccess: () => refetchDocument() });Add useCallback wrappers that find the section from editorSectionTree by id, then call the mutation.
Pass onAddContentBlock and onRemoveContentBlock to <Accordion>. Remove outlineOnly={true}.
The from-library endpoint needs the blockLibraryItem CUID. The Accordion's handleAddBlocks already receives TBlockLibraryItem[] with cuid. The override handler should call the /blocks/from-library endpoint when options.block_key is present.
| File | Change |
|---|---|
src/components/TemplateEditor/Accordion/index.tsx |
Replace outlineOnly with content override props, gate handlers |
src/pages/ModelInventoryOverview/Overview/Main/index.tsx |
Add useMutation hooks, wire to <Accordion> |
No backend changes.
- Open document overview → Edit Outline → click
+on leaf section → CONTENT menu shows - Add a Text Block from library → persists on refresh
- Add a Guideline Block → persists on refresh
- Add Assessment Summary → persists on refresh
- Remove each block type → persists on refresh
- Template Editor in Settings still works (local-only, no regression)
on_template_section: either astring(section ID, backend looks it up) or full section dict (needsid+title)content_data: dict withcontent_id(required),content_type,options(optional)position_index:None→ append at end,-1→ prepend,N→ insert after index N
{ "templateSection": TemplateSection, "index": number, "content": TemplateSectionContents }These need a full TemplateSection object (not just id string) and an index number.
| Handler | Data available | What API needs | Gap |
|---|---|---|---|
handleAddGuidelines(sectionId, guidelines[]) |
sectionId (string), content_id per guideline |
full section, index, content | Need section lookup + index |
handleAddAssessmentSummary(sectionId) |
sectionId (string) |
full section, index=-1, content | Need section lookup |
handleAddBlocks(sectionId, blocks[]) |
sectionId (string), TBlockLibraryItem[] with .cuid + .name |
full section, index, content, blockLibraryItem CUID | Need section lookup + must use /from-library endpoint |
handleRemoveGuideline(sectionId, guideline) |
sectionId, guideline.content_id |
full section, content {content_id} |
Need section lookup |
handleRemoveAssessmentSummary(sectionId) |
sectionId |
full section, content {content_id: 'assessment_summary'} |
Need section lookup |
handleRemoveBlock(sectionId, blockKey, contentId) |
sectionId, blockKey, contentId |
full section, content {content_id} |
Need section lookup |
The override callbacks need access to the full section object, not just sectionId. Two options:
Option A (recommended): Override callbacks receive (sectionId, ...) and the Document Overview page looks up the full section from editorSectionTree using a helper:
const findSection = (id: string): TemplateSectionTree | undefined => {
const flat = flattenTrees(editorSectionTree);
return flat.find(s => s.id === id);
};Option B: Backend already supports on_template_section as a string (section ID) — line 3080. But the route layer in ui_model_document_blocks.py passes payload["templateSection"] directly. Would need to verify the backend handles string-only section input through the route → handler path. Risk: the route model validates templateSection as fields.Raw(required=True) so a string should pass, and _update_content_block handles strings at line 3080.
-
from-libraryendpoint is required for text blocks —handleAddBlocksusesTBlockLibraryItemwith.cuid. Must call/blocks/from-library(not/blocks) to createMetadatarecords. Payload:{ templateSection, index, content, blockLibraryItem: block.cuid } -
Assessment summary uses
index: -1— backend interprets this as "insert at position 0" (prepend) -
Guidelines/text blocks use
index: null— backend interpretsposition_index=Noneas "append at end". But the API sendsindexas a number. The route passespayload["index"]→on_position_index=index. Need to send a value that maps to append. Looking at the backend: ifposition_index is None→ append. So we can sendindex: -2or any value and handle it, OR we rely on the fact thatindexdefaults to end-of-list. Actually:add_block()default ison_position_index=-1which means "insert at beginning". To append, we needNone. But the route always passespayload["index"]. Fix: sendindex: <length of contents array>to insert at the end position. -
Remove backend matches by
content_idonly (line 3157) — does NOT usecontent_typeorblock_key. Pre-existing behavior, same as Template Editor. Low risk sincecontent_idis typically unique within a section.