django-cfg 1.4.75__py3-none-any.whl → 1.4.76__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (52) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/agents/__init__.py +1 -1
  3. django_cfg/apps/agents/integration/registry.py +1 -1
  4. django_cfg/apps/agents/patterns/content_agents.py +1 -1
  5. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
  6. django_cfg/apps/centrifugo/views/dashboard.py +13 -0
  7. django_cfg/apps/centrifugo/views/testing_api.py +74 -15
  8. django_cfg/apps/tasks/views/dashboard.py +4 -4
  9. django_cfg/core/generation/integration_generators/api.py +5 -2
  10. django_cfg/management/commands/check_endpoints.py +1 -1
  11. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
  12. django_cfg/modules/django_unfold/callbacks/main.py +27 -25
  13. django_cfg/pyproject.toml +1 -1
  14. django_cfg/static/admin/css/constance.css +44 -0
  15. django_cfg/static/admin/css/dashboard.css +6 -170
  16. django_cfg/static/admin/css/layout.css +21 -0
  17. django_cfg/static/admin/css/tabs.css +95 -0
  18. django_cfg/static/admin/css/theme.css +74 -0
  19. django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
  20. django_cfg/static/admin/js/alpine/chart.js +101 -0
  21. django_cfg/static/admin/js/alpine/command-modal.js +159 -0
  22. django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
  23. django_cfg/static/admin/js/alpine/commands-section.js +260 -0
  24. django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
  25. django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
  26. django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
  27. django_cfg/static/admin/js/utils.js +60 -0
  28. django_cfg/templates/admin/components/modal.html +1 -1
  29. django_cfg/templates/admin/constance/change_list.html +3 -42
  30. django_cfg/templates/admin/index.html +0 -8
  31. django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
  32. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
  33. django_cfg/templates/admin/sections/commands_section.html +374 -451
  34. django_cfg/templates/admin/sections/documentation_section.html +13 -33
  35. django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
  36. django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
  37. django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
  38. django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
  39. django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
  40. django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
  41. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
  42. django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
  43. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
  44. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/RECORD +47 -39
  45. django_cfg/static/admin/js/commands.js +0 -171
  46. django_cfg/static/admin/js/dashboard.js +0 -126
  47. django_cfg/templates/admin/components/management_commands.js +0 -375
  48. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
  49. django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
  50. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
  51. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
  52. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Dashboard Tabs Styles
