Foundational classes and utilities for building interactive widgets in Jupyter environments
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive error handling components for graceful failure display, debugging support, and user-friendly error presentation in Jupyter widget environments.
Factory functions for creating widget models and views that display error information.
/**
* Create a widget model class for displaying error information
* @param error - Error object or value to display
* @param msg - Optional additional error message
* @returns Widget model class configured for error display
*/
function createErrorWidgetModel(
error: unknown,
msg?: string
): typeof WidgetModel;
/**
* Create a widget view class for displaying custom error information
* @param error - Error object or value to display
* @param msg - Optional additional error message
* @returns Widget view class configured for error display
*/
function createErrorWidgetView(
error?: unknown,
msg?: string
): typeof WidgetView;Usage Examples:
// Create error model for widget creation failure
const handleWidgetCreationError = (error: Error) => {
const ErrorModel = createErrorWidgetModel(
error,
'Failed to create widget'
);
const errorWidget = new ErrorModel({}, {
model_id: generateId(),
widget_manager: this.widgetManager
});
return errorWidget;
};
// Create error view for rendering failure
const handleRenderError = (error: Error) => {
const ErrorView = createErrorWidgetView(
error,
'Failed to render widget content'
);
const errorView = new ErrorView({
model: this.model
});
return errorView;
};
// Handle async errors
try {
const data = await fetchWidgetData();
this.model.set('data', data);
} catch (error) {
const ErrorModel = createErrorWidgetModel(error, 'Data loading failed');
const fallbackWidget = new ErrorModel({}, {
model_id: 'error-' + Date.now(),
widget_manager: this.widgetManager
});
this.showErrorWidget(fallbackWidget);
}Pre-built view component for displaying error information with interactive error details.
/**
* View component for displaying error information with click-to-expand functionality
*/
class ErrorWidgetView extends DOMWidgetView {
/**
* Extract error message and stack trace from the model
* @returns Object containing optional message and stack trace
*/
generateErrorMessage(): { msg?: string; stack: string };
/**
* Render the error widget with SVG icon and interactive error display
*/
render(): void;
}The ErrorWidgetView provides:
Usage Examples:
// Custom error view with additional context
class CustomErrorView extends ErrorWidgetView {
generateErrorMessage() {
const baseError = super.generateErrorMessage();
return {
...baseError,
msg: `Widget Error in ${this.model.get('widget_type')}: ${baseError.msg}`
};
}
render() {
super.render();
// Add custom styling
this.el.classList.add('custom-error-widget');
// Add additional error context
const contextDiv = document.createElement('div');
contextDiv.innerHTML = `
<small>Widget ID: ${this.model.model_id}</small><br>
<small>Time: ${new Date().toLocaleString()}</small>
`;
this.el.appendChild(contextDiv);
}
}
// Use in widget manager
const createErrorView = (model: WidgetModel, error: Error) => {
const errorView = new CustomErrorView({
model: model
});
// Set error data on model
model.set('error', error);
model.set('msg', 'Custom widget failed to initialize');
return errorView;
};// Robust widget creation with error fallback
const createWidgetWithFallback = async (
options: IModelOptions,
widgetManager: IWidgetManager
): Promise<WidgetModel> => {
try {
return await widgetManager.new_model(options);
} catch (error) {
console.error('Widget creation failed:', error);
// Create error widget as fallback
const ErrorModel = createErrorWidgetModel(
error,
`Failed to create ${options.model_name}`
);
return new ErrorModel({
_model_name: 'ErrorWidgetModel',
_view_name: 'ErrorWidgetView',
original_model_name: options.model_name,
original_module: options.model_module,
error_type: 'creation_failed'
}, {
model_id: options.model_id || generateId(),
widget_manager: widgetManager
});
}
};
// Widget manager integration
class RobustWidgetManager implements IWidgetManager {
async new_model(options: IModelOptions, state?: JSONObject): Promise<WidgetModel> {
return createWidgetWithFallback(options, this);
}
async create_view<VT extends WidgetView>(
model: WidgetModel,
options?: unknown
): Promise<VT> {
try {
return await this.createViewInternal(model, options);
} catch (error) {
console.error('View creation failed:', error);
// Return error view instead
const ErrorView = createErrorWidgetView(
error,
`Failed to create view for ${model.get('_model_name')}`
);
return new ErrorView({
model: model,
options: options
}) as VT;
}
}
}// Robust communication with error recovery
class RobustWidgetModel extends WidgetModel {
send(content: JSONValue, callbacks?: ICallbacks, buffers?: ArrayBuffer[]): void {
const enhancedCallbacks: ICallbacks = {
...callbacks,
shell: {
...callbacks?.shell,
error: (msg) => {
this.handleCommError(msg);
callbacks?.shell?.error?.(msg);
}
},
iopub: {
...callbacks?.iopub,
error: (msg) => {
this.handleIOPubError(msg);
callbacks?.iopub?.error?.(msg);
}
}
};
try {
super.send(content, enhancedCallbacks, buffers);
} catch (error) {
this.handleSendError(error);
}
}
private handleCommError(msg: any): void {
console.error('Comm error:', msg.content);
// Create error state
this.set('_error_state', {
type: 'comm_error',
message: msg.content.ename || 'Communication error',
details: msg.content.evalue || 'Unknown error',
timestamp: Date.now()
});
this.save_changes();
}
private handleIOPubError(msg: any): void {
console.error('IOPub error:', msg.content);
// Show error in UI if view exists
if (this.views) {
Object.values(this.views).forEach(async (viewPromise) => {
try {
const view = await viewPromise;
this.showErrorInView(view, msg.content);
} catch (viewError) {
console.error('Failed to show error in view:', viewError);
}
});
}
}
private handleSendError(error: any): void {
console.error('Send error:', error);
// Attempt to show error through error widget
const ErrorModel = createErrorWidgetModel(
error,
'Communication failed'
);
// Try to replace current widget with error widget
this.close();
// Implementation would replace widget in UI
}
private showErrorInView(view: WidgetView, errorContent: any): void {
// Add error overlay to existing view
const errorOverlay = document.createElement('div');
errorOverlay.className = 'widget-error-overlay';
errorOverlay.innerHTML = `
<div class="error-message">
<strong>Widget Error:</strong> ${errorContent.ename || 'Unknown error'}
<br>
<small>${errorContent.evalue || 'Check console for details'}</small>
</div>
`;
view.el.appendChild(errorOverlay);
}
}// Safe view rendering with error boundaries
class SafeWidgetView extends DOMWidgetView {
render(): void {
try {
this.renderContent();
} catch (error) {
this.renderError(error);
}
}
protected renderContent(): void {
// Override in subclasses
throw new Error('renderContent must be implemented');
}
protected renderError(error: Error): void {
console.error('View rendering failed:', error);
// Clear any partial content
this.el.innerHTML = '';
// Show error widget content
const errorView = new ErrorWidgetView({
model: this.model
});
// Set error data on model temporarily
const originalError = this.model.get('error');
const originalMsg = this.model.get('msg');
this.model.set({
error: error,
msg: 'Widget rendering failed'
});
errorView.render();
this.el.appendChild(errorView.el);
// Restore original values
this.model.set({
error: originalError,
msg: originalMsg
});
}
update(options?: any): void {
try {
this.updateContent(options);
} catch (error) {
console.error('View update failed:', error);
this.renderError(error);
}
}
protected updateContent(options?: any): void {
// Override in subclasses
}
}
// Usage in custom widgets
class ChartWidgetView extends SafeWidgetView {
protected renderContent(): void {
const data = this.model.get('data');
const chartType = this.model.get('chart_type');
if (!data || !chartType) {
throw new Error('Missing required data or chart type');
}
// Render chart content
this.renderChart(data, chartType);
}
protected updateContent(options?: any): void {
if (this.model.hasChanged('data') || this.model.hasChanged('chart_type')) {
this.renderContent();
}
}
}// Handle async operations in widgets
class AsyncWidgetModel extends WidgetModel {
private async handleAsyncOperation<T>(
operation: () => Promise<T>,
errorContext: string
): Promise<T | null> {
try {
this.set('loading', true);
this.save_changes();
const result = await operation();
this.set({
loading: false,
error: null
});
this.save_changes();
return result;
} catch (error) {
console.error(`${errorContext} failed:`, error);
this.set({
loading: false,
error: {
context: errorContext,
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: Date.now()
}
});
this.save_changes();
return null;
}
}
async loadData(): Promise<void> {
await this.handleAsyncOperation(
async () => {
const response = await fetch(this.get('data_url'));
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.set('data', data);
return data;
},
'Data loading'
);
}
async saveChangesToServer(): Promise<void> {
await this.handleAsyncOperation(
async () => {
const response = await fetch(this.get('save_url'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.get_state())
});
if (!response.ok) {
throw new Error(`Save failed: ${response.statusText}`);
}
return response.json();
},
'Saving changes'
);
}
}
// View that responds to error states
class AsyncWidgetView extends DOMWidgetView {
initialize(parameters: WidgetView.IInitializeParameters): void {
super.initialize(parameters);
this.listenTo(this.model, 'change:error', this.handleErrorChange);
this.listenTo(this.model, 'change:loading', this.handleLoadingChange);
}
private handleErrorChange(): void {
const error = this.model.get('error');
if (error) {
this.showError(error);
} else {
this.hideError();
}
}
private handleLoadingChange(): void {
const loading = this.model.get('loading');
if (loading) {
this.showLoadingIndicator();
} else {
this.hideLoadingIndicator();
}
}
private showError(error: any): void {
// Remove any existing error display
this.hideError();
const errorDiv = document.createElement('div');
errorDiv.className = 'widget-error-display';
errorDiv.innerHTML = `
<div class="error-header">
<strong>Error in ${error.context || 'Widget'}:</strong>
</div>
<div class="error-message">${error.message}</div>
<div class="error-time">
<small>Occurred at: ${new Date(error.timestamp).toLocaleString()}</small>
</div>
${error.stack ? `<details><summary>Stack Trace</summary><pre>${error.stack}</pre></details>` : ''}
`;
this.el.appendChild(errorDiv);
}
private hideError(): void {
const errorDisplay = this.el.querySelector('.widget-error-display');
if (errorDisplay) {
errorDisplay.remove();
}
}
private showLoadingIndicator(): void {
const loader = document.createElement('div');
loader.className = 'widget-loading';
loader.innerHTML = '<div class="loading-spinner"></div><span>Loading...</span>';
this.el.appendChild(loader);
}
private hideLoadingIndicator(): void {
const loader = this.el.querySelector('.widget-loading');
if (loader) {
loader.remove();
}
}
}// Widget that degrades gracefully when features are unavailable
class FeatureAwareWidget extends DOMWidgetView {
render(): void {
if (this.supportsAdvancedFeatures()) {
this.renderAdvanced();
} else if (this.supportsBasicFeatures()) {
this.renderBasic();
} else {
this.renderFallback();
}
}
private supportsAdvancedFeatures(): boolean {
return 'WebGL2RenderingContext' in window &&
'OffscreenCanvas' in window;
}
private supportsBasicFeatures(): boolean {
return 'Canvas' in window &&
'WebGLRenderingContext' in window;
}
private renderAdvanced(): void {
// High-performance WebGL2 rendering
}
private renderBasic(): void {
// Basic WebGL or canvas rendering
}
private renderFallback(): void {
// Simple HTML/CSS fallback
const message = document.createElement('div');
message.innerHTML = `
<div class="feature-warning">
<p>This browser doesn't support advanced rendering features.</p>
<p>Showing simplified version.</p>
</div>
`;
this.el.appendChild(message);
}
}// Error reporting and telemetry
class ErrorReporter {
static reportError(error: Error, context: string, widget?: WidgetModel): void {
const errorReport = {
message: error.message,
stack: error.stack,
context: context,
timestamp: Date.now(),
userAgent: navigator.userAgent,
widgetInfo: widget ? {
modelName: widget.get('_model_name'),
modelModule: widget.get('_model_module'),
modelId: widget.model_id
} : undefined
};
// Log to console
console.error('Widget Error Report:', errorReport);
// Send to error tracking service (if configured)
this.sendToErrorService(errorReport);
}
private static sendToErrorService(report: any): void {
// Implementation would send to error tracking service
// like Sentry, LogRocket, etc.
}
}
// Usage in widgets
class ReportingWidgetModel extends WidgetModel {
initialize(attributes: any, options: any): void {
try {
super.initialize(attributes, options);
} catch (error) {
ErrorReporter.reportError(
error instanceof Error ? error : new Error(String(error)),
'Widget initialization',
this
);
throw error;
}
}
}