JupyterLab application framework providing the core application class, shell management, plugin system, layout restoration, and routing capabilities.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Dependency injection tokens for service-based architecture, enabling loose coupling and extensibility in the plugin system.
Service token and interface for handling server connection failures with customizable dialog behavior.
/**
* Connection lost handler function type
* @param manager - Service manager instance
* @param err - Network error that occurred
* @param translator - Optional translator for internationalization
* @returns Promise resolving when handling is complete
*/
type IConnectionLost = (
manager: ServiceManager.IManager,
err: ServerConnection.NetworkError,
translator?: ITranslator
) => Promise<void>;
/**
* Service token for connection lost handling
*/
const IConnectionLost: Token<IConnectionLost>;Usage Examples:
import { IConnectionLost } from "@jupyterlab/application";
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
// Using connection lost service in a plugin
const connectionPlugin: JupyterFrontEndPlugin<void> = {
id: 'my-connection-plugin',
autoStart: true,
requires: [IConnectionLost],
activate: (app, connectionLost: IConnectionLost) => {
// Monitor service manager for connection issues
app.serviceManager.connectionFailure.connect(async (sender, err) => {
await connectionLost(app.serviceManager, err);
});
}
};
// Providing custom connection lost handler
const customConnectionLostPlugin: JupyterFrontEndPlugin<IConnectionLost> = {
id: 'custom-connection-lost',
provides: IConnectionLost,
activate: (app) => {
return async (manager, err, translator) => {
// Custom connection lost handling
console.error('Connection lost:', err.message);
// Show custom dialog or notification
const dialog = document.createElement('div');
dialog.textContent = 'Connection to server lost. Attempting to reconnect...';
document.body.appendChild(dialog);
// Attempt reconnection
try {
await manager.ready;
dialog.remove();
} catch (reconnectErr) {
dialog.textContent = 'Failed to reconnect. Please refresh the page.';
}
};
}
};Service token and interface for managing application busy and dirty states with reactive signals.
/**
* Application status management interface
*/
interface ILabStatus {
/** Signal emitted when application busy state changes */
readonly busySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
/** Signal emitted when application dirty state changes */
readonly dirtySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
/** Whether the application is currently busy */
readonly isBusy: boolean;
/** Whether the application has unsaved changes */
readonly isDirty: boolean;
/**
* Set the application state to busy
* @returns A disposable used to clear the busy state for the caller
*/
setBusy(): IDisposable;
/**
* Set the application state to dirty
* @returns A disposable used to clear the dirty state for the caller
*/
setDirty(): IDisposable;
}
/**
* Service token for application status management
*/
const ILabStatus: Token<ILabStatus>;Usage Examples:
import { ILabStatus } from "@jupyterlab/application";
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
// Using status service in a plugin
const statusPlugin: JupyterFrontEndPlugin<void> = {
id: 'my-status-plugin',
autoStart: true,
requires: [ILabStatus],
activate: (app, status: ILabStatus) => {
// Listen to status changes
status.busySignal.connect((sender, isBusy) => {
console.log('Application busy state:', isBusy);
// Update UI (e.g., show/hide spinner)
document.body.classList.toggle('busy', isBusy);
});
status.dirtySignal.connect((sender, isDirty) => {
console.log('Application dirty state:', isDirty);
// Update UI (e.g., show unsaved indicator)
document.title = isDirty ? '• My App' : 'My App';
});
// Set busy state during long operations
const performLongOperation = async () => {
const busyDisposable = status.setBusy();
try {
await someAsyncOperation();
} finally {
busyDisposable.dispose(); // Clear busy state
}
};
// Set dirty state when content changes
const handleContentChange = () => {
const dirtyDisposable = status.setDirty();
// Keep the disposable until content is saved
return dirtyDisposable;
};
}
};Service token for URL routing functionality (covered in detail in URL Routing documentation).
/**
* Service token for URL router
*/
const IRouter: Token<IRouter>;
/**
* URL routing service interface
*/
interface IRouter {
readonly base: string;
readonly commands: CommandRegistry;
readonly current: IRouter.ILocation;
readonly routed: ISignal<IRouter, IRouter.ILocation>;
readonly stop: Token<void>;
navigate(path: string, options?: IRouter.INavOptions): void;
register(options: IRouter.IRegisterOptions): IDisposable;
reload(): void;
route(url: string): void;
}Service token for layout restoration functionality (covered in detail in Layout Restoration documentation).
/**
* Service token for layout restoration
*/
const ILayoutRestorer: Token<ILayoutRestorer>;
/**
* Layout restoration service interface
*/
interface ILayoutRestorer extends IRestorer {
readonly restored: Promise<void>;
add(widget: Widget, name: string): void;
restore<T extends Widget>(
tracker: WidgetTracker<T>,
options: IRestorer.IOptions<T>
): Promise<any>;
}Service token for tracking MIME document widgets across the application.
/**
* MIME document widget tracker interface
*/
interface IMimeDocumentTracker extends IWidgetTracker<MimeDocument> {
// Inherits all IWidgetTracker methods for tracking MimeDocument widgets
}
/**
* Service token for MIME document tracker
*/
const IMimeDocumentTracker: Token<IMimeDocumentTracker>;Usage Examples:
import { IMimeDocumentTracker } from "@jupyterlab/application";
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
const mimeTrackerPlugin: JupyterFrontEndPlugin<void> = {
id: 'my-mime-tracker-plugin',
autoStart: true,
requires: [IMimeDocumentTracker],
activate: (app, tracker: IMimeDocumentTracker) => {
// Listen for new MIME documents
tracker.widgetAdded.connect((sender, widget) => {
console.log('New MIME document added:', widget.context.path);
});
// Access current MIME document
const current = tracker.currentWidget;
if (current) {
console.log('Current MIME document:', current.title.label);
}
// Find specific MIME documents
const htmlDocs = tracker.filter(widget =>
widget.context.path.endsWith('.html')
);
console.log(`Found ${htmlDocs.length} HTML documents`);
}
};Service token for updating tree path information in the application.
/**
* Tree path updater function type
* @param treePath - New tree path to set
*/
type ITreePathUpdater = (treePath: string) => void;
/**
* Service token for tree path updating
*/
const ITreePathUpdater: Token<ITreePathUpdater>;Usage Examples:
import { ITreePathUpdater } from "@jupyterlab/application";
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
const pathUpdaterPlugin: JupyterFrontEndPlugin<void> = {
id: 'my-path-updater-plugin',
autoStart: true,
requires: [ITreePathUpdater],
activate: (app, updatePath: ITreePathUpdater) => {
// Update path when navigating
const navigateToPath = (newPath: string) => {
updatePath(newPath);
console.log('Updated tree path to:', newPath);
};
// Example usage
navigateToPath('/notebooks/analysis.ipynb');
navigateToPath('/data/dataset.csv');
}
};Additional service tokens from the JupyterFrontEnd namespace.
/**
* Service token for application paths configuration
*/
const IPaths: Token<JupyterFrontEnd.IPaths>;
/**
* Service token for tree resolver functionality
*/
const ITreeResolver: Token<JupyterFrontEnd.ITreeResolver>;Common patterns for using service tokens in plugin development.
// Single service dependency
const basicPlugin: JupyterFrontEndPlugin<void> = {
id: 'basic-service-plugin',
autoStart: true,
requires: [ILabStatus],
activate: (app, status: ILabStatus) => {
// Use the service
const disposable = status.setBusy();
// ... do work
disposable.dispose();
}
};// Multiple service dependencies
const multiServicePlugin: JupyterFrontEndPlugin<void> = {
id: 'multi-service-plugin',
autoStart: true,
requires: [ILabStatus, IRouter, ILayoutRestorer],
activate: (app, status: ILabStatus, router: IRouter, restorer: ILayoutRestorer) => {
// Use multiple services together
router.routed.connect(() => {
const busyDisposable = status.setBusy();
// Restore layout after routing
restorer.restored.then(() => {
busyDisposable.dispose();
});
});
}
};// Optional service dependencies
const optionalServicePlugin: JupyterFrontEndPlugin<void> = {
id: 'optional-service-plugin',
autoStart: true,
requires: [ILabStatus],
optional: [IRouter],
activate: (app, status: ILabStatus, router: IRouter | null) => {
// Required service is always available
status.setBusy();
// Optional service might be null
if (router) {
router.navigate('/optional-feature');
} else {
console.log('Router not available, using fallback');
}
}
};// Providing a service implementation
const serviceProviderPlugin: JupyterFrontEndPlugin<IConnectionLost> = {
id: 'custom-connection-lost-provider',
provides: IConnectionLost,
activate: (app): IConnectionLost => {
return async (manager, err, translator) => {
// Custom implementation
console.error('Custom connection lost handler:', err);
};
}
};
// Overriding default service
const overrideServicePlugin: JupyterFrontEndPlugin<ILabStatus> = {
id: 'custom-status-provider',
provides: ILabStatus,
activate: (app): ILabStatus => {
// Return custom implementation
return new CustomLabStatus(app);
}
};// Composing services to create higher-level functionality
const compositeServicePlugin: JupyterFrontEndPlugin<IMyCompositeService> = {
id: 'composite-service',
provides: IMyCompositeService,
requires: [ILabStatus, IRouter, ILayoutRestorer],
activate: (app, status, router, restorer): IMyCompositeService => {
return new MyCompositeService(status, router, restorer);
}
};
interface IMyCompositeService {
navigateWithBusyState(path: string): Promise<void>;
saveAndNavigate(path: string): Promise<void>;
}
class MyCompositeService implements IMyCompositeService {
constructor(
private status: ILabStatus,
private router: IRouter,
private restorer: ILayoutRestorer
) {}
async navigateWithBusyState(path: string): Promise<void> {
const busyDisposable = this.status.setBusy();
try {
this.router.navigate(path);
await this.restorer.restored;
} finally {
busyDisposable.dispose();
}
}
async saveAndNavigate(path: string): Promise<void> {
const dirtyDisposable = this.status.setDirty();
try {
// Save current state
await this.saveCurrentState();
dirtyDisposable.dispose();
// Navigate to new path
await this.navigateWithBusyState(path);
} catch (error) {
// Keep dirty state if save failed
throw error;
}
}
private async saveCurrentState(): Promise<void> {
// Implementation for saving state
}
}// Good: Descriptive token with documentation
const IMyService: Token<IMyService> = new Token<IMyService>(
'@myapp/myservice:IMyService',
'A service for managing custom functionality in my application.'
);
// Good: Interface with clear contracts
interface IMyService {
/** Initialize the service */
initialize(): Promise<void>;
/** Clean up resources */
dispose(): void;
}// Service with proper error handling
const robustServicePlugin: JupyterFrontEndPlugin<void> = {
id: 'robust-service-plugin',
requires: [ILabStatus],
activate: (app, status: ILabStatus) => {
try {
// Service initialization
const disposable = status.setBusy();
// Handle service errors gracefully
status.busySignal.connect((sender, isBusy) => {
if (isBusy) {
// Set up error recovery
setTimeout(() => {
if (status.isBusy) {
console.warn('Operation taking longer than expected');
}
}, 5000);
}
});
} catch (error) {
console.error('Failed to initialize service:', error);
// Graceful degradation
}
}
};