diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..97da04c
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,29 @@
+# fdo-sdk Development Guidelines
+
+Auto-generated from all feature plans. Last updated: 2025-10-27
+
+## Active Technologies
+
+- TypeScript 5.7.3 (matching existing SDK codebase) + @anikitenko/fdo-sdk (the SDK itself), electron ^35.0.0, goober ^2.1.16 (CSS-in-JS), winston ^3.17.0 (logging) (002-plugin-examples)
+
+## Project Structure
+
+```text
+src/
+tests/
+```
+
+## Commands
+
+npm test && npm run lint
+
+## Code Style
+
+TypeScript 5.7.3 (matching existing SDK codebase): Follow standard conventions
+
+## Recent Changes
+
+- 002-plugin-examples: Added TypeScript 5.7.3 (matching existing SDK codebase) + @anikitenko/fdo-sdk (the SDK itself), electron ^35.0.0, goober ^2.1.16 (CSS-in-JS), winston ^3.17.0 (logging)
+
+
+
diff --git a/examples/01-basic-plugin.ts b/examples/01-basic-plugin.ts
new file mode 100644
index 0000000..ed51fe0
--- /dev/null
+++ b/examples/01-basic-plugin.ts
@@ -0,0 +1,172 @@
+/**
+ * Example 1: Basic Plugin Creation
+ *
+ * This example demonstrates the minimal requirements to create a working FDO plugin.
+ * It covers the core plugin lifecycle (initialization and rendering) and basic metadata structure.
+ *
+ * Compatible with SDK v1.x
+ *
+ * Learning Objectives:
+ * - Understand the FDO_SDK base class and FDOInterface
+ * - Learn how to implement required lifecycle methods (init and render)
+ * - See how to structure plugin metadata
+ * - Use the built-in logging system
+ *
+ * Expected Output:
+ * When this plugin runs in the FDO application, it will:
+ * 1. Log "BasicPlugin initialized!" to the console during initialization
+ * 2. Display a simple welcome message with the plugin name and version
+ * 3. Show a brief description of what the plugin does
+ */
+
+import { FDO_SDK, FDOInterface, PluginMetadata, DOMText, DOMNested } from "../src";
+
+/**
+ * BasicPlugin demonstrates the minimal plugin structure.
+ *
+ * All plugins must:
+ * 1. Extend the FDO_SDK base class
+ * 2. Implement the FDOInterface interface
+ * 3. Define metadata (name, version, author, description, icon)
+ * 4. Implement init() and render() methods
+ */
+export default class BasicPlugin extends FDO_SDK implements FDOInterface {
+ /**
+ * Plugin metadata - required by FDOInterface.
+ * This information is used by the FDO application to identify and describe your plugin.
+ *
+ * CUSTOMIZE HERE: Replace these values with your own plugin information
+ */
+ private readonly _metadata: PluginMetadata = {
+ name: "Basic Plugin Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "A minimal example demonstrating basic plugin creation and lifecycle",
+ icon: "icon.png"
+ };
+
+ /**
+ * Getter for plugin metadata.
+ * Required by FDOInterface.
+ */
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ /**
+ * Initialize the plugin.
+ *
+ * This method is called once when the plugin is loaded by the FDO application.
+ * Use this method to:
+ * - Set up initial state
+ * - Register message handlers (covered in example 02)
+ * - Initialize storage (covered in example 03)
+ * - Perform any setup tasks
+ *
+ * COMMON PITFALL: Do not perform long-running operations in init().
+ * The init() method should complete quickly to avoid blocking the application startup.
+ * For async operations, consider using promises or deferring work to handlers.
+ */
+ init(): void {
+ try {
+ this.log("BasicPlugin initialized!");
+
+
+ } catch (error) {
+ this.error(error as Error);
+
+ }
+ }
+
+ /**
+ * Render the plugin UI.
+ *
+ * This method is called when the plugin needs to display its user interface.
+ * It should return an HTML string that defines the plugin's UI.
+ *
+ * The returned HTML will be rendered in the FDO application's plugin container.
+ *
+ * CUSTOMIZE HERE: Replace this simple HTML with your own UI
+ *
+ * COMMON PITFALL: The render() method should return quickly.
+ * For complex UIs, consider using the DOM helper classes (see example 05)
+ * or breaking the UI into smaller components.
+ *
+ * @returns HTML string representing the plugin's user interface
+ */
+ render(): string {
+ try {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+
+ // Create main container
+ const mainContent = domNested.createBlockDiv([
+ // Header section
+ domText.createHText(1, `Welcome to ${this._metadata.name}`),
+ domText.createPText([
+ domText.createStrongText('Version:'),
+ ` ${this._metadata.version}`
+ ].join('')),
+ domText.createPText([
+ domText.createStrongText('Author:'),
+ ` ${this._metadata.author}`
+ ].join('')),
+ domText.createPText(this._metadata.description),
+
+ // What's Next section
+ domNested.createBlockDiv([
+ domText.createHText(3, 'What\'s Next?'),
+ domText.createPText('This is a basic plugin example. To learn more advanced features:'),
+ domNested.createList([
+ domNested.createListItem(['See example 02 for interactive UI with buttons and forms']),
+ domNested.createListItem(['See example 03 for data persistence']),
+ domNested.createListItem(['See example 04 for UI extensions (quick actions and side panels)']),
+ domNested.createListItem(['See example 05 for advanced DOM generation and styling'])
+ ])
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#f0f0f0',
+ borderRadius: '5px'
+ }
+ })
+ ], {
+ style: {
+ padding: '20px',
+ fontFamily: 'Arial, sans-serif'
+ }
+ });
+
+ return mainContent;
+
+ } catch (error) {
+ this.error(error as Error);
+
+ return `
+
+
Error rendering plugin
+
An error occurred while rendering the plugin UI. Check the console for details.
+
+ `;
+ }
+ }
+}
+
+/**
+ * Key Takeaways:
+ *
+ * 1. All plugins must extend FDO_SDK and implement FDOInterface
+ * 2. The metadata property is required and must include name, version, author, description, and icon
+ * 3. The init() method is called once during plugin load - use it for setup
+ * 4. The render() method returns HTML that defines the plugin's UI
+ * 5. Use this.log() for informational messages and this.error() for error logging
+ * 6. Always handle errors in init() and render() to prevent plugin failures
+ *
+ * Next Steps:
+ * - Copy this file to your project
+ * - Customize the metadata with your plugin information
+ * - Modify the render() method to display your own UI
+ * - Add your initialization logic to the init() method
+ * - Test your plugin in the FDO application
+ */
diff --git a/examples/02-interactive-plugin.ts b/examples/02-interactive-plugin.ts
new file mode 100644
index 0000000..b84d3ca
--- /dev/null
+++ b/examples/02-interactive-plugin.ts
@@ -0,0 +1,419 @@
+/**
+ * Example 2: Interactive Plugin with UI Actions
+ *
+ * This example demonstrates how to create interactive functionality where users can
+ * click buttons, fill forms, and trigger custom actions through the message-based
+ * communication system.
+ *
+ * Compatible with SDK v1.x
+ *
+ * Learning Objectives:
+ * - Register and handle custom message handlers
+ * - Create interactive UI elements (buttons, forms)
+ * - Process user input and update the UI
+ * - Handle asynchronous operations
+ * - Implement proper error handling for user interactions
+ *
+ * Expected Output:
+ * When this plugin runs in the FDO application, it will:
+ * 1. Display a form with a text input and submit button
+ * 2. Show a counter with increment/decrement buttons
+ * 3. Process button clicks and form submissions
+ * 4. Update the UI dynamically based on user actions
+ * 5. Log all user interactions to the console
+ */
+
+import { FDO_SDK, FDOInterface, PluginMetadata, PluginRegistry, DOMText, DOMNested, DOMButton, DOMInput } from "../src";
+
+declare global {
+ interface Window {
+ fdoSDK: {
+ sendMessage: (handler: string, data: any) => Promise;
+ };
+ }
+}
+
+/**
+ * InteractivePlugin demonstrates user interaction handling.
+ *
+ * Key concepts:
+ * - Message handlers: Functions that respond to UI events
+ * - Handler registration: Done in init() using PluginRegistry.registerHandler()
+ * - Async patterns: Handlers can be async for long-running operations
+ */
+export default class InteractivePlugin extends FDO_SDK implements FDOInterface {
+ /**
+ * Plugin metadata.
+ *
+ * CUSTOMIZE HERE: Replace with your plugin information
+ */
+ private readonly _metadata: PluginMetadata = {
+ name: "Interactive Plugin Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "Demonstrates interactive UI with buttons, forms, and message handlers",
+ icon: "icon.png"
+ };
+
+ /**
+ * Internal state for the counter example.
+ * In a real plugin, you might use the storage system (see example 03) for persistence.
+ */
+ private counter: number = 0;
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ /**
+ * Initialize the plugin and register message handlers.
+ *
+ * Message handlers are functions that respond to UI events.
+ * They are registered by name and can be triggered from UI elements.
+ *
+ * COMMON PITFALL: Always register handlers in init(), not in render().
+ * Handlers registered in render() will be re-registered every time the UI updates,
+ * potentially causing duplicate handler registrations.
+ */
+ init(): void {
+ try {
+ this.log("InteractivePlugin initialized!");
+
+ PluginRegistry.registerHandler("incrementCounter", (data: any) => {
+ return this.handleIncrement(data);
+ });
+
+ PluginRegistry.registerHandler("decrementCounter", (data: any) => {
+ return this.handleDecrement(data);
+ });
+
+ PluginRegistry.registerHandler("submitForm", async (data: any) => {
+ return await this.handleFormSubmit(data);
+ });
+
+
+ } catch (error) {
+ this.error(error as Error);
+ }
+ }
+
+ /**
+ * Handle increment button click.
+ *
+ * @param data - Data passed from the UI (can include button context, user info, etc.)
+ * @returns Result object that can be used to update the UI
+ */
+ private handleIncrement(data: any): any {
+ try {
+ this.counter++;
+ this.log(`Counter incremented to ${this.counter}`);
+
+ return {
+ success: true,
+ counter: this.counter,
+ message: `Counter is now ${this.counter}`
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to increment counter"
+ };
+ }
+ }
+
+ /**
+ * Handle decrement button click.
+ *
+ * @param data - Data passed from the UI
+ * @returns Result object
+ */
+ private handleDecrement(data: any): any {
+ try {
+ this.counter--;
+ this.log(`Counter decremented to ${this.counter}`);
+
+ return {
+ success: true,
+ counter: this.counter,
+ message: `Counter is now ${this.counter}`
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to decrement counter"
+ };
+ }
+ }
+
+ /**
+ * Handle form submission.
+ *
+ * This demonstrates async handler patterns for operations that might take time
+ * (e.g., API calls, file operations, complex computations).
+ *
+ * COMMON PITFALL: For async operations, always use async/await or promises.
+ * Don't block the UI with synchronous long-running operations.
+ *
+ * @param data - Form data from the UI
+ * @returns Promise resolving to result object
+ */
+ private async handleFormSubmit(data: any): Promise {
+ try {
+ this.log(`Form submitted with data: ${JSON.stringify(data)}`);
+
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+
+ const userName = data.userName || "Guest";
+
+ return {
+ success: true,
+ message: `Welcome, ${userName}! Your form has been processed.`,
+ timestamp: new Date().toISOString()
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to process form submission"
+ };
+ }
+ }
+
+ /**
+ * Render the interactive UI.
+ *
+ * This example shows how to create buttons and forms that trigger message handlers.
+ *
+ * CUSTOMIZE HERE: Replace with your own interactive UI
+ *
+ * @returns HTML string with interactive elements
+ */
+ render(): string {
+ try {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domButton = new DOMButton();
+ const domInput = new DOMInput("userName", {
+ style: {
+ padding: '8px',
+ width: '300px',
+ marginBottom: '10px'
+ }
+ });
+
+ // Create main container
+ const mainContent = domNested.createBlockDiv([
+ // Header section
+ domText.createHText(1, this._metadata.name),
+ domText.createPText(this._metadata.description),
+
+ // Counter Example
+ domNested.createBlockDiv([
+ domText.createHText(3, 'Counter Example'),
+ domText.createPText([
+ 'Current count: ',
+ domText.createStrongText(this.counter.toString())
+ ].join('')),
+
+ domButton.createButton('Increment',
+ () => window.fdoSDK.sendMessage('incrementCounter', {}),
+ {
+ style: {
+ padding: '10px 20px',
+ marginRight: '10px',
+ cursor: 'pointer'
+ }
+ }
+ ),
+
+ domButton.createButton('Decrement',
+ () => window.fdoSDK.sendMessage('decrementCounter', {}),
+ {
+ style: {
+ padding: '10px 20px',
+ cursor: 'pointer'
+ }
+ }
+ ),
+
+ domText.createPText(
+ 'Click the buttons to increment or decrement the counter. The counter value is maintained in plugin state.',
+ {
+ style: {
+ marginTop: '10px',
+ fontSize: '12px',
+ color: '#666'
+ }
+ }
+ )
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#f0f0f0',
+ borderRadius: '5px'
+ }
+ }),
+
+ // Form Example
+ domNested.createBlockDiv([
+ domText.createHText(3, 'Form Example'),
+ domNested.createForm([
+ domText.createLabelText('Enter your name:', 'userName', {
+ style: {
+ display: 'block',
+ marginBottom: '5px'
+ }
+ }),
+
+ domInput.createInput('text'),
+
+ domButton.createButton('Submit',
+ () => handleFormSubmit(),
+ {
+ style: {
+ padding: '10px 20px',
+ cursor: 'pointer',
+ backgroundColor: '#007bff',
+ color: 'white',
+ border: 'none',
+ borderRadius: '3px'
+ }
+ })
+ ], {
+ customAttributes: {
+ onsubmit: 'event.preventDefault(); handleFormSubmit();'
+ }
+ }),
+
+ domNested.createBlockDiv([], {
+ customAttributes: {
+ id: 'form-result'
+ },
+ style: {
+ marginTop: '10px'
+ }
+ }),
+
+ domText.createPText(
+ 'Enter your name and submit the form to see async handler processing.',
+ {
+ style: {
+ marginTop: '10px',
+ fontSize: '12px',
+ color: '#666'
+ }
+ }
+ )
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#e8f4f8',
+ borderRadius: '5px'
+ }
+ }),
+
+ ``,
+
+ // Key Concepts
+ domNested.createBlockDiv([
+ domText.createHText(3, 'Key Concepts'),
+ domNested.createList([
+ domNested.createListItem([
+ domText.createStrongText('Message Handlers:'),
+ ' Functions registered in init() that respond to UI events'
+ ]),
+ domNested.createListItem([
+ domText.createStrongText('Handler Registration:'),
+ ' Use PluginRegistry.registerHandler(name, function)'
+ ]),
+ domNested.createListItem([
+ domText.createStrongText('Async Patterns:'),
+ ' Handlers can be async for long-running operations'
+ ]),
+ domNested.createListItem([
+ domText.createStrongText('Error Handling:'),
+ ' Always wrap handler logic in try-catch blocks'
+ ]),
+ domNested.createListItem([
+ domText.createStrongText('State Management:'),
+ ' Use class properties for temporary state, storage for persistence'
+ ])
+ ])
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#fff3cd',
+ borderRadius: '5px'
+ }
+ })
+ ], {
+ style: {
+ padding: '20px',
+ fontFamily: 'Arial, sans-serif'
+ }
+ });
+
+ return mainContent;
+
+ } catch (error) {
+ this.error(error as Error);
+ return `
+
+
Error rendering plugin
+
An error occurred while rendering the plugin UI. Check the console for details.
+
+ `;
+ }
+ }
+}
+
+/**
+ * Key Takeaways:
+ *
+ * 1. Register message handlers in init() using PluginRegistry.registerHandler()
+ * 2. Handlers can be synchronous or asynchronous (use async/await for long operations)
+ * 3. Always handle errors in handlers to prevent UI failures
+ * 4. Use class properties for temporary state, storage system for persistence
+ * 5. Validate user input before processing to prevent security issues
+ * 6. Return structured data from handlers to enable UI updates
+ *
+ * Common Pitfalls to Avoid:
+ * - Don't register handlers in render() - do it in init()
+ * - Don't forget error handling in handlers
+ * - Don't block the UI with synchronous long-running operations
+ * - Don't trust user input - always validate
+ *
+ * Next Steps:
+ * - See example 03 for data persistence with the storage system
+ * - See example 04 for UI extensions (quick actions and side panels)
+ * - See example 05 for advanced DOM generation with styling
+ */
diff --git a/examples/03-persistence-plugin.ts b/examples/03-persistence-plugin.ts
new file mode 100644
index 0000000..9e8b85f
--- /dev/null
+++ b/examples/03-persistence-plugin.ts
@@ -0,0 +1,556 @@
+/**
+ * Example 3: Data Persistence Plugin
+ *
+ * This example demonstrates how to save and retrieve user data across application sessions
+ * using the FDO SDK storage system. It covers both in-memory (StoreDefault) and file-based
+ * (StoreJson) storage backends.
+ *
+ * Compatible with SDK v1.x
+ *
+ * Learning Objectives:
+ * - Use StoreDefault for temporary data (in-memory)
+ * - Use StoreJson for persistent data (file-based)
+ * - Implement proper key naming conventions
+ * - Handle storage errors gracefully
+ * - Save and retrieve different data types
+ *
+ * Expected Output:
+ * When this plugin runs in the FDO application, it will:
+ * 1. Display saved user preferences (name, theme, notifications)
+ * 2. Provide forms to update preferences
+ * 3. Show temporary session data (visit count, last action)
+ * 4. Persist preference changes across application restarts
+ * 5. Clear temporary data on each session
+ */
+
+import { FDO_SDK, FDOInterface, PluginMetadata, PluginRegistry, DOMText, DOMNested, DOMButton, DOMInput } from "../src";
+
+declare global {
+ interface Window {
+ fdoSDK: {
+ sendMessage: (handler: string, data: any) => Promise;
+ };
+ }
+}
+
+/**
+ * PersistencePlugin demonstrates data storage and retrieval.
+ *
+ * Key concepts:
+ * - StoreDefault: In-memory storage (data lost on restart)
+ * - StoreJson: File-based storage (data persists across restarts)
+ * - Key naming: Use namespaced keys to avoid conflicts
+ * - Error handling: Storage operations can fail
+ */
+export default class PersistencePlugin extends FDO_SDK implements FDOInterface {
+ /**
+ * Plugin metadata.
+ *
+ * CUSTOMIZE HERE: Replace with your plugin information
+ */
+ private readonly _metadata: PluginMetadata = {
+ name: "Persistence Plugin Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "Demonstrates data persistence with StoreDefault and StoreJson",
+ icon: "icon.png"
+ };
+
+ /**
+ * Storage instances.
+ * StoreDefault is automatically available without registration.
+ * StoreJson must be registered before use.
+ */
+ private tempStore: any; // StoreDefault for temporary data
+ private persistentStore: any; // StoreJson for persistent data
+
+ /**
+ * Key naming convention.
+ * Use namespaced keys to avoid conflicts with other plugins.
+ * Format: "pluginName:category:keyName"
+ *
+ * COMMON PITFALL: Always namespace your keys to prevent conflicts.
+ * Without namespacing, multiple plugins might overwrite each other's data.
+ */
+ private readonly KEYS = {
+ USER_NAME: "persistencePlugin:prefs:userName",
+ USER_THEME: "persistencePlugin:prefs:theme",
+ NOTIFICATIONS_ENABLED: "persistencePlugin:prefs:notifications",
+
+ VISIT_COUNT: "persistencePlugin:session:visitCount",
+ LAST_ACTION: "persistencePlugin:session:lastAction",
+ SESSION_START: "persistencePlugin:session:startTime"
+ };
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ /**
+ * Initialize the plugin and set up storage.
+ *
+ * COMMON PITFALL: Always initialize storage in init(), not in render().
+ * Storage operations in render() can cause performance issues and data inconsistencies.
+ */
+ init(): void {
+ try {
+ this.log("PersistencePlugin initialized!");
+
+ this.tempStore = PluginRegistry.useStore("default");
+
+ try {
+ this.persistentStore = PluginRegistry.useStore("json");
+ } catch (error) {
+ this.log("StoreJson not available, using StoreDefault for all storage");
+ this.persistentStore = this.tempStore;
+ }
+
+ this.initializeSessionData();
+
+ PluginRegistry.registerHandler("savePreferences", (data: any) => {
+ return this.handleSavePreferences(data);
+ });
+
+ PluginRegistry.registerHandler("clearPreferences", (data: any) => {
+ return this.handleClearPreferences(data);
+ });
+
+ PluginRegistry.registerHandler("recordAction", (data: any) => {
+ return this.handleRecordAction(data);
+ });
+
+
+ } catch (error) {
+ this.error(error as Error);
+ }
+ }
+
+ /**
+ * Initialize session data in temporary storage.
+ * This data is cleared on each application restart.
+ */
+ private initializeSessionData(): void {
+ try {
+ const visitCount = this.tempStore.get(this.KEYS.VISIT_COUNT) || 0;
+ this.tempStore.set(this.KEYS.VISIT_COUNT, visitCount + 1);
+
+ this.tempStore.set(this.KEYS.SESSION_START, new Date().toISOString());
+
+ this.log(`Session initialized. Visit count: ${visitCount + 1}`);
+
+ } catch (error) {
+ this.error(error as Error);
+ }
+ }
+
+ /**
+ * Handle saving user preferences to persistent storage.
+ *
+ * @param data - Preference data from the UI
+ * @returns Result object
+ */
+ private handleSavePreferences(data: any): any {
+ try {
+ if (!data) {
+ return {
+ success: false,
+ error: "No data provided"
+ };
+ }
+
+ if (data.userName !== undefined) {
+ this.persistentStore.set(this.KEYS.USER_NAME, data.userName);
+ }
+
+ if (data.theme !== undefined) {
+ this.persistentStore.set(this.KEYS.USER_THEME, data.theme);
+ }
+
+ if (data.notificationsEnabled !== undefined) {
+ this.persistentStore.set(this.KEYS.NOTIFICATIONS_ENABLED, data.notificationsEnabled);
+ }
+
+ this.log("Preferences saved successfully");
+
+ return {
+ success: true,
+ message: "Preferences saved successfully",
+ savedData: data
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to save preferences"
+ };
+ }
+ }
+
+ /**
+ * Handle clearing all preferences.
+ *
+ * @param data - Optional data
+ * @returns Result object
+ */
+ private handleClearPreferences(data: any): any {
+ try {
+ this.persistentStore.remove(this.KEYS.USER_NAME);
+ this.persistentStore.remove(this.KEYS.USER_THEME);
+ this.persistentStore.remove(this.KEYS.NOTIFICATIONS_ENABLED);
+
+ this.log("Preferences cleared");
+
+ return {
+ success: true,
+ message: "All preferences have been cleared"
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to clear preferences"
+ };
+ }
+ }
+
+ /**
+ * Handle recording a user action to temporary storage.
+ *
+ * @param data - Action data
+ * @returns Result object
+ */
+ private handleRecordAction(data: any): any {
+ try {
+ const action = data.action || "unknown";
+ const timestamp = new Date().toISOString();
+
+ this.tempStore.set(this.KEYS.LAST_ACTION, {
+ action,
+ timestamp
+ });
+
+ this.log(`Action recorded: ${action}`);
+
+ return {
+ success: true,
+ action,
+ timestamp
+ };
+
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to record action"
+ };
+ }
+ }
+
+ /**
+ * Render the persistence UI.
+ *
+ * CUSTOMIZE HERE: Replace with your own storage UI
+ *
+ * @returns HTML string
+ */
+ render(): string {
+ try {
+ const userName = this.persistentStore.get(this.KEYS.USER_NAME) || "Not set";
+ const theme = this.persistentStore.get(this.KEYS.USER_THEME) || "light";
+ const notificationsEnabled = this.persistentStore.get(this.KEYS.NOTIFICATIONS_ENABLED) ?? true;
+
+ const visitCount = this.tempStore.get(this.KEYS.VISIT_COUNT) || 0;
+ const lastAction = this.tempStore.get(this.KEYS.LAST_ACTION);
+ const sessionStart = this.tempStore.get(this.KEYS.SESSION_START);
+
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domButton = new DOMButton();
+ const domInput = new DOMInput("", {});
+
+ // Create main container
+ const mainContent = domNested.createBlockDiv([
+ // Header section
+ domText.createHText(1, this._metadata.name),
+ domText.createPText(this._metadata.description),
+
+ // Persistent Data Section
+ domNested.createBlockDiv([
+ domText.createHText(3, "Persistent Preferences (StoreJson)"),
+ domText.createPText("These preferences are saved to a file and persist across application restarts.", {
+ style: {
+ fontSize: '12px',
+ color: '#666',
+ marginBottom: '15px'
+ }
+ }),
+
+ // Current values display
+ domNested.createBlockDiv([
+ domText.createPText([
+ domText.createStrongText("User Name:"),
+ ` ${userName}`
+ ].join(''), { style: { marginBottom: '10px' } }),
+
+ domText.createPText([
+ domText.createStrongText("Theme:"),
+ ` ${theme}`
+ ].join(''), { style: { marginBottom: '10px' } }),
+
+ domText.createPText([
+ domText.createStrongText("Notifications:"),
+ ` ${notificationsEnabled ? "Enabled" : "Disabled"}`
+ ].join(''), { style: { marginBottom: '10px' } })
+ ]),
+
+ // Preferences form
+ domNested.createForm([
+ // Username input
+ domNested.createBlockDiv([
+ domText.createLabelText("User Name:", "userName", {
+ style: { display: 'block', marginBottom: '5px' }
+ }),
+ new DOMInput("userName", {
+ style: { padding: '8px', width: '300px' }
+ }).createInput("text")
+ ], { style: { marginBottom: '10px' } }),
+
+ // Theme select
+ domNested.createBlockDiv([
+ domText.createLabelText("Theme:", "theme", {
+ style: { display: 'block', marginBottom: '5px' }
+ }),
+ new DOMInput("theme", {
+ style: { padding: '8px', width: '316px' }
+ }).createSelect([
+ domInput.createOption("Light", "light", theme === 'light'),
+ domInput.createOption("Dark", "dark", theme === 'dark'),
+ domInput.createOption("Auto", "auto", theme === 'auto')
+ ])
+ ], { style: { marginBottom: '10px' } }),
+
+ // Notifications checkbox
+ domNested.createBlockDiv([
+ domText.createLabelText([
+ new DOMInput("notifications", {}).createInput("checkbox"),
+ " Enable Notifications"
+ ].join(''), "notifications")
+ ], { style: { marginBottom: '15px' } }),
+
+ // Form buttons
+ domNested.createBlockDiv([
+ domButton.createButton("Save Preferences",
+ () => {
+ const form = document.querySelector('form');
+ if (form) form.dispatchEvent(new Event('submit'));
+ },
+ {
+ style: {
+ padding: '10px 20px',
+ marginRight: '10px',
+ cursor: 'pointer',
+ backgroundColor: '#007bff',
+ color: 'white',
+ border: 'none',
+ borderRadius: '3px'
+ }
+ }
+ ),
+
+ domButton.createButton("Clear All",
+ () => window.fdoSDK.sendMessage('clearPreferences', {}),
+ {
+ style: {
+ padding: '10px 20px',
+ cursor: 'pointer',
+ backgroundColor: '#dc3545',
+ color: 'white',
+ border: 'none',
+ borderRadius: '3px'
+ }
+ }
+ )
+ ])
+ ], {
+ customAttributes: {
+ onsubmit: 'event.preventDefault(); savePreferences();'
+ },
+ style: { marginTop: '15px' }
+ }),
+
+ // Result area
+ domNested.createBlockDiv([], {
+ customAttributes: { id: 'prefs-result' },
+ style: { marginTop: '10px' }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#e8f4f8',
+ borderRadius: '5px'
+ }
+ }),
+
+ // Temporary Data Section
+ domNested.createBlockDiv([
+ domText.createHText(3, "Session Data (StoreDefault)"),
+ domText.createPText("This data is stored in memory and cleared when the application restarts.", {
+ style: {
+ fontSize: '12px',
+ color: '#666',
+ marginBottom: '15px'
+ }
+ }),
+
+ domNested.createBlockDiv([
+ domText.createPText([
+ domText.createStrongText("Visit Count:"),
+ ` ${visitCount}`
+ ].join(''), { style: { marginBottom: '10px' } }),
+
+ domText.createPText([
+ domText.createStrongText("Session Started:"),
+ ` ${sessionStart ? new Date(sessionStart).toLocaleString() : 'N/A'}`
+ ].join(''), { style: { marginBottom: '10px' } }),
+
+ domText.createPText([
+ domText.createStrongText("Last Action:"),
+ ` ${lastAction ? `${lastAction.action} at ${new Date(lastAction.timestamp).toLocaleTimeString()}` : 'None'}`
+ ].join(''), { style: { marginBottom: '15px' } })
+ ]),
+
+ domButton.createButton("Record Action",
+ () => window.fdoSDK.sendMessage('recordAction', { action: 'Button Click' }),
+ {
+ style: {
+ padding: '10px 20px',
+ cursor: 'pointer'
+ }
+ }
+ )
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#f0f0f0',
+ borderRadius: '5px'
+ }
+ }),
+
+ // Key Concepts Section
+ domNested.createBlockDiv([
+ domText.createHText(3, "Storage Concepts"),
+ domNested.createList([
+ domNested.createListItem([
+ domText.createStrongText("StoreDefault:"),
+ " In-memory storage, data cleared on restart"
+ ]),
+ domNested.createListItem([
+ domText.createStrongText("StoreJson:"),
+ " File-based storage, data persists across restarts"
+ ]),
+ domNested.createListItem([
+ domText.createStrongText("Key Naming:"),
+ " Use namespaced keys (pluginName:category:key)"
+ ]),
+ domNested.createListItem([
+ domText.createStrongText("Error Handling:"),
+ " Always wrap storage operations in try-catch"
+ ]),
+ domNested.createListItem([
+ domText.createStrongText("Data Types:"),
+ " Stores support strings, numbers, booleans, objects, arrays"
+ ])
+ ])
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#fff3cd',
+ borderRadius: '5px'
+ }
+ }),
+
+ // JavaScript for handling preferences
+ domText.createText(`
+
+ `)
+ ], {
+ style: {
+ padding: '20px',
+ fontFamily: 'Arial, sans-serif'
+ }
+ });
+
+ return mainContent;
+
+ } catch (error) {
+ this.error(error as Error);
+ return `
+
+
Error rendering plugin
+
An error occurred while rendering the plugin UI. Check the console for details.
+
+ `;
+ }
+ }
+}
+
+/**
+ * Key Takeaways:
+ *
+ * 1. Use StoreDefault for temporary data (in-memory, cleared on restart)
+ * 2. Use StoreJson for persistent data (file-based, survives restarts)
+ * 3. Always namespace your keys to avoid conflicts (pluginName:category:key)
+ * 4. Wrap all storage operations in try-catch blocks
+ * 5. Initialize storage in init(), not in render()
+ * 6. Storage supports all JSON-serializable types (strings, numbers, objects, arrays)
+ *
+ * Common Pitfalls to Avoid:
+ * - Don't use generic key names - always namespace them
+ * - Don't forget error handling for storage operations
+ * - Don't perform storage operations in render() - do it in handlers
+ * - Don't assume StoreJson is always available - have a fallback
+ *
+ * Next Steps:
+ * - See example 04 for UI extensions (quick actions and side panels)
+ * - See example 05 for advanced DOM generation with styling
+ */
diff --git a/examples/04-ui-extensions-plugin.ts b/examples/04-ui-extensions-plugin.ts
new file mode 100644
index 0000000..1a13082
--- /dev/null
+++ b/examples/04-ui-extensions-plugin.ts
@@ -0,0 +1,733 @@
+/**
+ * Example 4: Quick Actions and Side Panel Integration
+ *
+ * This example demonstrates how to extend the FDO application's UI by adding quick action
+ * shortcuts and side panel menu items using mixins. These UI extensions make your plugin
+ * more discoverable and accessible to users.
+ *
+ * Compatible with SDK v1.x
+ *
+ * Learning Objectives:
+ * - Use QuickActionMixin to add quick action shortcuts
+ * - Use SidePanelMixin to add side panel menu items
+ * - Route messages from UI extensions to plugin handlers
+ * - Configure icons and labels for UI extensions
+ * - Handle errors in UI extension operations
+ *
+ * Expected Output:
+ * When this plugin runs in the FDO application, it will:
+ * 1. Add quick actions to the application's quick action menu
+ * 2. Add a side panel with multiple menu items
+ * 3. Route quick action and side panel selections to appropriate handlers
+ * 4. Display different content based on which UI extension was triggered
+ * 5. Log all UI extension interactions
+ */
+
+import {
+ FDO_SDK,
+ FDOInterface,
+ PluginMetadata,
+ PluginRegistry,
+ QuickActionMixin,
+ SidePanelMixin,
+ QuickAction,
+ SidePanelConfig,
+ DOMText,
+ DOMNested,
+ DOMButton,
+ DOMInput,
+ DOMLink
+} from "../src";
+
+/**
+ * UIExtensionsPlugin demonstrates UI extension capabilities.
+ *
+ * Key concepts:
+ * - Mixins: Add functionality to plugin classes
+ * - QuickActionMixin: Adds defineQuickActions() method
+ * - SidePanelMixin: Adds defineSidePanel() method
+ * - Message routing: UI extensions trigger message handlers
+ *
+ * COMMON PITFALL: Mixins must be applied using the mixin pattern.
+ * This example shows the correct way to apply multiple mixins.
+ */
+
+const UIExtensionsPluginBase = SidePanelMixin(QuickActionMixin(FDO_SDK));
+
+export default class UIExtensionsPlugin extends UIExtensionsPluginBase implements FDOInterface {
+ /**
+ * Plugin metadata.
+ *
+ * CUSTOMIZE HERE: Replace with your plugin information
+ */
+ private readonly _metadata: PluginMetadata = {
+ name: "UI Extensions Plugin Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "Demonstrates quick actions and side panel integration using mixins",
+ icon: "icon.png"
+ };
+
+ /**
+ * Current view state.
+ * Used to track which UI extension was triggered.
+ */
+ private currentView: string = "default";
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ /**
+ * Initialize the plugin and register message handlers.
+ *
+ * COMMON PITFALL: Register handlers for all message types used in UI extensions.
+ * If a handler is missing, clicking the UI extension will result in an error.
+ */
+ init(): void {
+ try {
+ this.log("UIExtensionsPlugin initialized!");
+
+ PluginRegistry.registerHandler("quickSearch", (data: any) => {
+ return this.handleQuickSearch(data);
+ });
+
+ PluginRegistry.registerHandler("quickCreate", (data: any) => {
+ return this.handleQuickCreate(data);
+ });
+
+ PluginRegistry.registerHandler("quickSettings", (data: any) => {
+ return this.handleQuickSettings(data);
+ });
+
+ PluginRegistry.registerHandler("showDashboard", (data: any) => {
+ return this.handleShowDashboard(data);
+ });
+
+ PluginRegistry.registerHandler("showReports", (data: any) => {
+ return this.handleShowReports(data);
+ });
+
+ PluginRegistry.registerHandler("showSettings", (data: any) => {
+ return this.handleShowSettings(data);
+ });
+
+
+ } catch (error) {
+ this.error(error as Error);
+ }
+ }
+
+ /**
+ * Define quick actions for the plugin.
+ *
+ * Quick actions appear in the FDO application's quick action menu and provide
+ * shortcuts to common plugin functionality.
+ *
+ * CUSTOMIZE HERE: Define your own quick actions
+ *
+ * @returns Array of QuickAction objects
+ */
+ defineQuickActions(): QuickAction[] {
+ try {
+ return [
+ {
+ name: "Search Plugin Data",
+ message_type: "quickSearch",
+ subtitle: "Search through plugin data",
+ icon: "search.png"
+ },
+ {
+ name: "Create New Item",
+ message_type: "quickCreate",
+ subtitle: "Create a new item in the plugin",
+ icon: "create.png"
+ },
+ {
+ name: "Plugin Settings",
+ message_type: "quickSettings",
+ icon: "settings.png"
+ }
+ ];
+ } catch (error) {
+ this.error(error as Error);
+ return [];
+ }
+ }
+
+ /**
+ * Define side panel configuration for the plugin.
+ *
+ * The side panel provides a persistent menu in the FDO application's UI
+ * for accessing plugin features.
+ *
+ * CUSTOMIZE HERE: Define your own side panel structure
+ *
+ * @returns SidePanelConfig object
+ */
+ defineSidePanel(): SidePanelConfig {
+ try {
+ return {
+ icon: "panel.png",
+ label: "UI Extensions",
+ submenu_list: [
+ {
+ id: "dashboard",
+ name: "Dashboard",
+ message_type: "showDashboard"
+ },
+ {
+ id: "reports",
+ name: "Reports",
+ message_type: "showReports"
+ },
+ {
+ id: "settings",
+ name: "Settings",
+ message_type: "showSettings"
+ }
+ ]
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ icon: "panel.png",
+ label: "UI Extensions",
+ submenu_list: []
+ };
+ }
+ }
+
+ /**
+ * Handle quick search action.
+ *
+ * @param data - Data from the quick action
+ * @returns Result object
+ */
+ private handleQuickSearch(data: any): any {
+ try {
+ this.currentView = "search";
+ this.log("Quick search triggered");
+
+ return {
+ success: true,
+ view: "search",
+ message: "Search view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to activate search"
+ };
+ }
+ }
+
+ /**
+ * Handle quick create action.
+ *
+ * @param data - Data from the quick action
+ * @returns Result object
+ */
+ private handleQuickCreate(data: any): any {
+ try {
+ this.currentView = "create";
+ this.log("Quick create triggered");
+
+ return {
+ success: true,
+ view: "create",
+ message: "Create view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to activate create"
+ };
+ }
+ }
+
+ /**
+ * Handle quick settings action.
+ *
+ * @param data - Data from the quick action
+ * @returns Result object
+ */
+ private handleQuickSettings(data: any): any {
+ try {
+ this.currentView = "settings";
+ this.log("Quick settings triggered");
+
+ return {
+ success: true,
+ view: "settings",
+ message: "Settings view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to activate settings"
+ };
+ }
+ }
+
+ /**
+ * Handle show dashboard from side panel.
+ *
+ * @param data - Data from the side panel
+ * @returns Result object
+ */
+ private handleShowDashboard(data: any): any {
+ try {
+ this.currentView = "dashboard";
+ this.log("Dashboard view triggered from side panel");
+
+ return {
+ success: true,
+ view: "dashboard",
+ message: "Dashboard view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to show dashboard"
+ };
+ }
+ }
+
+ /**
+ * Handle show reports from side panel.
+ *
+ * @param data - Data from the side panel
+ * @returns Result object
+ */
+ private handleShowReports(data: any): any {
+ try {
+ this.currentView = "reports";
+ this.log("Reports view triggered from side panel");
+
+ return {
+ success: true,
+ view: "reports",
+ message: "Reports view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to show reports"
+ };
+ }
+ }
+
+ /**
+ * Handle show settings from side panel.
+ *
+ * @param data - Data from the side panel
+ * @returns Result object
+ */
+ private handleShowSettings(data: any): any {
+ try {
+ this.currentView = "settings";
+ this.log("Settings view triggered from side panel");
+
+ return {
+ success: true,
+ view: "settings",
+ message: "Settings view activated"
+ };
+ } catch (error) {
+ this.error(error as Error);
+ return {
+ success: false,
+ error: "Failed to show settings"
+ };
+ }
+ }
+
+ /**
+ * Render the UI based on current view.
+ *
+ * CUSTOMIZE HERE: Replace with your own view rendering logic
+ *
+ * @returns HTML string
+ */
+ render(): string {
+ try {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+
+ // Main container
+ const mainContent = domNested.createBlockDiv([
+ domText.createHText(1, this._metadata.name),
+ domText.createPText(this._metadata.description),
+
+ // Dynamic content based on current view
+ this.renderCurrentView(),
+
+ // UI Extensions Info
+ domNested.createBlockDiv([
+ domText.createHText(3, "UI Extensions"),
+ domText.createPText([
+ domText.createStrongText("Quick Actions:"),
+ " Access quick actions from the FDO application's quick action menu"
+ ].join('')),
+ domNested.createList([
+ "Search Plugin Data",
+ "Create New Item",
+ "Plugin Settings"
+ ]),
+
+ domText.createPText([
+ domText.createStrongText("Side Panel:"),
+ " Access features from the side panel menu"
+ ].join(''), { style: { marginTop: '15px' } }),
+ domNested.createList([
+ "Dashboard",
+ "Reports",
+ "Settings"
+ ])
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#fff3cd',
+ borderRadius: '5px'
+ }
+ })
+ ], {
+ style: {
+ padding: '20px',
+ fontFamily: 'Arial, sans-serif'
+ }
+ });
+
+ return mainContent;
+
+ } catch (error) {
+ this.error(error as Error);
+ const errorDomText = new DOMText();
+ const errorDomNested = new DOMNested();
+ return errorDomNested.createBlockDiv([
+ errorDomText.createHText(2, "Error rendering plugin"),
+ errorDomText.createPText("An error occurred while rendering the plugin UI. Check the console for details.")
+ ], {
+ style: {
+ padding: '20px',
+ color: 'red'
+ }
+ });
+ }
+ }
+
+ /**
+ * Render the current view based on the view state
+ */
+ private renderCurrentView(): string {
+ switch (this.currentView) {
+ case "search":
+ return this.renderSearchView();
+ case "create":
+ return this.renderCreateView();
+ case "dashboard":
+ return this.renderDashboardView();
+ case "reports":
+ return this.renderReportsView();
+ case "settings":
+ return this.renderSettingsView();
+ default:
+ return this.renderDefaultView();
+ }
+ }
+
+ /**
+ * Render the default view.
+ */
+ private renderDefaultView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Welcome to UI Extensions Example"),
+ domText.createPText("This plugin demonstrates quick actions and side panel integration."),
+ domText.createPText("Try the following:"),
+ domNested.createList([
+ "Use the quick action menu to trigger quick actions",
+ "Use the side panel to navigate between views",
+ "See how different views are rendered based on UI extension triggers"
+ ])
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#f0f0f0',
+ borderRadius: '5px'
+ }
+ });
+ }
+
+ /**
+ * Render the search view.
+ */
+ private renderSearchView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domInput = new DOMInput("search", {
+ style: {
+ padding: '10px',
+ width: '400px',
+ marginTop: '10px'
+ }
+ });
+ const domButton = new DOMButton();
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Search View"),
+ domText.createPText("This view was triggered by the \"Search Plugin Data\" quick action."),
+ domNested.createBlockDiv([
+ domInput.createInput("text"),
+ domButton.createButton("Search", () => {}, {
+ style: {
+ padding: '10px 20px',
+ marginLeft: '10px',
+ cursor: 'pointer'
+ }
+ })
+ ], {
+ style: {
+ display: 'flex',
+ alignItems: 'center'
+ }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#e8f4f8',
+ borderRadius: '5px'
+ }
+ });
+ }
+
+ /**
+ * Render the create view.
+ */
+ private renderCreateView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domInput = new DOMInput("create", {
+ style: {
+ padding: '10px',
+ width: '400px',
+ marginBottom: '10px'
+ }
+ });
+ const domButton = new DOMButton();
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Create View"),
+ domText.createPText("This view was triggered by the \"Create New Item\" quick action."),
+ domNested.createForm([
+ domNested.createBlockDiv([
+ domInput.createInput("text")
+ ], {
+ style: {
+ marginBottom: '10px'
+ }
+ }),
+ domNested.createBlockDiv([
+ domInput.createTextarea()
+ ], {
+ style: {
+ marginBottom: '10px'
+ }
+ }),
+ domButton.createButton("Create", () => {}, {
+ style: {
+ padding: '10px 20px',
+ cursor: 'pointer'
+ }
+ })
+ ], {
+ style: { marginTop: '10px' }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#d4edda',
+ borderRadius: '5px'
+ }
+ });
+ }
+
+ /**
+ * Render the dashboard view.
+ */
+ private renderDashboardView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+
+ const createMetricBlock = (title: string, value: string) => {
+ return domNested.createBlockDiv([
+ domText.createHText(4, title),
+ domText.createPText(value, {
+ style: {
+ fontSize: '24px',
+ fontWeight: 'bold'
+ }
+ })
+ ], {
+ style: {
+ padding: '15px',
+ backgroundColor: 'white',
+ borderRadius: '5px'
+ }
+ });
+ };
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Dashboard View"),
+ domText.createPText("This view was triggered from the side panel \"Dashboard\" menu item."),
+ domNested.createBlockDiv([
+ createMetricBlock("Metric 1", "42"),
+ createMetricBlock("Metric 2", "87%")
+ ], {
+ style: {
+ display: 'grid',
+ gridTemplateColumns: '1fr 1fr',
+ gap: '15px',
+ marginTop: '15px'
+ }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#cfe2ff',
+ borderRadius: '5px'
+ }
+ });
+ }
+
+ /**
+ * Render the reports view.
+ */
+ private renderReportsView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domLink = new DOMLink("reports", {
+ style: { color: '#007bff' }
+ });
+
+ const createReportItem = (title: string) => {
+ return domNested.createBlockDiv([
+ domText.createText(`${title} - `),
+ domText.createText(domLink.createLink("#", "View"))
+ ]);
+ };
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Reports View"),
+ domText.createPText("This view was triggered from the side panel \"Reports\" menu item."),
+ domNested.createList([
+ createReportItem("Monthly Report"),
+ createReportItem("Quarterly Report"),
+ createReportItem("Annual Report")
+ ], {
+ style: { marginTop: '10px' }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#f8d7da',
+ borderRadius: '5px'
+ }
+ });
+ }
+
+ /**
+ * Render the settings view.
+ */
+ private renderSettingsView(): string {
+ const domText = new DOMText();
+ const domNested = new DOMNested();
+ const domInput = new DOMInput("settings", {});
+ const domButton = new DOMButton();
+
+ const createCheckboxOption = (label: string, checked: boolean = false) => {
+ return domNested.createBlockDiv([
+ domText.createLabelText(label, "settings-" + label.toLowerCase().replace(/\s+/g, '-'), {
+ style: { display: 'block', marginBottom: '10px' }
+ }),
+ domInput.createInput(checked ? "checkbox" : "checkbox")
+ ]);
+ };
+
+ return domNested.createBlockDiv([
+ domText.createHText(3, "Settings View"),
+ domText.createPText("This view can be triggered from either the quick action or side panel."),
+ domNested.createForm([
+ createCheckboxOption("Enable notifications", true),
+ createCheckboxOption("Auto-save"),
+
+ domNested.createBlockDiv([
+ domText.createLabelText("Theme:", "settings-theme", {
+ style: { marginRight: '10px' }
+ }),
+ domInput.createSelect([
+ "Light",
+ "Dark",
+ "Auto"
+ ])
+ ], {
+ style: { marginBottom: '10px' }
+ }),
+
+ domButton.createButton("Save Settings", () => {}, {
+ style: {
+ padding: '10px 20px',
+ marginTop: '10px',
+ cursor: 'pointer'
+ }
+ })
+ ], {
+ style: { marginTop: '10px' }
+ })
+ ], {
+ style: {
+ marginTop: '20px',
+ padding: '15px',
+ backgroundColor: '#e2e3e5',
+ borderRadius: '5px'
+ }
+ });
+ }
+}
+
+/**
+ * Key Takeaways:
+ *
+ * 1. Use QuickActionMixin to add quick action shortcuts to the FDO application
+ * 2. Use SidePanelMixin to add side panel menu items
+ * 3. Apply mixins using the mixin pattern: SidePanelMixin(QuickActionMixin(FDO_SDK))
+ * 4. Define quick actions in defineQuickActions() method
+ * 5. Define side panel structure in defineSidePanel() method
+ * 6. Register handlers for all message types used in UI extensions
+ * 7. Always handle errors in defineQuickActions() and defineSidePanel()
+ *
+ * Common Pitfalls to Avoid:
+ * - Don't forget to register handlers for all UI extension message types
+ * - Don't throw errors in defineQuickActions() or defineSidePanel()
+ * - Don't forget to apply mixins to the base class
+ * - Don't use the same message_type for different actions without proper routing
+ *
+ * Next Steps:
+ * - See example 05 for advanced DOM generation with styling
+ * - Combine UI extensions with storage (example 03) for stateful plugins
+ * - Use interactive handlers (example 02) with UI extensions for dynamic behavior
+ */
diff --git a/examples/05-advanced-dom-plugin.ts b/examples/05-advanced-dom-plugin.ts
new file mode 100644
index 0000000..8acb924
--- /dev/null
+++ b/examples/05-advanced-dom-plugin.ts
@@ -0,0 +1,562 @@
+/**
+ * Example 5: Advanced DOM Generation
+ *
+ * This example demonstrates how to create rich, styled UI components using the SDK's
+ * DOM generation classes. It covers the various DOM helper classes (DOMText, DOMButton,
+ * DOMInput, etc.) and shows how to build complex interfaces with custom styling using
+ * CSS-in-JS via goober.
+ *
+ * Compatible with SDK v1.x
+ *
+ * Learning Objectives:
+ * - Use DOM helper classes for programmatic HTML generation
+ * - Apply CSS-in-JS styling with goober integration
+ * - Build complex nested UI structures
+ * - Create forms with multiple input types
+ * - Compose reusable UI components
+ *
+ * Expected Output:
+ * When this plugin runs in the FDO application, it will:
+ * 1. Display a styled card layout with custom CSS
+ * 2. Show a complex form with multiple input types
+ * 3. Demonstrate nested UI structures
+ * 4. Apply custom styles using CSS-in-JS
+ * 5. Render all elements using DOM helper classes
+ */
+
+import {
+ FDO_SDK,
+ FDOInterface,
+ PluginMetadata,
+ DOM,
+ DOMText,
+ DOMButton,
+ DOMInput,
+ DOMLink,
+ DOMNested,
+ DOMMisc
+} from "@anikitenko/fdo-sdk";
+
+/**
+ * AdvancedDOMPlugin demonstrates advanced DOM generation capabilities.
+ *
+ * Key concepts:
+ * - DOM helper classes: Programmatic HTML generation
+ * - CSS-in-JS: Style objects converted to CSS classes
+ * - Component composition: Building complex UIs from simple elements
+ * - Nested structures: Using DOMNested for containers
+ */
+export default class AdvancedDOMPlugin extends FDO_SDK implements FDOInterface {
+ /**
+ * Plugin metadata.
+ *
+ * CUSTOMIZE HERE: Replace with your plugin information
+ */
+ private readonly _metadata: PluginMetadata = {
+ name: "Advanced DOM Plugin Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "Demonstrates advanced DOM generation with helper classes and CSS-in-JS styling",
+ icon: "icon.png"
+ };
+
+ /**
+ * DOM helper instances.
+ * These provide methods for creating HTML elements programmatically.
+ */
+ private dom: DOM;
+ private domText: DOMText;
+ private domButton: DOMButton;
+ private domInput: DOMInput;
+ private domLink: DOMLink;
+ private domNested: DOMNested;
+ private domMisc: DOMMisc;
+
+ constructor() {
+ super();
+
+ this.dom = new DOM();
+ this.domText = new DOMText();
+ this.domButton = new DOMButton();
+ this.domInput = new DOMInput();
+ this.domLink = new DOMLink();
+ this.domNested = new DOMNested();
+ this.domMisc = new DOMMisc();
+ }
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ /**
+ * Initialize the plugin.
+ *
+ * COMMON PITFALL: DOM helper classes should be instantiated in the constructor,
+ * not in init() or render(), to avoid unnecessary object creation.
+ */
+ init(): void {
+ try {
+ this.log("AdvancedDOMPlugin initialized!");
+
+
+ } catch (error) {
+ this.error(error as Error);
+ }
+ }
+
+ /**
+ * Render the plugin UI using DOM helper classes.
+ *
+ * This example demonstrates:
+ * - Creating styled elements with CSS-in-JS
+ * - Building complex nested structures
+ * - Composing reusable UI components
+ * - Using all major DOM helper classes
+ *
+ * CUSTOMIZE HERE: Replace with your own DOM generation logic
+ *
+ * @returns HTML string with embedded CSS
+ */
+ render(): string {
+ try {
+ const containerStyle = {
+ padding: "20px",
+ fontFamily: "Arial, sans-serif",
+ maxWidth: "800px",
+ margin: "0 auto"
+ };
+
+ const header = this.createHeader();
+ const infoCard = this.createInfoCard();
+ const formSection = this.createFormSection();
+ const buttonSection = this.createButtonSection();
+ const conceptsSection = this.createConceptsSection();
+
+ const content = this.domNested.createBlockDiv(
+ { style: containerStyle },
+ undefined,
+ header,
+ infoCard,
+ formSection,
+ buttonSection,
+ conceptsSection
+ );
+
+ return this.dom.renderHTML(content);
+
+ } catch (error) {
+ this.error(error as Error);
+
+ return `
+
+
Error rendering plugin
+
An error occurred while rendering the plugin UI. Check the console for details.
+
+ `;
+ }
+ }
+
+ /**
+ * Create the header section.
+ * Demonstrates text element creation with styling.
+ */
+ private createHeader(): string {
+ const titleStyle = {
+ color: "#333",
+ marginBottom: "10px"
+ };
+
+ const subtitleStyle = {
+ color: "#666",
+ fontSize: "14px",
+ marginBottom: "20px"
+ };
+
+ const title = this.domText.createHText(
+ 1,
+ this._metadata.name,
+ { style: titleStyle }
+ );
+
+ const subtitle = this.domText.createPText(
+ this._metadata.description,
+ { style: subtitleStyle }
+ );
+
+ return title + subtitle;
+ }
+
+ /**
+ * Create an info card with styled content.
+ * Demonstrates nested structures and CSS-in-JS styling.
+ */
+ private createInfoCard(): string {
+ const cardStyle = {
+ backgroundColor: "#f8f9fa",
+ padding: "20px",
+ borderRadius: "8px",
+ marginBottom: "20px",
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
+ };
+
+ const cardTitleStyle = {
+ color: "#007bff",
+ marginTop: "0",
+ marginBottom: "15px"
+ };
+
+ const listStyle = {
+ lineHeight: "1.8",
+ color: "#495057"
+ };
+
+ const cardTitle = this.domText.createHText(
+ 3,
+ "DOM Helper Classes",
+ { style: cardTitleStyle }
+ );
+
+ const description = this.domText.createPText(
+ "This example uses the following DOM helper classes to generate HTML programmatically:",
+ { style: { marginBottom: "10px" } }
+ );
+
+ const listItems = [
+ this.domText.createLiText("DOMText - Text elements (h1-h6, p, span, strong, etc.)"),
+ this.domText.createLiText("DOMButton - Button elements with click handlers"),
+ this.domText.createLiText("DOMInput - Form input elements (text, checkbox, radio, etc.)"),
+ this.domText.createLiText("DOMLink - Anchor elements for navigation"),
+ this.domText.createLiText("DOMNested - Container elements (div, ul, form, etc.)"),
+ this.domText.createLiText("DOMMisc - Miscellaneous elements (hr, etc.)")
+ ];
+
+ const list = this.domNested.createList(
+ listItems,
+ { style: listStyle }
+ );
+
+ return this.domNested.createBlockDiv(
+ { style: cardStyle },
+ undefined,
+ cardTitle,
+ description,
+ list
+ );
+ }
+
+ /**
+ * Create a complex form section.
+ * Demonstrates form composition with multiple input types.
+ */
+ private createFormSection(): string {
+ const formStyle = {
+ backgroundColor: "#e8f4f8",
+ padding: "20px",
+ borderRadius: "8px",
+ marginBottom: "20px"
+ };
+
+ const formTitle = this.domText.createHText(
+ 3,
+ "Form Example",
+ { style: { marginTop: "0", marginBottom: "15px" } }
+ );
+
+ const labelStyle = {
+ display: "block",
+ marginBottom: "5px",
+ fontWeight: "bold",
+ color: "#333"
+ };
+
+ const inputStyle = {
+ padding: "8px",
+ width: "100%",
+ marginBottom: "15px",
+ border: "1px solid #ced4da",
+ borderRadius: "4px",
+ boxSizing: "border-box"
+ };
+
+ const nameLabel = this.domText.createLabelText(
+ "Name:",
+ { style: labelStyle }
+ );
+
+ const nameInput = this.domInput.createInput(
+ "text",
+ {
+ style: inputStyle,
+ placeholder: "Enter your name",
+ id: "name-input"
+ }
+ );
+
+ const emailLabel = this.domText.createLabelText(
+ "Email:",
+ { style: labelStyle }
+ );
+
+ const emailInput = this.domInput.createInput(
+ "email",
+ {
+ style: inputStyle,
+ placeholder: "Enter your email",
+ id: "email-input"
+ }
+ );
+
+ const messageLabel = this.domText.createLabelText(
+ "Message:",
+ { style: labelStyle }
+ );
+
+ const messageInput = this.domInput.createTextarea(
+ {
+ style: { ...inputStyle, height: "100px", resize: "vertical" },
+ placeholder: "Enter your message",
+ id: "message-input"
+ }
+ );
+
+ const notifyCheckbox = this.domInput.createInput(
+ "checkbox",
+ { id: "notify-checkbox", style: { marginRight: "8px" } }
+ );
+
+ const notifyLabel = this.domText.createLabelText(
+ "Send me notifications",
+ { style: { display: "inline", fontWeight: "normal" } }
+ );
+
+ const checkboxContainer = this.domNested.createBlockDiv(
+ { style: { marginBottom: "15px" } },
+ undefined,
+ notifyCheckbox + notifyLabel
+ );
+
+ const submitButton = this.domButton.createButton(
+ "Submit Form",
+ () => { /* Handler would be registered separately */ },
+ {
+ style: {
+ padding: "10px 20px",
+ backgroundColor: "#007bff",
+ color: "white",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ fontSize: "14px"
+ }
+ }
+ );
+
+ const formContent = this.domNested.createForm(
+ { style: { marginTop: "15px" } },
+ undefined,
+ nameLabel,
+ nameInput,
+ emailLabel,
+ emailInput,
+ messageLabel,
+ messageInput,
+ checkboxContainer,
+ submitButton
+ );
+
+ return this.domNested.createBlockDiv(
+ { style: formStyle },
+ undefined,
+ formTitle,
+ formContent
+ );
+ }
+
+ /**
+ * Create a button section.
+ * Demonstrates button styling and composition.
+ */
+ private createButtonSection(): string {
+ const sectionStyle = {
+ backgroundColor: "#d4edda",
+ padding: "20px",
+ borderRadius: "8px",
+ marginBottom: "20px"
+ };
+
+ const sectionTitle = this.domText.createHText(
+ 3,
+ "Button Examples",
+ { style: { marginTop: "0", marginBottom: "15px" } }
+ );
+
+ const primaryButton = this.domButton.createButton(
+ "Primary Action",
+ () => {},
+ {
+ style: {
+ padding: "10px 20px",
+ backgroundColor: "#007bff",
+ color: "white",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ marginRight: "10px",
+ marginBottom: "10px"
+ }
+ }
+ );
+
+ const secondaryButton = this.domButton.createButton(
+ "Secondary Action",
+ () => {},
+ {
+ style: {
+ padding: "10px 20px",
+ backgroundColor: "#6c757d",
+ color: "white",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ marginRight: "10px",
+ marginBottom: "10px"
+ }
+ }
+ );
+
+ const dangerButton = this.domButton.createButton(
+ "Danger Action",
+ () => {},
+ {
+ style: {
+ padding: "10px 20px",
+ backgroundColor: "#dc3545",
+ color: "white",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ marginBottom: "10px"
+ }
+ }
+ );
+
+ const buttonContainer = this.domNested.createBlockDiv(
+ { style: { marginTop: "10px" } },
+ undefined,
+ primaryButton,
+ secondaryButton,
+ dangerButton
+ );
+
+ return this.domNested.createBlockDiv(
+ { style: sectionStyle },
+ undefined,
+ sectionTitle,
+ buttonContainer
+ );
+ }
+
+ /**
+ * Create a concepts section.
+ * Demonstrates links and dividers.
+ */
+ private createConceptsSection(): string {
+ const sectionStyle = {
+ backgroundColor: "#fff3cd",
+ padding: "20px",
+ borderRadius: "8px"
+ };
+
+ const sectionTitle = this.domText.createHText(
+ 3,
+ "Key Concepts",
+ { style: { marginTop: "0", marginBottom: "15px" } }
+ );
+
+ const concepts = [
+ this.domText.createPText(
+ this.domText.createStrongText("CSS-in-JS: ") +
+ "Style objects are converted to CSS classes using goober. This provides scoped styling and prevents conflicts."
+ ),
+ this.domText.createPText(
+ this.domText.createStrongText("Component Composition: ") +
+ "Complex UIs are built by composing simple elements. Each DOM helper returns an HTML string that can be combined."
+ ),
+ this.domText.createPText(
+ this.domText.createStrongText("Nested Structures: ") +
+ "Use DOMNested.createBlockDiv() to create containers that hold multiple child elements."
+ ),
+ this.domText.createPText(
+ this.domText.createStrongText("renderHTML(): ") +
+ "This method wraps your content with a