fix: preserve textAlign/verticalAlign/containerId through REST round-trip

ElementSharedFieldsSchema in server.ts was silently stripping textAlign,
verticalAlign, and containerId on every POST/PATCH, causing bound text
inside containers to lose centering after a sync round-trip.

- Add missing fields to ElementSharedFieldsSchema (server.ts)
- Add textAlign?, verticalAlign?, containerId? to ServerElement (types.ts)
- Set textAlign: "center", verticalAlign: "top" on MCP subtitle elements (index.ts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
newblacc
2026-03-30 20:56:46 +02:00
co-authored by Claude Sonnet 4.6
parent 90cae43193
commit 87e8a8e314
4 changed files with 13 additions and 0 deletions
+3
View File
@@ -23,6 +23,9 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Title/subtitle auto-injection for WS-delivered container elements: `handleCanvasChange()` now called explicitly after `element_created` updates scene (Excalidraw's `CaptureUpdateAction.NEVER` suppresses the `onChange` callback)
- Curved arrow control points remain deformable after sync round-trip (merge strategy preserves Excalidraw internals)
- E2E: `curved arrow stays deformable after sync round-trip` regression test passing
- `textAlign`, `verticalAlign`, `containerId` added to `ElementSharedFieldsSchema` in `server.ts` — Zod was silently stripping these fields on every REST round-trip, causing bound text to lose centering after sync
- `ServerElement` type updated with `textAlign?`, `verticalAlign?`, `containerId?` to match schema
- Subtitle element in MCP `create_element` now sets `textAlign: "center"` and `verticalAlign: "top"`
- E2E: `new container arrival auto-injects title and subtitle text` now reliably passes with the double-WS fix
## [1.0.1] - 2026-03-29
+2
View File
@@ -1159,6 +1159,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest)
text: effectiveSubtitle,
fontSize: resolvedSubtitleSize,
fontFamily: resolvedSubtitleFont,
textAlign: 'center',
verticalAlign: 'top',
strokeColor: elementProps.strokeColor ?? '#1e1e1e',
opacity: elementProps.opacity ?? 100,
roughness: elementProps.roughness ?? USER_PREFS.roughness,
+4
View File
@@ -487,6 +487,10 @@ const ElementSharedFieldsSchema = z.object({
endBinding: z.any().nullable().optional(),
boundElements: z.any().nullable().optional(),
elbowed: z.boolean().optional(),
// Text alignment properties (required for bound text inside containers)
textAlign: z.string().optional(),
verticalAlign: z.string().optional(),
containerId: z.string().nullable().optional(),
// Image element properties
fileId: z.string().optional(),
status: z.string().optional(),
+4
View File
@@ -143,6 +143,10 @@ export interface ServerElement extends Omit<ExcalidrawElementBase, 'id'> {
end?: { id: string };
startBinding?: ExcalidrawBinding | null;
endBinding?: ExcalidrawBinding | null;
// Text alignment (bound text inside containers)
textAlign?: string;
verticalAlign?: string;
containerId?: string | null;
// Image element properties
fileId?: string;
status?: string;