Skip to content

Commit d0e103a

Browse files
committed
fix(tui): fix border rendering in iocraft column layouts
Create component nodes as plain JavaScript objects instead of calling native io.view() and io.text() functions. The native functions were causing NAPI deserialization to lose Option<String> properties like border_style, border_color, and flex_direction when nodes were passed as array elements. Changes: - Text() now creates plain {type: 'Text', content: ...} objects - Box() now creates plain {type: 'View', children: ...} objects - Added padding and margin shorthand props to BoxProps - Fixed padding_x/padding_y and margin_x/margin_y property names - Added test-iocraft-layout.mjs for layout bug reproduction All three TUI renderers (analytics, audit-log, threat-feed) now display bordered sections correctly.
1 parent f7b55a3 commit d0e103a

File tree

2 files changed

+94
-21
lines changed

2 files changed

+94
-21
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env node
2+
/**
3+
* @fileoverview Minimal test case to demonstrate iocraft layout bug.
4+
*
5+
* ISSUE: Layout properties (flex_direction, border_style, etc.) are not being
6+
* applied during rendering.
7+
*
8+
* Run: node scripts/test-iocraft-layout.mjs
9+
*/
10+
11+
import { createRequire } from 'node:module'
12+
13+
const require = createRequire(import.meta.url)
14+
const iocraft = require('@socketaddon/iocraft')
15+
const io = iocraft.default || iocraft
16+
17+
io.init()
18+
19+
console.log('Testing iocraft layout engine\n')
20+
console.log('=' .repeat(60))
21+
22+
// Test 1: Column layout.
23+
console.log('\n1. Column Layout Test')
24+
console.log('-'.repeat(60))
25+
const columnBox = io.view([
26+
io.text('Line 1'),
27+
io.text('Line 2'),
28+
io.text('Line 3'),
29+
])
30+
columnBox.flex_direction = 'column'
31+
32+
console.log('Expected: Lines stacked vertically')
33+
console.log('Actual output:')
34+
io.printComponent(columnBox)
35+
console.log('\nComponent tree:', JSON.stringify(columnBox, null, 2))
36+
37+
// Test 2: Border rendering.
38+
console.log('\n2. Border Test')
39+
console.log('-'.repeat(60))
40+
const borderBox = io.view([io.text('Content')])
41+
borderBox.border_style = 'single'
42+
borderBox.padding = 1
43+
44+
console.log('Expected: Box with single-line border around padded content')
45+
console.log('Actual output:')
46+
io.printComponent(borderBox)
47+
console.log('\nComponent tree:', JSON.stringify(borderBox, null, 2))
48+
49+
// Test 3: Gap spacing.
50+
console.log('\n3. Gap Test')
51+
console.log('-'.repeat(60))
52+
const gapBox = io.view([io.text('A'), io.text('B'), io.text('C')])
53+
gapBox.flex_direction = 'column'
54+
gapBox.gap = 1
55+
56+
console.log('Expected: Items stacked vertically with 1 line gap between them')
57+
console.log('Actual output:')
58+
io.printComponent(gapBox)
59+
60+
console.log('\n' + '='.repeat(60))
61+
console.log('\nSUMMARY:')
62+
console.log('- flex_direction: "column" → NOT WORKING (items render in row)')
63+
console.log('- border_style: "single" → NOT WORKING (no border drawn)')
64+
console.log('- gap → PARTIALLY WORKING (adds space in row layout)')
65+
console.log('\nThe layout engine does not appear to be processing flexbox properties.')