3
+ * Styling for tab navigation and tab content
4
+ */
5
+
6
+ /* Tab Content Animation */
7
+ .tab-content {
8
+ animation: fadeIn 0.3s ease-in-out;
9
+ }
10
+
11
+ @keyframes fadeIn {
12
+ from {
13
+ opacity: 0;
14
+ transform: translateY(10px);
15
+ }
16
+ to {
17
+ opacity: 1;
18
+ transform: translateY(0);
19
+ }
20
+ }
21
+
22
+ /* Dashboard Tabs Navigation */
23
+ #dashboard-tabs {
24
+ border-bottom: 2px solid #e5e7eb !important;
25
+ }
26
+
27
+ html.dark #dashboard-tabs {
28
+ border-bottom-color: #374151 !important;
29
+ }
30
+
31
+ #dashboard-tabs button {
32
+ cursor: pointer;
33
+ transition: all 0.2s ease-in-out;
34
+ background-color: #f3f4f6 !important;
35
+ color: #6b7280 !important;
36
+ border-bottom: 2px solid transparent !important;
37
+ }
38
+
39
+ html.dark #dashboard-tabs button {
40
+ background-color: #374151 !important;
41
+ color: #9ca3af !important;
42
+ }
43
+
44
+ #dashboard-tabs button:hover {
45
+ background-color: #e5e7eb !important;
46
+ color: #374151 !important;
47
+ }
48
+
49
+ html.dark #dashboard-tabs button:hover {
50
+ background-color: #4b5563 !important;
51
+ color: #d1d5db !important;
52
+ }
53
+
54
+ /* Active Tab */
55
+ #dashboard-tabs button.active,
56
+ #dashboard-tabs button[class*="bg-blue"] {
57
+ background-color: #2563eb !important;
58
+ color: white !important;
59
+ border-bottom-color: #2563eb !important;
60
+ }
61
+
62
+ html.dark #dashboard-tabs button.active,
63
+ html.dark #dashboard-tabs button[class*="bg-blue"] {
64
+ background-color: #3b82f6 !important;
65
+ color: white !important;
66
+ border-bottom-color: #3b82f6 !important;
67
+ }
68
+
69
+ /* Command Modal Tabs */
70
+ .command-tab {
71
+ background-color: transparent;
72
+ color: #6b7280;
73
+ transition: all 0.2s ease;
74
+ }
75
+
76
+ .command-tab:hover {
77
+ background-color: #f3f4f6;
78
+ }
79
+
80
+ html.dark .command-tab {
81
+ color: #9ca3af;
82
+ }
83
+
84
+ html.dark .command-tab:hover {
85
+ background-color: #374151;
86
+ }
87
+
88
+ .command-tab.active {
89
+ background-color: #3b82f6;
90
+ color: white;
91
+ }
92
+
93
+ html.dark .command-tab.active {
94
+ background-color: #2563eb;
95
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Theme Styles
3
+ * Cross-theme compatible styles for light/dark mode
4
+ */
5
+
6
+ /* Theme Cards - Glass morphism effect */
7
+ .theme-card {
8
+ background-color: rgba(255, 255, 255, 0.2) !important;
9
+ backdrop-filter: blur(10px) !important;
10
+ color: #111827 !important;
11
+ border-color: rgba(229, 231, 235, 0.6) !important;
12
+ border-radius: 10px !important;
13
+ transition: all 0.2s ease;
14
+ }
15
+
16
+ html.dark .theme-card {
17
+ background-color: rgba(31, 41, 55, 0.2) !important;
18
+ backdrop-filter: blur(10px) !important;
19
+ color: white !important;
20
+ border-color: rgba(55, 65, 81, 0.6) !important;
21
+ border-radius: 10px !important;
22
+ }
23
+
24
+ /* Theme Text */
25
+ .theme-text {
26
+ color: #111827 !important;
27
+ }
28
+
29
+ html.dark .theme-text {
30
+ color: white !important;
31
+ }
32
+
33
+ /* Theme Borders */
34
+ .theme-border {
35
+ border-color: rgba(229, 231, 235, 0.6) !important;
36
+ }
37
+
38
+ html.dark .theme-border {
39
+ border-color: rgba(55, 65, 81, 0.6) !important;
40
+ }
41
+
42
+ /* Universal Card Borders - Transparent cross-theme */
43
+ .card-border,
44
+ .border-base-200,
45
+ [class*="border-base-200"],
46
+ [class*="dark:border-base-700"] {
47
+ border-color: rgba(229, 231, 235, 0.6) !important;
48
+ }
49
+
50
+ html.dark .card-border,
51
+ html.dark .border-base-200,
52
+ html.dark [class*="border-base-200"],
53
+ html.dark [class*="dark:border-base-700"] {
54
+ border-color: rgba(55, 65, 81, 0.6) !important;
55
+ }
56
+
57
+ /* Icon Spacing Defaults */
58
+ .theme-card .material-icons:not(.no-margin) {
59
+ margin-right: 0.75rem !important; /* 12px default spacing */
60
+ }
61
+
62
+ .theme-card .flex.items-center .material-icons {
63
+ margin-right: 0.75rem !important;
64
+ }
65
+
66
+ .theme-card .status-badge .material-icons {
67
+ margin-right: 0.25rem !important; /* 4px for badges */
68
+ }
69
+
70
+ /* Icon spacing for buttons and links */
71
+ .theme-card button .material-icons,
72
+ .theme-card a .material-icons {
73
+ margin-right: 0.5rem !important;
74
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Activity Tracker Alpine.js Component
3
+ *
4
+ * GitHub-style heatmap visualization for activity data
5
+ */
6
+
7
+ function activityTrackerComponent(activityData) {
8
+ return {
9
+ activityData: activityData || [],
10
+ weeks: [],
11
+
12
+ init() {
13
+ this.processData();
14
+ },
15
+
16
+ processData() {
17
+ if (!this.activityData || this.activityData.length === 0) {
18
+ return;
19
+ }
20
+
21
+ // Group days into weeks (7 days per column)
22
+ this.weeks = [];
23
+ for (let i = 0; i < this.activityData.length; i += 7) {
24
+ this.weeks.push(this.activityData.slice(i, i + 7));
25
+ }
26
+ },
27
+
28
+ getCellColor(count) {
29
+ if (count === 0) {
30
+ return 'bg-gray-200 dark:bg-gray-700';
31
+ } else if (count <= 2) {
32
+ return 'bg-green-200 dark:bg-green-800';
33
+ } else if (count <= 5) {
34
+ return 'bg-green-400 dark:bg-green-600';
35
+ } else if (count <= 10) {
36
+ return 'bg-green-600 dark:bg-green-500';
37
+ } else {
38
+ return 'bg-green-800 dark:bg-green-400';
39
+ }
40
+ },
41
+
42
+ getCellTitle(day) {
43
+ return `${day.date}: ${day.count} activities`;
44
+ },
45
+
46
+ get hasData() {
47
+ return this.activityData && this.activityData.length > 0;
48
+ }
49
+ };
50
+ }
51
+
52
+ // Register component
53
+ document.addEventListener('alpine:init', () => {
54
+ Alpine.data('activityTracker', activityTrackerComponent);
55
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Chart Alpine.js Component
3
+ *
4
+ * Universal Chart.js wrapper for Alpine
5
+ */
6
+
7
+ function chartComponent(chartData, chartType = 'line', options = {}) {
8
+ return {
9
+ chart: null,
10
+ chartData: chartData,
11
+ chartType: chartType,
12
+
13
+ init() {
14
+ this.$nextTick(() => {
15
+ this.renderChart();
16
+ });
17
+ },
18
+
19
+ renderChart() {
20
+ const canvas = this.$refs.canvas;
21
+
22
+ if (!canvas || typeof Chart === 'undefined') {
23
+ console.error('Chart.js not loaded or canvas not found');
24
+ return;
25
+ }
26
+
27
+ try {
28
+ // Default options
29
+ const defaultOptions = {
30
+ responsive: true,
31
+ maintainAspectRatio: false,
32
+ plugins: {
33
+ legend: {
34
+ display: true,
35
+ position: 'top'
36
+ }
37
+ },
38
+ scales: {
39
+ y: {
40
+ beginAtZero: true,
41
+ ticks: {
42
+ precision: 0
43
+ }
44
+ }
45
+ }
46
+ };
47
+
48
+ // Merge with custom options
49
+ const mergedOptions = this.deepMerge(defaultOptions, options);
50
+
51
+ this.chart = new Chart(canvas, {
52
+ type: this.chartType,
53
+ data: this.chartData,
54
+ options: mergedOptions
55
+ });
56
+ } catch (error) {
57
+ console.error('Error creating chart:', error);
58
+ }
59
+ },
60
+
61
+ updateChart(newData) {
62
+ if (this.chart) {
63
+ this.chart.data = newData;
64
+ this.chart.update();
65
+ }
66
+ },
67
+
68
+ destroy() {
69
+ if (this.chart) {
70
+ this.chart.destroy();
71
+ this.chart = null;
72
+ }
73
+ },
74
+
75
+ deepMerge(target, source) {
76
+ const output = Object.assign({}, target);
77
+ if (this.isObject(target) && this.isObject(source)) {
78
+ Object.keys(source).forEach(key => {
79
+ if (this.isObject(source[key])) {
80
+ if (!(key in target))
81
+ Object.assign(output, { [key]: source[key] });
82
+ else
83
+ output[key] = this.deepMerge(target[key], source[key]);
84
+ } else {
85
+ Object.assign(output, { [key]: source[key] });
86
+ }
87
+ });
88
+ }
89
+ return output;
90
+ },
91
+
92
+ isObject(item) {
93
+ return item && typeof item === 'object' && !Array.isArray(item);
94
+ }
95
+ };
96
+ }
97
+
98
+ // Register component
99
+ document.addEventListener('alpine:init', () => {
100
+ Alpine.data('chart', chartComponent);
101
+ });
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Command Modal Alpine.js Component
3
+ *
4
+ * Manages command execution modal with tabs for output and documentation
5
+ * Requires: /static/admin/js/utils.js for getCookie function
6
+ */
7
+
8
+ function commandModalComponent() {
9
+ return {
10
+ open: false,
11
+ commandName: '',
12
+ activeTab: 'output',
13
+ outputHtml: '',
14
+ docsHtml: '<p class="text-font-subtle-light dark:text-font-subtle-dark">Loading documentation...</p>',
15
+ statusText: 'Executing...',
16
+ statusClass: 'bg-yellow-500 animate-pulse',
17
+
18
+ async execute(commandName) {
19
+ this.commandName = commandName;
20
+ this.open = true;
21
+ this.activeTab = 'output';
22
+ this.outputHtml = '';
23
+ this.statusText = 'Executing...';
24
+ this.statusClass = 'bg-yellow-500 animate-pulse';
25
+
26
+ // Load documentation
27
+ this.loadDocumentation(commandName);
28
+
29
+ // Execute command
30
+ try {
31
+ const response = await fetch('/cfg/commands/execute/', {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ 'X-CSRFToken': window.getCookie('csrftoken')
36
+ },
37
+ body: JSON.stringify({
38
+ command: commandName,
39
+ args: [],
40
+ options: {}
41
+ })
42
+ });
43
+
44
+ if (!response.ok) {
45
+ throw new Error(`HTTP error! status: ${response.status}`);
46
+ }
47
+
48
+ const reader = response.body.getReader();
49
+ const decoder = new TextDecoder();
50
+
51
+ while (true) {
52
+ const {done, value} = await reader.read();
53
+ if (done) break;
54
+
55
+ const chunk = decoder.decode(value);
56
+ const lines = chunk.split('\n');
57
+
58
+ lines.forEach(line => {
59
+ if (line.startsWith('data: ')) {
60
+ try {
61
+ const data = JSON.parse(line.slice(6));
62
+ this.handleCommandData(data);
63
+ } catch (e) {
64
+ console.error('Error parsing command data:', e);
65
+ }
66
+ }
67
+ });
68
+ }
69
+ } catch (error) {
70
+ console.error('Error executing command:', error);
71
+ this.outputHtml += `\n❌ Error: ${error.message}`;
72
+ this.statusText = 'Error';
73
+ this.statusClass = 'bg-red-500';
74
+ }
75
+ },
76
+
77
+ handleCommandData(data) {
78
+ switch (data.type) {
79
+ case 'start':
80
+ this.outputHtml = `🚀 Starting command: ${data.command}\n📝 Arguments: ${data.args.join(' ')}\n\n`;
81
+ this.statusText = 'Executing...';
82
+ this.statusClass = 'bg-yellow-500 animate-pulse';
83
+ break;
84
+ case 'output':
85
+ this.outputHtml += data.line + '\n';
86
+ break;
87
+ case 'complete':
88
+ const success = data.return_code === 0;
89
+ this.statusText = success ? 'Completed' : 'Failed';
90
+ this.statusClass = success ? 'bg-green-500' : 'bg-red-500';
91
+ let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
92
+ if (data.execution_time) {
93
+ completionMessage += ` (${data.execution_time}s)`;
94
+ }
95
+ this.outputHtml += '\n' + completionMessage;
96
+ break;
97
+ case 'error':
98
+ this.outputHtml += `❌ Error: ${data.error}\n`;
99
+ this.statusText = 'Error';
100
+ this.statusClass = 'bg-red-500';
101
+ break;
102
+ }
103
+ },
104
+
105
+ async loadDocumentation(commandName) {
106
+ this.docsHtml = `
107
+ <div class="space-y-4">
108
+ <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">${commandName}</h3>
109
+ <div class="text-sm text-font-subtle-light dark:text-font-subtle-dark">
110
+ <p class="mb-2">Loading documentation for <code class="bg-base-200 dark:bg-base-700 px-2 py-1 rounded">${commandName}</code>...</p>
111
+ <p class="mt-4">To view full documentation, run:</p>
112
+ <pre class="bg-base-200 dark:bg-base-700 p-3 rounded-lg mt-2"><code>python manage.py help ${commandName}</code></pre>
113
+ </div>
114
+ </div>
115
+ `;
116
+
117
+ try {
118
+ const response = await fetch(`/cfg/commands/help/${commandName}/`);
119
+ const data = await response.json();
120
+ if (data.help_text) {
121
+ this.docsHtml = `
122
+ <div class="space-y-4">
123
+ <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">${commandName}</h3>
124
+ <pre class="text-sm text-font-default-light dark:text-font-default-dark whitespace-pre-wrap">${data.help_text}</pre>
125
+ </div>
126
+ `;
127
+ }
128
+ } catch (error) {
129
+ console.error('Error loading documentation:', error);
130
+ this.docsHtml = `
131
+ <div class="text-red-600 dark:text-red-400">
132
+ <p>Failed to load documentation.</p>
133
+ <p class="text-sm mt-2">Try running: <code class="bg-base-200 dark:bg-base-700 px-2 py-1 rounded">python manage.py help ${commandName}</code></p>
134
+ </div>
135
+ `;
136
+ }
137
+ },
138
+
139
+ close() {
140
+ this.open = false;
141
+ }
142
+ };
143
+ }
144
+
145
+ // Register component when Alpine initializes
146
+ document.addEventListener('alpine:init', () => {
147
+ Alpine.data('commandModal', commandModalComponent);
148
+ });
149
+
150
+ // Global wrapper for backward compatibility
151
+ window.executeCommand = function(commandName) {
152
+ const modalEl = document.querySelector('[x-data="commandModal"]');
153
+ if (modalEl && Alpine) {
154
+ const component = Alpine.$data(modalEl);
155
+ if (component && component.execute) {
156
+ component.execute(commandName);
157
+ }
158
+ }
159
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Commands Panel Alpine.js Component
3
+ *
4
+ * Manages command search, filtering, and category toggling
5
+ */
6
+
7
+ function commandsPanelComponent(totalCommands) {
8
+ return {
9
+ searchQuery: '',
10
+ totalCommands: totalCommands,
11
+ visibleCommands: totalCommands,
12
+ expandedCategories: new Set(),
13
+
14
+ init() {
15
+ // Keyboard shortcuts
16
+ document.addEventListener('keydown', (e) => {
17
+ // Focus search with Ctrl+F or Cmd+F
18
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
19
+ e.preventDefault();
20
+ this.$refs.searchInput?.focus();
21
+ }
22
+
23
+ // Clear search with Escape
24
+ if (e.key === 'Escape' && document.activeElement === this.$refs.searchInput) {
25
+ this.clearSearch();
26
+ this.$refs.searchInput?.blur();
27
+ }
28
+ });
29
+ },
30
+
31
+ search() {
32
+ const query = this.searchQuery.toLowerCase().trim();
33
+ let visibleCount = 0;
34
+
35
+ // Get all categories
36
+ const categories = document.querySelectorAll('[id^="content-"]');
37
+
38
+ categories.forEach(category => {
39
+ const categoryName = category.id.replace('content-', '');
40
+ const commands = category.querySelectorAll('.command-item');
41
+ let categoryHasVisibleCommands = false;
42
+
43
+ commands.forEach(command => {
44
+ const commandName = command.querySelector('.command-name')?.textContent.toLowerCase() || '';
45
+ const commandDesc = command.querySelector('.command-description')?.textContent.toLowerCase() || '';
46
+
47
+ if (!query || commandName.includes(query) || commandDesc.includes(query)) {
48
+ command.style.display = 'block';
49
+ categoryHasVisibleCommands = true;
50
+ visibleCount++;
51
+ } else {
52
+ command.style.display = 'none';
53
+ }
54
+ });
55
+
56
+ // Show/hide category based on whether it has visible commands
57
+ const categoryHeader = document.querySelector(`button[data-category="${categoryName}"]`);
58
+ const categoryContainer = categoryHeader?.parentElement;
59
+
60
+ if (categoryContainer) {
61
+ if (categoryHasVisibleCommands) {
62
+ categoryContainer.style.display = 'block';
63
+
64
+ // Auto-expand categories when searching
65
+ if (query) {
66
+ this.expandedCategories.add(categoryName);
67
+ }
68
+ } else {
69
+ categoryContainer.style.display = 'none';
70
+ }
71
+ }
72
+ });
73
+
74
+ this.visibleCommands = visibleCount;
75
+ },
76
+
77
+ clearSearch() {
78
+ this.searchQuery = '';
79
+ this.visibleCommands = this.totalCommands;
80
+
81
+ // Show all commands and categories
82
+ const categories = document.querySelectorAll('[id^="content-"]');
83
+ const allCommands = document.querySelectorAll('.command-item');
84
+
85
+ categories.forEach(category => {
86
+ const categoryName = category.id.replace('content-', '');
87
+ const categoryHeader = document.querySelector(`button[data-category="${categoryName}"]`);
88
+ const categoryContainer = categoryHeader?.parentElement;
89
+
90
+ if (categoryContainer) {
91
+ categoryContainer.style.display = 'block';
92
+ }
93
+
94
+ // Collapse all categories
95
+ this.expandedCategories.delete(categoryName);
96
+ });
97
+
98
+ allCommands.forEach(command => {
99
+ command.style.display = 'block';
100
+ });
101
+ },
102
+
103
+ toggleCategory(categoryName) {
104
+ if (this.expandedCategories.has(categoryName)) {
105
+ this.expandedCategories.delete(categoryName);
106
+ } else {
107
+ this.expandedCategories.add(categoryName);
108
+ }
109
+ },
110
+
111
+ isCategoryExpanded(categoryName) {
112
+ return this.expandedCategories.has(categoryName);
113
+ },
114
+
115
+ get showNoResults() {
116
+ return this.visibleCommands === 0 && this.searchQuery.trim() !== '';
117
+ },
118
+
119
+ get showClearButton() {
120
+ return this.searchQuery.trim() !== '';
121
+ }
122
+ };
123
+ }
124
+
125
+ // Register component when Alpine initializes
126
+ document.addEventListener('alpine:init', () => {
127
+ Alpine.data('commandsPanel', commandsPanelComponent);
128
+ });
129
+
130
+ // Global wrapper for backward compatibility
131
+ window.toggleCategory = function(category) {
132
+ const panel = document.querySelector('[x-data*="commandsPanel"]');
133
+ if (panel && Alpine) {
134
+ const component = Alpine.$data(panel);
135
+ if (component && component.toggleCategory) {
136
+ component.toggleCategory(category);
137
+ }
138
+ }
139
+ };