These docs describe features that exist as documented in the doc but either never wired up or regressed when v3 shipped. Each gets a clear status banner so plugin authors don't waste time chasing features that don't actually work. FONT_MANAGER.md - The "For Plugin Developers / Plugin Font Registration" section documents adding a "fonts" block to manifest.json that gets registered via FontManager.register_plugin_fonts(). The method exists at src/font_manager.py:150 but is **never called from anywhere** in the codebase (verified: zero callers). A plugin shipping a manifest "fonts" block has its fonts silently ignored. Added a status warning and a note about how to actually ship plugin fonts (regular files in the plugin dir, loaded directly). PLUGIN_IMPLEMENTATION_SUMMARY.md - Added a top-level status banner. - Architecture diagram referenced src/plugin_system/registry_manager.py (which doesn't exist) and listed plugins/ as the install location. Replaced with the real file list (plugin_loader, schema_manager, health_monitor, operation_queue, state_manager) and pointed at plugin-repos/ as the default install location. - "Dependency Management: Virtual Environments" — verified there's no per-plugin venv. Removed the bullet and added a note that plugin Python deps install into the system Python environment, with no conflict resolution. - "Permission System: File Access Control / Network Access / Resource Limits / CPU and memory constraints" — none of these exist. There's a resource_monitor.py and health_monitor.py for metrics/warnings, but no hard caps or sandboxing. Replaced the section with what's actually implemented and a clear note that plugins run in the same process with full file/network access. PLUGIN_CUSTOM_ICONS.md and PLUGIN_CUSTOM_ICONS_FEATURE.md - The custom-icon feature was implemented in the v2 web interface via a getPluginIcon() helper in templates/index_v2.html that read the manifest "icon" field. When the v3 web interface was built, that helper wasn't ported. Verified in web_interface/templates/v3/base.html:515 and :774, plugin tab icons are hardcoded to `fas fa-puzzle-piece`. The "icon" field in plugin manifests is currently silently ignored (verified with grep across web_interface/ and src/plugin_system/ — zero non-action- related reads of plugin.icon or manifest.icon). - Added a status banner to both docs noting the regression so plugin authors don't think their custom icons are broken in their own plugin code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
Plugin Custom Icons Feature
⚠️ Status: this doc describes the v2 web interface implementation of plugin custom icons. The feature regressed when the v3 web interface was built — the
getPluginIcon()helper referenced below lived intemplates/index_v2.html(which is now archived) and was not ported to the v3 templates. Plugin tab icons in v3 are hardcoded tofas fa-puzzle-piece(web_interface/templates/v3/base.html:515and:774). Theiconfield inmanifest.jsonis currently silently ignored.
What Was Implemented
You asked: "How could a plugin add their own custom icon?"
Answer: Plugins can now specify custom icons in their manifest.json file using the icon field!
Features Delivered
✅ Font Awesome Support - Use any Font Awesome icon (e.g., fas fa-clock)
✅ Emoji Support - Use any emoji character (e.g., ⏰ or 👋)
✅ Custom Image Support - Use custom image files or URLs
✅ Automatic Detection - System automatically detects icon type
✅ Fallback Support - Default puzzle piece icon if none specified
✅ Tab & Header Icons - Icons appear in both tab buttons and configuration page headers
How It Works
For Plugin Developers
Simply add an icon field to your plugin's manifest.json:
{
"id": "my-plugin",
"name": "My Plugin",
"icon": "fas fa-star", // ← Add this line
"config_schema": "config_schema.json",
...
}
Three Icon Types Supported
1. Font Awesome Icons (Recommended)
"icon": "fas fa-clock"
Best for: Professional, consistent UI appearance
2. Emoji Icons (Fun!)
"icon": "⏰"
Best for: Colorful, fun plugins; no setup needed
3. Custom Images
"icon": "/plugins/my-plugin/logo.png"
Best for: Unique branding; requires image file
Implementation Details
Frontend Changes (templates/index_v2.html)
New Function: getPluginIcon(plugin)
- Checks if plugin has
iconfield in manifest - Detects icon type automatically:
- Contains
fa-→ Font Awesome - 1-4 characters → Emoji
- Starts with URL/path → Custom image
- Otherwise → Default puzzle piece
- Contains
Updated Functions:
generatePluginTabs()- Uses custom icon for tab buttongeneratePluginConfigForm()- Uses custom icon in page header
Example Plugin Updates
hello-world plugin:
"icon": "👋"
clock-simple plugin:
"icon": "fas fa-clock"
Code Example
Here's what the icon detection logic does. Important: Plugin manifests must be treated as untrusted input and require escaping/validation before rendering.
// Helper function to escape HTML entities
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Helper function to validate and sanitize image URLs
function isValidImageUrl(url) {
if (!url || typeof url !== 'string') {
return false;
}
// Only allow http, https, or relative paths starting with /
const allowedProtocols = ['http:', 'https:'];
const urlLower = url.toLowerCase().trim();
// Reject dangerous protocols
if (urlLower.startsWith('javascript:') ||
urlLower.startsWith('data:') ||
urlLower.startsWith('vbscript:') ||
urlLower.startsWith('onerror=') ||
urlLower.startsWith('onload=')) {
return false;
}
// Allow relative paths starting with /
if (url.startsWith('/')) {
return true;
}
// Validate absolute URLs
try {
const urlObj = new URL(url);
return allowedProtocols.includes(urlObj.protocol);
} catch (e) {
// Invalid URL format
return false;
}
}
// Helper function to safely validate Font Awesome class names
function isValidFontAwesomeClass(icon) {
// Whitelist pattern: only allow alphanumeric, dash, underscore, and spaces
// Must contain 'fa-' for Font Awesome
const faPattern = /^[a-zA-Z0-9\s_-]*fa-[a-zA-Z0-9-]+[a-zA-Z0-9\s_-]*$/;
return faPattern.test(icon) && icon.includes('fa-');
}
function getPluginIcon(plugin) {
if (plugin.icon) {
const icon = String(plugin.icon).trim();
// Font Awesome icon - escape class name to prevent XSS
if (isValidFontAwesomeClass(icon)) {
const escapedIcon = escapeHtml(icon);
return `<i class="${escapedIcon}"></i>`;
}
// Emoji - use textContent to safely render (no HTML injection possible)
if (icon.length <= 4) {
// Create element and set textContent (safe from XSS)
const span = document.createElement('span');
span.style.fontSize = '1.1em';
span.textContent = icon; // textContent automatically escapes
return span.outerHTML;
}
// Custom image - validate URL and set src attribute safely
if (isValidImageUrl(icon)) {
// Create img element and set attributes safely
const img = document.createElement('img');
img.src = icon; // URL already validated
img.alt = '';
img.style.width = '16px';
img.style.height = '16px';
return img.outerHTML;
}
}
// Default fallback
return '<i class="fas fa-puzzle-piece"></i>';
}
Security Notes:
- Plugin manifests are treated as untrusted input
- All text content is escaped using
escapeHtml()ortextContent - Image URLs are validated to only allow
http://,https://, or relative paths starting with/ - Dangerous protocols (
javascript:,data:, etc.) are explicitly rejected - Font Awesome class names are validated against a whitelist pattern
- DOM elements are created and attributes set directly rather than using string interpolation
Visual Examples
Before (No Custom Icons)
[🧩 Hello World] [🧩 Clock Simple] [🧩 Weather Display]
After (With Custom Icons)
[👋 Hello World] [⏰ Clock Simple] [☀️ Weather Display]
Documentation Created
📚 Comprehensive guide: docs/PLUGIN_CUSTOM_ICONS.md
Contains:
- Complete icon type explanations
- Font Awesome icon recommendations by category
- Emoji suggestions for common plugin types
- Custom image guidelines
- Best practices and troubleshooting
- Examples for every use case
📝 Updated existing docs:
PLUGIN_CONFIGURATION_TABS.md- Added icon referencePLUGIN_CONFIG_TABS_SUMMARY.md- Added icon quick tipPLUGIN_CONFIG_QUICK_START.md- Added icon bonus section
Popular Icon Recommendations
By Plugin Category
Time & Calendar
- Font Awesome:
fas fa-clock,fas fa-calendar,fas fa-hourglass - Emoji: ⏰ 📅 ⏱️
Weather
- Font Awesome:
fas fa-cloud-sun,fas fa-temperature-high - Emoji: ☀️ 🌧️ ⛈️
Finance
- Font Awesome:
fas fa-chart-line,fas fa-dollar-sign - Emoji: 💰 📈 💵
Sports
- Font Awesome:
fas fa-football-ball,fas fa-trophy - Emoji: ⚽ 🏀 🎮
Music
- Font Awesome:
fas fa-music,fas fa-headphones - Emoji: 🎵 🎶 🎸
News
- Font Awesome:
fas fa-newspaper,fas fa-rss - Emoji: 📰 📡 📻
Utilities
- Font Awesome:
fas fa-tools,fas fa-cog - Emoji: 🔧 ⚙️ 🛠️
Usage Examples
Weather Plugin
{
"id": "weather-pro",
"name": "Weather Pro",
"icon": "fas fa-cloud-sun",
"description": "Advanced weather display"
}
Result: ☁️ Weather Pro tab
Game Scores
{
"id": "game-scores",
"name": "Game Scores",
"icon": "🎮",
"description": "Live game scores"
}
Result: 🎮 Game Scores tab
Custom Branding
{
"id": "company-metrics",
"name": "Company Metrics",
"icon": "/plugins/company-metrics/logo.svg",
"description": "Internal dashboard"
}
Result: [logo] Company Metrics tab
Benefits
For Users
- Visual Recognition - Instantly identify plugins
- Better Navigation - Find plugins faster
- Professional Appearance - Polished, modern UI
For Developers
- Easy to Add - Just one line in manifest
- Flexible Options - Choose what fits your plugin
- No Code Required - Pure configuration
For the Project
- Plugin Differentiation - Each plugin stands out
- Enhanced UX - More intuitive interface
- Branding Support - Plugins can show identity
Backward Compatibility
✅ Fully backward compatible
- Plugins without
iconfield still work - Default puzzle piece icon used automatically
- No breaking changes to existing plugins
Testing
To test custom icons:
- Open web interface at
http://your-pi-ip:5000 - Check installed plugins:
- Hello World should show 👋
- Clock Simple should show 🕐
- Install a new plugin with custom icon
- Verify icon appears in:
- Tab navigation bar
- Plugin configuration page header
File Changes
Modified Files
templates/index_v2.html- Added
getPluginIcon()function - Updated
generatePluginTabs() - Updated
generatePluginConfigForm()
- Added
Updated Plugin Manifests
ledmatrix-plugins/plugins/hello-world/manifest.json- Added emoji iconledmatrix-plugins/plugins/clock-simple/manifest.json- Added Font Awesome icon
New Documentation
docs/PLUGIN_CUSTOM_ICONS.md- Complete guide (80+ lines)
Updated Documentation
docs/PLUGIN_CONFIGURATION_TABS.mddocs/PLUGIN_CONFIG_TABS_SUMMARY.mddocs/PLUGIN_CONFIG_QUICK_START.md
Quick Reference
Add Icon to Your Plugin
{
"id": "your-plugin",
"name": "Your Plugin Name",
"icon": "fas fa-star", // or emoji or image URL
"config_schema": "config_schema.json",
...
}
Icon Format Examples
// Font Awesome
"icon": "fas fa-star"
"icon": "far fa-heart"
"icon": "fab fa-twitter"
// Emoji
"icon": "⭐"
"icon": "❤️"
"icon": "🐦"
// Custom Image
"icon": "/plugins/my-plugin/icon.png"
"icon": "https://example.com/logo.svg"
Browse Available Icons
- Font Awesome: fontawesome.com/icons (Free tier includes 2,000+ icons)
- Emojis: unicode.org/emoji
Best Practices
- Choose meaningful icons - Icon should relate to plugin function
- Keep it simple - Works better at small sizes
- Test visibility - Ensure icon is clear at 16px
- Match UI style - Font Awesome recommended for consistency
- Document choice - Note icon meaning in plugin README
Troubleshooting
Icon not showing?
- Check manifest syntax (JSON valid?)
- Verify icon field spelling
- Refresh plugins in web interface
- Check browser console for errors
Wrong icon appearing?
- Font Awesome: Verify class name at fontawesome.com
- Emoji: Try different emoji (platform rendering varies)
- Custom image: Check file path and permissions
Future Enhancements
Possible future improvements:
- Icon picker in plugin store
- Animated icons support
- SVG path support
- Icon themes/styles
- Dynamic icon changes based on state
Summary
Mission accomplished! 🎉
Plugins can now have custom icons by adding one line to their manifest:
"icon": "fas fa-your-icon"
Three formats supported:
- ✅ Font Awesome (professional)
- ✅ Emoji (fun)
- ✅ Custom images (branded)
The feature is:
- ✅ Easy to use (one line)
- ✅ Flexible (three options)
- ✅ Backward compatible
- ✅ Well documented
- ✅ Already working in example plugins
Ready to use! 🚀