packages/cli/src/utils/terminal/iocraft.mts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export interface BoxProps {
6969
| 'center'
7070
| 'space-between'
7171
| 'space-around'
72+
margin?: number
7273
marginBottom?: number
7374
marginLeft?: number
7475
marginRight?: number
@@ -77,6 +78,7 @@ export interface BoxProps {
7778
marginY?: number
7879
overflowX?: 'visible' | 'hidden'
7980
overflowY?: 'visible' | 'hidden'
81+
padding?: number
8082
paddingBottom?: number
8183
paddingLeft?: number
8284
paddingRight?: number
@@ -102,18 +104,20 @@ export type Element = import('@socketaddon/iocraft').ComponentNode
102104
* Create a text element with styling.
103105
*/
104106
export function Text(props: TextProps): Element {
105-
const io = getIocraft()
106107
const content =
107108
typeof props.children === 'string'
108109
? props.children
109110
: Array.isArray(props.children)
110111
? props.children.join('')
111112
: ''
112113

113-
// Create basic text node.
114-
const node = io.text(content)
114+
// Create text node as plain object to avoid NAPI deserialization bugs
115+
const node: Element = {
116+
type: 'Text',
117+
content,
118+
}
115119

116-
// Apply styling properties.
120+
// Apply styling properties
117121
if (props.bold) {
118122
node.bold = true
119123
}
@@ -134,17 +138,19 @@ export function Text(props: TextProps): Element {
134138
* Create a box/view element with layout properties.
135139
*/
136140
export function Box(props: BoxProps): Element {
137-
const io = getIocraft()
138141
const children = Array.isArray(props.children)
139142
? props.children
140143
: props.children
141144
? [props.children]
142145
: []
143146

144-
// Create basic view node.
145-
const node = io.view(children)
147+
// Create view node as plain object to avoid NAPI deserialization bugs
148+
const node: Element = {
149+
type: 'View',
150+
children,
151+
}
146152

147-
// Apply layout properties.
153+
// Apply layout properties
148154
if (props.flexDirection) {
149155
node.flex_direction = props.flexDirection
150156
}
@@ -164,22 +170,23 @@ export function Box(props: BoxProps): Element {
164170
node.gap = props.gap
165171
}
166172

167-
// Dimensions.
173+
// Dimensions
168174
if (props.width !== undefined) {
169175
node.width = props.width
170176
}
171177
if (props.height !== undefined) {
172178
node.height = props.height
173179
}
174180

175-
// Padding (handle both individual and shorthand).
181+
// Padding (handle both individual and shorthand)
182+
if (props.padding !== undefined) {
183+
node.padding = props.padding
184+
}
176185
if (props.paddingX !== undefined) {
177-
node.padding_left = props.paddingX
178-
node.padding_right = props.paddingX
186+
node.padding_x = props.paddingX
179187
}
180188
if (props.paddingY !== undefined) {
181-
node.padding_top = props.paddingY
182-
node.padding_bottom = props.paddingY
189+
node.padding_y = props.paddingY
183190
}
184191
if (props.paddingTop !== undefined) {
185192
node.padding_top = props.paddingTop
@@ -194,14 +201,15 @@ export function Box(props: BoxProps): Element {
194201
node.padding_left = props.paddingLeft
195202
}
196203

197-
// Margin (handle both individual and shorthand).
204+
// Margin (handle both individual and shorthand)
205+
if (props.margin !== undefined) {
206+
node.margin = props.margin
207+
}
198208
if (props.marginX !== undefined) {
199-
node.margin_left = props.marginX
200-
node.margin_right = props.marginX
209+
node.margin_x = props.marginX
201210
}
202211
if (props.marginY !== undefined) {
203-
node.margin_top = props.marginY
204-
node.margin_bottom = props.marginY
212+
node.margin_y = props.marginY
205213
}
206214
if (props.marginTop !== undefined) {
207215
node.margin_top = props.marginTop
@@ -216,15 +224,15 @@ export function Box(props: BoxProps): Element {
216224
node.margin_left = props.marginLeft
217225
}
218226

219-
// Border.
227+
// Border
220228
if (props.borderStyle) {
221229
node.border_style = props.borderStyle
222230
}
223231
if (props.borderColor) {
224232
node.border_color = props.borderColor
225233
}
226234

227-
// Background.
235+
// Background
228236
if (props.backgroundColor) {
229237
node.background_color = props.backgroundColor
230238
}

0 commit comments

Comments
 (0)