| @@ -236,8 +236,17 @@ | ||
| 236 | 236 | * Line-items table (delegated to the order variable). |
| 237 | 237 | */ |
| 238 | 238 | private static function itemsTable(): array |
| 239 | 239 | { |
| 240 | + // No inner content. The block is dynamic — it registers with | |
| 241 | + // `save: () => null` and renders through | |
| 242 | + // ReceiptItemTableBlockEditor::render(), which ignores $content and emits | |
| 243 | + // its own {{order.pdf_items_table}} placeholder built from these | |
| 244 | + // attributes. Passing content here made blocksToContent() serialise an | |
| 245 | + // open/close pair with `{{order.items_table}}` inside it, and the editor | |
| 246 | + // then failed to validate the saved markup against a save() that produces | |
| 247 | + // nothing. Every other receipt block already passes null and serialises | |
| 248 | + // self-closing. | |
| 240 | 249 | return self::block('fluent-cart/receipt-item-table', [ |
| 241 | 250 | 'headerBg' => self::WHITE, |
| 242 | 251 | 'headerColor' => self::TEXT, |
| 243 | 252 | 'bodyColor' => self::TEXT, |
| @@ -242,9 +251,9 @@ | ||
| 242 | 251 | 'headerColor' => self::TEXT, |
| 243 | 252 | 'bodyColor' => self::TEXT, |
| 244 | 253 | 'borderColor' => self::BORDER, |
| 245 | 254 | 'fontSize' => 11, |
| 246 | - ], '{{order.items_table}}'); | |
| 255 | + ]); | |
| 247 | 256 | } |
| 248 | 257 | |
| 249 | 258 | /** |
| 250 | 259 | * Payment summary with optional sub-rows and a bold total row. |