@@ -325,44 +325,64 @@ static void render_text(struct Clayterm *ct, int x0, int y0,
325325static void render_border (struct Clayterm * ct , int x0 , int y0 , int x1 , int y1 ,
326326 Clay_RenderCommand * cmd ) {
327327 Clay_BorderRenderData * b = & cmd -> renderData .border ;
328- uint32_t fg = color (b -> color );
329- /* userData is currently exclusively the packed border-bg word. */
330- uint32_t bg = (uint32_t )(uintptr_t )cmd -> userData ;
328+ /* Must match border packing in ops.ts.
329+ * userData points at eight required words in the command buffer: resolved
330+ * fg/bg pairs in top, right, bottom, left order. Fallback resolution
331+ * (shared color/bg vs side overrides) happens on the TypeScript side; this
332+ * renderer consumes explicit values only. The command buffer outlives the
333+ * render pass within reduce(). Missing userData is a wire-format violation.
334+ */
335+ const uint32_t * s = (const uint32_t * )cmd -> userData ;
336+ if (s == NULL ) {
337+ __builtin_trap ();
338+ }
339+
340+ uint32_t top_fg = s [0 ];
341+ uint32_t top_bg = s [1 ];
342+ uint32_t right_fg = s [2 ];
343+ uint32_t right_bg = s [3 ];
344+ uint32_t bot_fg = s [4 ];
345+ uint32_t bot_bg = s [5 ];
346+ uint32_t left_fg = s [6 ];
347+ uint32_t left_bg = s [7 ];
331348 int top = b -> width .top > 0 ;
332349 int bot = b -> width .bottom > 0 ;
333350 int left = b -> width .left > 0 ;
334351 int right = b -> width .right > 0 ;
335352
336- /* corners — rounded when corner radius > 0 */
353+ /* corners — rounded when corner radius > 0. Drawn only when both adjacent
354+ * sides are enabled; a terminal cell holds a single fg/bg, so top corners
355+ * take the top side attributes and bottom corners take the bottom side
356+ * attributes (deterministic approximation of CSS split corners). */
337357 uint32_t tl = b -> cornerRadius .topLeft > 0 ? 0x256d : 0x250c ;
338358 uint32_t tr = b -> cornerRadius .topRight > 0 ? 0x256e : 0x2510 ;
339359 uint32_t bl = b -> cornerRadius .bottomLeft > 0 ? 0x2570 : 0x2514 ;
340360 uint32_t br = b -> cornerRadius .bottomRight > 0 ? 0x256f : 0x2518 ;
341361
342362 if (top && left )
343- setcell (ct , x0 , y0 , tl , fg , bg );
363+ setcell (ct , x0 , y0 , tl , top_fg , top_bg );
344364 if (top && right )
345- setcell (ct , x1 - 1 , y0 , tr , fg , bg );
365+ setcell (ct , x1 - 1 , y0 , tr , top_fg , top_bg );
346366 if (bot && left )
347- setcell (ct , x0 , y1 - 1 , bl , fg , bg );
367+ setcell (ct , x0 , y1 - 1 , bl , bot_fg , bot_bg );
348368 if (bot && right )
349- setcell (ct , x1 - 1 , y1 - 1 , br , fg , bg );
369+ setcell (ct , x1 - 1 , y1 - 1 , br , bot_fg , bot_bg );
350370
351371 /* horizontal edges */
352372 if (top )
353373 for (int x = x0 + left ; x < x1 - right ; x ++ )
354- setcell (ct , x , y0 , 0x2500 , fg , bg );
374+ setcell (ct , x , y0 , 0x2500 , top_fg , top_bg );
355375 if (bot )
356376 for (int x = x0 + left ; x < x1 - right ; x ++ )
357- setcell (ct , x , y1 - 1 , 0x2500 , fg , bg );
377+ setcell (ct , x , y1 - 1 , 0x2500 , bot_fg , bot_bg );
358378
359- /* vertical edges */
379+ /* vertical edges — excluding joined corner cells owned by top/bottom */
360380 if (left )
361381 for (int y = y0 + top ; y < y1 - bot ; y ++ )
362- setcell (ct , x0 , y , 0x2502 , fg , bg );
382+ setcell (ct , x0 , y , 0x2502 , left_fg , left_bg );
363383 if (right )
364384 for (int y = y0 + top ; y < y1 - bot ; y ++ )
365- setcell (ct , x1 - 1 , y , 0x2502 , fg , bg );
385+ setcell (ct , x1 - 1 , y , 0x2502 , right_fg , right_bg );
366386}
367387
368388/* ── Command buffer helpers ───────────────────────────────────────── */
@@ -577,15 +597,18 @@ void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row) {
577597 }
578598
579599 if (mask & PROP_BORDER ) {
580- decl .border .color = unpack_color (rd (buf , len , & i ));
581-
582- decl .userData = (void * )(uintptr_t )rd (buf , len , & i );
583-
584600 uint32_t bw = rd (buf , len , & i );
585601 decl .border .width .left = bw & 0xff ;
586602 decl .border .width .right = (bw >> 8 ) & 0xff ;
587603 decl .border .width .top = (bw >> 16 ) & 0xff ;
588604 decl .border .width .bottom = (bw >> 24 ) & 0xff ;
605+
606+ /* Resolved per-side fg/bg attribute words (top, right, bottom,
607+ * left). Routed to render_border via userData; the command buffer
608+ * remains valid for the whole render pass. */
609+ if (i + 8 <= len )
610+ decl .userData = (void * )& buf [i ];
611+ i += 8 ;
589612 }
590613
591614 if (mask & PROP_CLIP ) {
0 commit comments