Kraken TUI uses a structured error handling system that translates FFI error codes into typed exceptions.
KrakenError Class
All errors from the native core are wrapped in KrakenError:
export class KrakenError extends Error {
public readonly code : number ;
constructor ( message : string , code : number ) {
super ( message );
this . name = "KrakenError" ;
this . code = code ;
}
}
Error Codes
Code Type Source -1Error Validation failure, invalid handle, or runtime error -2Panic Internal panic caught at FFI boundary
Usage Example
try {
const box = new Box ({ width: "100%" , height: "100%" });
box . setStyle ({ fg: "#FF0000" });
app . setRoot ( box );
} catch ( error ) {
if ( error instanceof KrakenError ) {
console . error ( `Native error [ ${ error . code } ]: ${ error . message } ` );
if ( error . code === - 2 ) {
console . error ( "Fatal: internal panic occurred" );
process . exit ( 1 );
}
}
}
checkResult() Helper
The checkResult() function translates FFI return codes into exceptions:
export function checkResult ( code : number , context ?: string ) : void {
if ( code >= 0 ) return ;
let message : string ;
if ( code === - 2 ) {
message = "Internal panic in native core" ;
} else {
const errPtr = ffi . tui_get_last_error ();
if ( errPtr ) {
message = new CString ( errPtr ). toString ();
ffi . tui_clear_error ();
} else {
message = "Unknown error" ;
}
}
if ( context ) {
message = ` ${ context } : ${ message } ` ;
}
throw new KrakenError ( message , code );
}
Context Parameter
Provide context to make errors more actionable:
checkResult (
ffi . tui_set_content ( handle , ptr , len ),
"Failed to set text content"
);
// Error message: "Failed to set text content: invalid handle: 42"
All Widget class methods use checkResult() internally, so you rarely need to call it directly.
Common Error Categories
Invalid Handle Errors
Occur when using a destroyed or non-existent handle:
const box = new Box ();
box . destroy ();
box . setStyle ({ fg: "red" }); // KrakenError: invalid handle: 1
Prevention:
Don’t use widgets after calling .destroy()
Use .destroySubtree() for recursive cleanup
Check handle !== 0 before FFI calls
Tree Invariant Violations
Occur when violating tree structure rules:
const box1 = new Box ();
const box2 = new Box ();
const text = new Text ({ content: "Hello" });
box1 . append ( text );
box2 . append ( text ); // KrakenError: node already has a parent
Prevention:
Remove a child from its parent before re-parenting: parent.removeChild(child)
Use .destroySubtree() when unmounting component trees
Encoding Errors
Occur when passing invalid UTF-8:
const invalidUtf8 = new Uint8Array ([ 0xFF , 0xFE ]);
ffi . tui_set_content ( handle , ptr ( invalidUtf8 ), 2 );
// KrakenError: invalid UTF-8
Prevention:
Use TextEncoder to encode strings: new TextEncoder().encode(str)
Validate external input before passing to FFI
Render Failures
Occur when the terminal is in an invalid state:
app . shutdown ();
app . render (); // KrakenError: context not initialized
Prevention:
Call Kraken.init() before any other operations
Don’t call methods after shutdown()
Handle terminal resize events gracefully
Error Recovery Strategies
Graceful Degradation
function safeRender ( app : Kraken ) : boolean {
try {
app . render ();
return true ;
} catch ( error ) {
if ( error instanceof KrakenError ) {
console . warn ( "Render failed, skipping frame:" , error . message );
return false ;
}
throw error ; // Re-throw non-Kraken errors
}
}
while ( running ) {
app . readInput ( 16 );
for ( const event of app . drainEvents ()) {
handleEvent ( event );
}
safeRender ( app );
}
function createWidget ( type : NodeType ) : Widget | null {
try {
switch ( type ) {
case NodeType . Box :
return new Box ();
case NodeType . Text :
return new Text ();
default :
return null ;
}
} catch ( error ) {
console . error ( "Widget creation failed:" , error );
return null ;
}
}
State Reset on Error
class AppState {
private root ?: Box ;
reset () : void {
try {
this . root ?. destroySubtree ();
} catch ( error ) {
console . warn ( "Cleanup failed:" , error );
}
this . root = new Box ({ width: "100%" , height: "100%" });
}
recover ( error : KrakenError ) : void {
console . error ( "Fatal error, resetting state:" , error . message );
this . reset ();
}
}
Debugging Techniques
Enable Debug Mode
Enable verbose logging in the native core:
const app = Kraken . init ();
ffi . tui_set_debug ( 1 ); // Enable debug logging to stderr
// Now all tree mutations, layout recomputations, and event
// processing will log structured diagnostics
Debug mode has zero overhead when disabled but generates significant stderr output when enabled. Not recommended for production.
Inspect Error Messages
Error messages include detailed context:
try {
ffi . tui_append_child ( parent , child );
} catch ( error ) {
console . error ( error . message );
// "node 42 already has parent 17"
// "invalid handle: 99"
// "cannot append node to itself"
}
Diagnostics counters help identify issues:
const dirtyCount = ffi . tui_get_perf_counter ( 5 ); // Dirty node count
const eventDepth = ffi . tui_get_perf_counter ( 3 ); // Event buffer depth
if ( dirtyCount > 100 ) {
console . warn ( "High dirty node count - possible layout thrashing" );
}
if ( eventDepth > 50 ) {
console . warn ( "Event buffer buildup - drain loop may be too slow" );
}
Check Terminal Capabilities
const caps = ffi . tui_get_capabilities ();
if (( caps & 0x01 ) === 0 ) {
console . warn ( "Terminal lacks truecolor support - colors will degrade" );
}
if (( caps & 0x02 ) === 0 ) {
console . warn ( "Terminal lacks mouse support - no click events" );
}
Memory Budget Tracking
const nodeCount = ffi . tui_get_node_count ();
if ( nodeCount > 1000 ) {
console . warn ( `High widget count: ${ nodeCount } nodes` );
}
Best Practices
Always wrap high-level operations
function safeOperation < T >( op : () => T , fallback : T ) : T {
try {
return op ();
} catch ( error ) {
if ( error instanceof KrakenError ) {
console . error ( "Operation failed:" , error . message );
return fallback ;
}
throw error ;
}
}
Clean up resources in finally blocks
function renderFrame ( app : Kraken ) : void {
let tempWidget : Box | null = null ;
try {
tempWidget = new Box ();
// ... use widget ...
app . render ();
} finally {
tempWidget ?. destroy ();
}
}
Log context for debugging
try {
widget . setStyle ({ fg: color });
} catch ( error ) {
console . error ( `setStyle failed for widget ${ widget . handle } :` , error );
console . error ( ` Color: ${ color } ` );
console . error ( ` Widget type: ${ widget . type } ` );
}