💡 Suggestion
This is a suggestion to enhance the tray menu system. The primary objective is to achieve full BitBar/Argos compatibility by implementing hierarchical submenus, allowing plugins to create multi-level menu structures using the standard -- prefix notation.
🎯 Objective
Implement complete hierarchical submenu parsing and rendering for the system tray, following the BitBar specification and Argos conventions. This will enable plugins to create complex nested menus like:
#!/bin/bash
# Example: cpu.10s.sh
echo "🖥️ CPU: 85%"
echo "---"
echo "Status"
echo "--Core 1: 90% | color=red"
echo "--Core 2: 80% | color=orange"
echo "----Details"
echo "------Frequency: 3.2 GHz"
echo "------Temperature: 65°C"
echo "Settings"
echo "--Refresh Rate"
echo "----5 seconds | bash='crossbar config set refresh 5s'"
echo "----10 seconds | bash='crossbar config set refresh 10s'"
Expected Result: A tray menu with proper indentation/hierarchy:
🖥️ CPU: 85%
---
Status
├─ Core 1: 90% (red text)
├─ Core 2: 80% (orange text)
└─ Details
├─ Frequency: 3.2 GHz
└─ Temperature: 65°C
Settings
└─ Refresh Rate
├─ 5 seconds
└─ 10 seconds
📋 Current State Analysis
What Already Works ✅
- Basic menu parsing in
lib/core/output_parser.dart (_parseBitBar method)
MenuItem model already has submenu field (lib/models/plugin_output.dart)
- JSON format supports hierarchical menus:
{
"menu": [
{"text": "Parent", "submenu": [{"text": "Child"}]}
]
}
What's Missing ❌
- BitBar text format doesn't parse
-- prefixes to create hierarchy
- Current parser treats all menu items as flat list (level 0)
- Tray service needs recursive menu building logic
🛠️ Implementation Suggestions
1. Enhance BitBar Parser
Location: lib/core/output_parser.dart (around line 70, in _parseBitBar method)
Current behavior: All lines after --- are treated as top-level menu items.
Suggested approach:
// Parse hierarchy level based on leading dashes
static int _getMenuLevel(String line) {
int level = 0;
for (int i = 0; i < line.length; i++) {
if (line[i] == '-' && (i + 1 < line.length && line[i + 1] == '-')) {
level++;
i++; // Skip next dash
} else {
break;
}
}
return level;
}
// Build hierarchical structure from flat list
static List<MenuItem> _buildMenuHierarchy(List<_MenuItemFlat> flatItems) {
final List<MenuItem> rootItems = [];
final Map<int, MenuItem> lastAtLevel = {};
for (final flat in flatItems) {
final item = MenuItem(
text: flat.text,
bash: flat.bash,
href: flat.href,
color: flat.color,
);
if (flat.level == 0) {
rootItems.add(item);
lastAtLevel[0] = item;
} else {
// Find parent at level-1 and add as submenu
final parent = lastAtLevel[flat.level - 1];
if (parent != null) {
parent.submenu ??= [];
parent.submenu!.add(item);
}
lastAtLevel[flat.level] = item;
}
}
return rootItems;
}
Reference: BitBar spec defines levels as:
Text = level 0 (root menu item)
--Text = level 1 (submenu of previous level 0)
----Text = level 2 (submenu of previous level 1)
------Text = level 3, and so on...
2. Update MenuItem Model (if needed)
Location: lib/models/plugin_output.dart
Current state: MenuItem already has optional submenu field, which is good!
Optional enhancement: Add level field for debugging/validation:
class MenuItem {
final int level; // For debugging - not strictly necessary
// ... existing fields
}
3. Implement Recursive Tray Rendering
Location: Create or update tray menu builder (likely in lib/services/tray_service.dart)
Suggested structure:
import 'package:tray_manager/tray_manager.dart';
class TrayMenuBuilder {
Menu buildMenu(List<MenuItem> items) {
final menu = Menu();
_addItemsToMenu(menu, items);
return menu;
}
void _addItemsToMenu(Menu menu, List<MenuItem> items) {
for (final item in items) {
if (item.separator) {
menu.addSeparator();
} else if (item.submenu != null && item.submenu!.isNotEmpty) {
// Create submenu recursively
final submenu = Menu();
_addItemsToMenu(submenu, item.submenu!);
menu.addItem(MenuItem(
label: item.text ?? '',
submenu: submenu,
));
} else {
// Leaf item with action
menu.addItem(MenuItem(
label: item.text ?? '',
onClick: () => _handleClick(item),
));
}
}
}
void _handleClick(MenuItem item) {
if (item.bash != null) {
// Execute bash command
} else if (item.href != null) {
// Open URL
}
}
}
Note: The exact API depends on tray_manager package capabilities. Check tray_manager documentation for submenu support.
🧪 Testing Strategy
Unit Tests
-
Parser tests (test/unit/core/output_parser_test.dart):
- Single level menu (current behavior)
- Two-level hierarchy (
--)
- Three-level hierarchy (
----)
- Mixed levels with separators
- Edge cases: orphaned submenus, invalid indentation
-
Example test case:
test('parses multi-level BitBar format', () {
const input = '''
🖥️ CPU: 85%
---
Status
--Core 1: 90%
----Details
------Temperature: 65°C
''';
final output = OutputParser.parse(input, 'cpu.10s.sh');
expect(output.menu.length, 1); // "Status"
expect(output.menu[0].submenu?.length, 1); // "Core 1: 90%"
expect(output.menu[0].submenu![0].submenu?.length, 1); // "Details"
expect(output.menu[0].submenu![0].submenu![0].submenu?.length, 1); // "Temperature"
});
Integration Tests
- Create test plugins with complex hierarchies
- Verify visual rendering in tray (manual QA on Linux/macOS/Windows)
📚 References
✅ Acceptance Criteria
🎯 Success Metrics
- Compatibility: All BitBar/Argos example plugins work without modification
- Performance: Menu build time < 50ms for 100 items
- UX: Users can navigate 3+ levels deep intuitively
📝 Additional Notes
- This feature is critical for v1.5.0 milestone ("Menu Perfeito")
- Prioritize correctness over performance initially
- Consider adding max depth limit (e.g., 10 levels) to prevent abuse
- Some window managers (GNOME) may have limitations on submenu rendering
Related Issues: (will be added as we create v1.6.0+ issues)
Milestone: v1.5.0
Priority: High
Estimated Effort: 2-3 days
💡 Suggestion
This is a suggestion to enhance the tray menu system. The primary objective is to achieve full BitBar/Argos compatibility by implementing hierarchical submenus, allowing plugins to create multi-level menu structures using the standard
--prefix notation.🎯 Objective
Implement complete hierarchical submenu parsing and rendering for the system tray, following the BitBar specification and Argos conventions. This will enable plugins to create complex nested menus like:
Expected Result: A tray menu with proper indentation/hierarchy:
📋 Current State Analysis
What Already Works ✅
lib/core/output_parser.dart(_parseBitBarmethod)MenuItemmodel already hassubmenufield (lib/models/plugin_output.dart){ "menu": [ {"text": "Parent", "submenu": [{"text": "Child"}]} ] }What's Missing ❌
--prefixes to create hierarchy🛠️ Implementation Suggestions
1. Enhance BitBar Parser
Location:
lib/core/output_parser.dart(around line 70, in_parseBitBarmethod)Current behavior: All lines after
---are treated as top-level menu items.Suggested approach:
Reference: BitBar spec defines levels as:
Text= level 0 (root menu item)--Text= level 1 (submenu of previous level 0)----Text= level 2 (submenu of previous level 1)------Text= level 3, and so on...2. Update MenuItem Model (if needed)
Location:
lib/models/plugin_output.dartCurrent state:
MenuItemalready has optionalsubmenufield, which is good!Optional enhancement: Add
levelfield for debugging/validation:3. Implement Recursive Tray Rendering
Location: Create or update tray menu builder (likely in
lib/services/tray_service.dart)Suggested structure:
Note: The exact API depends on
tray_managerpackage capabilities. Check tray_manager documentation for submenu support.🧪 Testing Strategy
Unit Tests
Parser tests (
test/unit/core/output_parser_test.dart):--)----)Example test case:
Integration Tests
📚 References
--notationlib/core/output_parser.dartlib/models/plugin_output.dart✅ Acceptance Criteria
--,----, etc. creates proper hierarchy🎯 Success Metrics
📝 Additional Notes
Related Issues: (will be added as we create v1.6.0+ issues)
Milestone: v1.5.0
Priority: High
Estimated Effort: 2-3 days