appclean 1.9.0 → 2.0.0

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.
Files changed (206) hide show
  1. package/GUI_IMPLEMENTATION_STATUS.md +143 -0
  2. package/MD_Files/INDEX.md +51 -0
  3. package/PHASE2_COMPLETION.md +281 -0
  4. package/PHASE3_COMPLETION.md +364 -0
  5. package/README.md +411 -91
  6. package/assets/logo.svg +34 -0
  7. package/dist/core/appUpdateChecker.js +12 -16
  8. package/dist/core/appUpdateChecker.js.map +1 -1
  9. package/dist/core/detector.js +14 -18
  10. package/dist/core/detector.js.map +1 -1
  11. package/dist/core/duplicateFileFinder.js +12 -19
  12. package/dist/core/duplicateFileFinder.js.map +1 -1
  13. package/dist/core/orphanedDependencyDetector.js +19 -26
  14. package/dist/core/orphanedDependencyDetector.js.map +1 -1
  15. package/dist/core/performanceOptimizer.js +6 -10
  16. package/dist/core/performanceOptimizer.js.map +1 -1
  17. package/dist/core/permissionHandler.js +21 -25
  18. package/dist/core/permissionHandler.js.map +1 -1
  19. package/dist/core/pluginSystem.js +9 -13
  20. package/dist/core/pluginSystem.js.map +1 -1
  21. package/dist/core/removalRecorder.js +12 -19
  22. package/dist/core/removalRecorder.js.map +1 -1
  23. package/dist/core/remover.js +59 -66
  24. package/dist/core/remover.js.map +1 -1
  25. package/dist/core/reportGenerator.d.ts +1 -1
  26. package/dist/core/reportGenerator.d.ts.map +1 -1
  27. package/dist/core/reportGenerator.js +27 -34
  28. package/dist/core/reportGenerator.js.map +1 -1
  29. package/dist/core/scheduledCleanup.js +23 -30
  30. package/dist/core/scheduledCleanup.js.map +1 -1
  31. package/dist/core/serviceFileDetector.js +24 -31
  32. package/dist/core/serviceFileDetector.js.map +1 -1
  33. package/dist/core/verificationModule.js +10 -14
  34. package/dist/core/verificationModule.js.map +1 -1
  35. package/dist/index.js +118 -156
  36. package/dist/index.js.map +1 -1
  37. package/dist/managers/brewManager.js +30 -37
  38. package/dist/managers/brewManager.js.map +1 -1
  39. package/dist/managers/customManager.js +23 -30
  40. package/dist/managers/customManager.js.map +1 -1
  41. package/dist/managers/linuxManager.js +29 -36
  42. package/dist/managers/linuxManager.js.map +1 -1
  43. package/dist/managers/npmManager.js +27 -34
  44. package/dist/managers/npmManager.js.map +1 -1
  45. package/dist/types/index.js +1 -2
  46. package/dist/ui/client/api/client.d.ts +24 -0
  47. package/dist/ui/client/api/client.d.ts.map +1 -0
  48. package/dist/ui/client/api/client.js +96 -0
  49. package/dist/ui/client/api/client.js.map +1 -0
  50. package/dist/ui/client/app.d.ts +7 -0
  51. package/dist/ui/client/app.d.ts.map +1 -0
  52. package/dist/ui/client/app.js +71 -0
  53. package/dist/ui/client/app.js.map +1 -0
  54. package/dist/ui/client/index.html +107 -0
  55. package/dist/ui/client/pages/appDetails.d.ts +8 -0
  56. package/dist/ui/client/pages/appDetails.d.ts.map +1 -0
  57. package/dist/ui/client/pages/appDetails.js +287 -0
  58. package/dist/ui/client/pages/appDetails.js.map +1 -0
  59. package/dist/ui/client/pages/appSearch.d.ts +2 -0
  60. package/dist/ui/client/pages/appSearch.d.ts.map +1 -0
  61. package/dist/ui/client/pages/appSearch.js +210 -0
  62. package/dist/ui/client/pages/appSearch.js.map +1 -0
  63. package/dist/ui/client/pages/dashboard.d.ts +2 -0
  64. package/dist/ui/client/pages/dashboard.d.ts.map +1 -0
  65. package/dist/ui/client/pages/dashboard.js +154 -0
  66. package/dist/ui/client/pages/dashboard.js.map +1 -0
  67. package/dist/ui/client/pages/settings.d.ts +7 -0
  68. package/dist/ui/client/pages/settings.d.ts.map +1 -0
  69. package/dist/ui/client/pages/settings.js +279 -0
  70. package/dist/ui/client/pages/settings.js.map +1 -0
  71. package/dist/ui/client/state/appStore.d.ts +38 -0
  72. package/dist/ui/client/state/appStore.d.ts.map +1 -0
  73. package/dist/ui/client/state/appStore.js +121 -0
  74. package/dist/ui/client/state/appStore.js.map +1 -0
  75. package/dist/ui/client/state/dashboardStore.d.ts +31 -0
  76. package/dist/ui/client/state/dashboardStore.d.ts.map +1 -0
  77. package/dist/ui/client/state/dashboardStore.js +70 -0
  78. package/dist/ui/client/state/dashboardStore.js.map +1 -0
  79. package/dist/ui/client/state/uiStore.d.ts +43 -0
  80. package/dist/ui/client/state/uiStore.d.ts.map +1 -0
  81. package/dist/ui/client/state/uiStore.js +109 -0
  82. package/dist/ui/client/state/uiStore.js.map +1 -0
  83. package/dist/ui/client/styles/animations.css +327 -0
  84. package/dist/ui/client/styles/base.css +214 -0
  85. package/dist/ui/client/styles/components.css +400 -0
  86. package/dist/ui/client/styles/layout.css +224 -0
  87. package/dist/ui/client/styles/variables.css +140 -0
  88. package/dist/ui/client/utils/events.d.ts +19 -0
  89. package/dist/ui/client/utils/events.d.ts.map +1 -0
  90. package/dist/ui/client/utils/events.js +54 -0
  91. package/dist/ui/client/utils/events.js.map +1 -0
  92. package/dist/ui/client/utils/formatting.d.ts +11 -0
  93. package/dist/ui/client/utils/formatting.d.ts.map +1 -0
  94. package/dist/ui/client/utils/formatting.js +104 -0
  95. package/dist/ui/client/utils/formatting.js.map +1 -0
  96. package/dist/ui/client/utils/router.d.ts +25 -0
  97. package/dist/ui/client/utils/router.d.ts.map +1 -0
  98. package/dist/ui/client/utils/router.js +90 -0
  99. package/dist/ui/client/utils/router.js.map +1 -0
  100. package/dist/ui/guiServer.d.ts +8 -5
  101. package/dist/ui/guiServer.d.ts.map +1 -1
  102. package/dist/ui/guiServer.js +137 -505
  103. package/dist/ui/guiServer.js.map +1 -1
  104. package/dist/ui/menu.js +18 -27
  105. package/dist/ui/menu.js.map +1 -1
  106. package/dist/ui/prompts.js +34 -47
  107. package/dist/ui/prompts.js.map +1 -1
  108. package/dist/ui/server/middleware/errorHandler.d.ts +19 -0
  109. package/dist/ui/server/middleware/errorHandler.d.ts.map +1 -0
  110. package/dist/ui/server/middleware/errorHandler.js +100 -0
  111. package/dist/ui/server/middleware/errorHandler.js.map +1 -0
  112. package/dist/ui/server/routes/apps.d.ts +8 -0
  113. package/dist/ui/server/routes/apps.d.ts.map +1 -0
  114. package/dist/ui/server/routes/apps.js +74 -0
  115. package/dist/ui/server/routes/apps.js.map +1 -0
  116. package/dist/ui/server/routes/dashboard.d.ts +4 -0
  117. package/dist/ui/server/routes/dashboard.d.ts.map +1 -0
  118. package/dist/ui/server/routes/dashboard.js +57 -0
  119. package/dist/ui/server/routes/dashboard.js.map +1 -0
  120. package/dist/ui/server/routes/settings.d.ts +6 -0
  121. package/dist/ui/server/routes/settings.d.ts.map +1 -0
  122. package/dist/ui/server/routes/settings.js +31 -0
  123. package/dist/ui/server/routes/settings.js.map +1 -0
  124. package/dist/ui/server/services/appService.d.ts +45 -0
  125. package/dist/ui/server/services/appService.d.ts.map +1 -0
  126. package/dist/ui/server/services/appService.js +114 -0
  127. package/dist/ui/server/services/appService.js.map +1 -0
  128. package/dist/ui/server/services/removalService.d.ts +24 -0
  129. package/dist/ui/server/services/removalService.d.ts.map +1 -0
  130. package/dist/ui/server/services/removalService.js +83 -0
  131. package/dist/ui/server/services/removalService.js.map +1 -0
  132. package/dist/utils/filesystem.js +32 -49
  133. package/dist/utils/filesystem.js.map +1 -1
  134. package/dist/utils/logger.js +9 -18
  135. package/dist/utils/logger.js.map +1 -1
  136. package/dist/utils/platform.js +10 -22
  137. package/dist/utils/platform.js.map +1 -1
  138. package/dist/utils/upgrade.js +10 -14
  139. package/dist/utils/upgrade.js.map +1 -1
  140. package/package.json +4 -2
  141. package/src/core/appUpdateChecker.ts +1 -1
  142. package/src/core/detector.ts +6 -6
  143. package/src/core/duplicateFileFinder.ts +1 -1
  144. package/src/core/orphanedDependencyDetector.ts +2 -2
  145. package/src/core/performanceOptimizer.ts +1 -1
  146. package/src/core/permissionHandler.ts +2 -2
  147. package/src/core/pluginSystem.ts +1 -1
  148. package/src/core/removalRecorder.ts +2 -2
  149. package/src/core/remover.ts +11 -11
  150. package/src/core/reportGenerator.ts +2 -2
  151. package/src/core/scheduledCleanup.ts +2 -2
  152. package/src/core/serviceFileDetector.ts +2 -2
  153. package/src/core/verificationModule.ts +2 -2
  154. package/src/index.ts +8 -8
  155. package/src/managers/brewManager.ts +3 -3
  156. package/src/managers/customManager.ts +2 -2
  157. package/src/managers/linuxManager.ts +3 -3
  158. package/src/managers/npmManager.ts +3 -3
  159. package/src/ui/client/api/client.ts +163 -0
  160. package/src/ui/client/app.ts +121 -0
  161. package/src/ui/client/index.html +107 -0
  162. package/src/ui/client/pages/appDetails.ts +356 -0
  163. package/src/ui/client/pages/appSearch.ts +270 -0
  164. package/src/ui/client/pages/dashboard.ts +189 -0
  165. package/src/ui/client/pages/settings.ts +342 -0
  166. package/src/ui/client/state/appStore.ts +169 -0
  167. package/src/ui/client/state/dashboardStore.ts +113 -0
  168. package/src/ui/client/state/uiStore.ts +166 -0
  169. package/src/ui/client/styles/animations.css +327 -0
  170. package/src/ui/client/styles/base.css +214 -0
  171. package/src/ui/client/styles/components.css +400 -0
  172. package/src/ui/client/styles/layout.css +224 -0
  173. package/src/ui/client/styles/variables.css +140 -0
  174. package/src/ui/client/utils/events.ts +74 -0
  175. package/src/ui/client/utils/formatting.ts +157 -0
  176. package/src/ui/client/utils/router.ts +161 -0
  177. package/src/ui/guiServer.ts +183 -502
  178. package/src/ui/prompts.ts +1 -1
  179. package/src/ui/server/middleware/errorHandler.ts +174 -0
  180. package/src/ui/server/routes/apps.ts +132 -0
  181. package/src/ui/server/routes/dashboard.ts +93 -0
  182. package/src/ui/server/routes/settings.ts +63 -0
  183. package/src/ui/server/services/appService.ts +184 -0
  184. package/src/ui/server/services/removalService.ts +138 -0
  185. package/src/utils/upgrade.ts +1 -1
  186. package/tsconfig.json +3 -2
  187. package/INDEX.md +0 -165
  188. /package/{ACTION_CHECKLIST.md → MD_Files/ACTION_CHECKLIST.md} +0 -0
  189. /package/{APPCLEAN_SUMMARY.md → MD_Files/APPCLEAN_SUMMARY.md} +0 -0
  190. /package/{CHANGELOG.md → MD_Files/CHANGELOG.md} +0 -0
  191. /package/{CODE_OF_CONDUCT.md → MD_Files/CODE_OF_CONDUCT.md} +0 -0
  192. /package/{CODE_REVIEW_REPORT.md → MD_Files/CODE_REVIEW_REPORT.md} +0 -0
  193. /package/{COMMUNITY_POSTS.md → MD_Files/COMMUNITY_POSTS.md} +0 -0
  194. /package/{DEPLOYMENT_GUIDE.md → MD_Files/DEPLOYMENT_GUIDE.md} +0 -0
  195. /package/{DEPLOYMENT_STATUS.md → MD_Files/DEPLOYMENT_STATUS.md} +0 -0
  196. /package/{EXECUTIVE_REPORT.md → MD_Files/EXECUTIVE_REPORT.md} +0 -0
  197. /package/{GITHUB_OPTIMIZATION.md → MD_Files/GITHUB_OPTIMIZATION.md} +0 -0
  198. /package/{MARKETING_SUMMARY.md → MD_Files/MARKETING_SUMMARY.md} +0 -0
  199. /package/{NPM_PACKAGE_OPTIMIZATION.md → MD_Files/NPM_PACKAGE_OPTIMIZATION.md} +0 -0
  200. /package/{NPM_PUBLISH.md → MD_Files/NPM_PUBLISH.md} +0 -0
  201. /package/{PROJECT_SUMMARY.txt → MD_Files/PROJECT_SUMMARY.txt} +0 -0
  202. /package/{PUBLICATION_SUCCESS_REPORT.md → MD_Files/PUBLICATION_SUCCESS_REPORT.md} +0 -0
  203. /package/{QUICKSTART.md → MD_Files/QUICKSTART.md} +0 -0
  204. /package/{SETUP_GITHUB.md → MD_Files/SETUP_GITHUB.md} +0 -0
  205. /package/{TESTING_SUMMARY.md → MD_Files/TESTING_SUMMARY.md} +0 -0
  206. /package/{setup-github.sh → MD_Files/setup-github.sh} +0 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * App Search Page - Search, filter, and browse installed applications
3
+ */
4
+
5
+ import { appStore, InstalledApp, SearchOptions } from '../state/appStore.js';
6
+ import { uiStore } from '../state/uiStore.js';
7
+ import { formatBytes, getMethodBadgeColor, formatInstallMethod, debounce } from '../utils/formatting.js';
8
+
9
+ /**
10
+ * Render App Search Page
11
+ */
12
+ export function renderAppSearch(): void {
13
+ const container = document.getElementById('page-container');
14
+ if (!container) return;
15
+
16
+ // Show loading state
17
+ container.innerHTML = `
18
+ <div class="loading-state" style="text-align: center; padding: 60px 20px;">
19
+ <div class="spinner-lg spinner"></div>
20
+ <p class="text-center text-muted mt-4">Loading apps...</p>
21
+ </div>
22
+ `;
23
+
24
+ // Load apps
25
+ appStore.loadApps().then(() => {
26
+ renderAppSearchContent(container);
27
+
28
+ // Subscribe to store updates
29
+ appStore.subscribe((state) => {
30
+ if (!state.isLoading) {
31
+ renderAppSearchContent(container);
32
+ }
33
+ });
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Render app search page content
39
+ */
40
+ function renderAppSearchContent(container: HTMLElement): void {
41
+ const state = appStore.getState();
42
+
43
+ container.innerHTML = `
44
+ <div class="app-search-page">
45
+ <!-- Header -->
46
+ <div class="page-header mb-8">
47
+ <h1 class="text-3xl font-bold">📦 Applications</h1>
48
+ <p class="text-secondary mt-2">Search and manage installed apps</p>
49
+ </div>
50
+
51
+ <!-- Search and Filters -->
52
+ <div class="card mb-6">
53
+ <div class="card-body">
54
+ <!-- Search Input -->
55
+ <div class="form-group">
56
+ <input
57
+ type="text"
58
+ id="search-input"
59
+ class="form-input"
60
+ placeholder="Search apps by name..."
61
+ value="${escapeHtml(state.searchOptions.query)}"
62
+ autocomplete="off"
63
+ >
64
+ </div>
65
+
66
+ <!-- Filters Row -->
67
+ <div class="flex flex-wrap gap-2 items-center">
68
+ <span class="text-sm font-medium text-muted">Method:</span>
69
+ ${renderMethodFilter(state.searchOptions.installMethod || '')}
70
+
71
+ <span class="text-sm font-medium text-muted ml-4">Sort:</span>
72
+ ${renderSortFilter(state.searchOptions.sortBy || 'name')}
73
+ </div>
74
+
75
+ <!-- Results Count -->
76
+ <p class="text-sm text-muted mt-4">
77
+ ${state.isLoading ? 'Loading...' : `${state.total} app${state.total !== 1 ? 's' : ''} found`}
78
+ </p>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Apps Grid -->
83
+ <div class="apps-grid">
84
+ ${
85
+ state.apps.length > 0
86
+ ? `
87
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
88
+ ${state.apps.map((app) => renderAppCard(app)).join('')}
89
+ </div>
90
+ `
91
+ : `
92
+ <div class="card">
93
+ <div class="card-body text-center py-12">
94
+ <p class="text-2xl mb-2">🔍</p>
95
+ <p class="text-lg font-medium">No apps found</p>
96
+ <p class="text-muted mt-2">
97
+ ${state.searchOptions.query ? 'Try a different search query' : 'No applications detected'}
98
+ </p>
99
+ </div>
100
+ </div>
101
+ `
102
+ }
103
+ </div>
104
+
105
+ <!-- Load More Button -->
106
+ ${
107
+ state.apps.length < state.total
108
+ ? `
109
+ <div class="text-center mt-8">
110
+ <button class="btn btn-secondary" id="load-more-btn">
111
+ Load More (${state.apps.length} of ${state.total})
112
+ </button>
113
+ </div>
114
+ `
115
+ : ''
116
+ }
117
+ </div>
118
+ `;
119
+
120
+ // Setup event listeners
121
+ setupSearchListeners();
122
+ setupLoadMoreListener();
123
+
124
+ // Scroll to top
125
+ window.scrollTo(0, 0);
126
+ }
127
+
128
+ /**
129
+ * Render app card
130
+ */
131
+ function renderAppCard(app: InstalledApp): string {
132
+ return `
133
+ <div class="card app-card hover-lift cursor-pointer" onclick="window.location.hash = '#/apps/${encodeURIComponent(app.name)}'">
134
+ <div class="card-body">
135
+ <div class="flex-between mb-2">
136
+ <h3 class="text-lg font-bold">${escapeHtml(app.name)}</h3>
137
+ <span class="badge ${getMethodBadgeColor(app.installMethod)}">
138
+ ${formatInstallMethod(app.installMethod)}
139
+ </span>
140
+ </div>
141
+
142
+ <p class="text-sm text-muted mb-3">
143
+ ${app.version ? `v${escapeHtml(app.version)}` : 'Version unknown'}
144
+ </p>
145
+
146
+ <div class="flex-between pt-3 border-t border-color">
147
+ <span class="text-sm text-muted">
148
+ ${app.size ? formatBytes(app.size) : 'Size unknown'}
149
+ </span>
150
+ <button
151
+ class="btn btn-sm btn-ghost-primary"
152
+ onclick="
153
+ event.stopPropagation();
154
+ window.location.hash = '#/apps/${encodeURIComponent(app.name)}';
155
+ "
156
+ >
157
+ Details →
158
+ </button>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ /**
166
+ * Render method filter buttons
167
+ */
168
+ function renderMethodFilter(selected: string): string {
169
+ const methods = ['', 'npm', 'yarn', 'pnpm', 'brew', 'apt', 'yum', 'dnf', 'custom'];
170
+ const labels: Record<string, string> = {
171
+ '': 'All',
172
+ 'npm': 'npm',
173
+ 'yarn': 'Yarn',
174
+ 'pnpm': 'pnpm',
175
+ 'brew': 'Homebrew',
176
+ 'apt': 'apt',
177
+ 'yum': 'yum',
178
+ 'dnf': 'dnf',
179
+ 'custom': 'Custom',
180
+ };
181
+
182
+ return methods
183
+ .map(
184
+ (method) => `
185
+ <button
186
+ class="btn btn-sm ${method === selected ? 'btn-primary' : 'btn-ghost'} method-filter"
187
+ data-method="${method}"
188
+ >
189
+ ${labels[method]}
190
+ </button>
191
+ `
192
+ )
193
+ .join('');
194
+ }
195
+
196
+ /**
197
+ * Render sort filter dropdown
198
+ */
199
+ function renderSortFilter(selected: string): string {
200
+ return `
201
+ <select id="sort-select" class="form-input" style="width: auto;">
202
+ <option value="name" ${selected === 'name' ? 'selected' : ''}>Name (A-Z)</option>
203
+ <option value="size" ${selected === 'size' ? 'selected' : ''}>Size (Largest)</option>
204
+ <option value="date" ${selected === 'date' ? 'selected' : ''}>Date (Newest)</option>
205
+ </select>
206
+ `;
207
+ }
208
+
209
+ /**
210
+ * Setup search input listener with debounce
211
+ */
212
+ function setupSearchListeners(): void {
213
+ const searchInput = document.getElementById('search-input') as HTMLInputElement;
214
+ const methodButtons = document.querySelectorAll('.method-filter');
215
+ const sortSelect = document.getElementById('sort-select') as HTMLSelectElement;
216
+
217
+ // Debounced search
218
+ const debouncedSearch = debounce((query: string) => {
219
+ appStore.setSearchQuery(query);
220
+ }, 300);
221
+
222
+ if (searchInput) {
223
+ searchInput.addEventListener('input', (e) => {
224
+ const query = (e.target as HTMLInputElement).value;
225
+ debouncedSearch(query);
226
+ });
227
+ }
228
+
229
+ // Method filter buttons
230
+ methodButtons.forEach((btn) => {
231
+ btn.addEventListener('click', () => {
232
+ const method = btn.getAttribute('data-method');
233
+ appStore.setInstallMethodFilter(method || undefined);
234
+ });
235
+ });
236
+
237
+ // Sort dropdown
238
+ if (sortSelect) {
239
+ sortSelect.addEventListener('change', (e) => {
240
+ const sort = (e.target as HTMLSelectElement).value as any;
241
+ appStore.setSortBy(sort);
242
+ });
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Setup load more button
248
+ */
249
+ function setupLoadMoreListener(): void {
250
+ const loadMoreBtn = document.getElementById('load-more-btn');
251
+ if (loadMoreBtn) {
252
+ loadMoreBtn.addEventListener('click', () => {
253
+ appStore.loadNextPage();
254
+ });
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Escape HTML
260
+ */
261
+ function escapeHtml(text: string): string {
262
+ const map: Record<string, string> = {
263
+ '&': '&amp;',
264
+ '<': '&lt;',
265
+ '>': '&gt;',
266
+ '"': '&quot;',
267
+ "'": '&#039;',
268
+ };
269
+ return text.replace(/[&<>"']/g, (m) => map[m]);
270
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Dashboard Page - Overview of system health and app statistics
3
+ */
4
+
5
+ import { dashboardStore, DashboardStats } from '../state/dashboardStore.js';
6
+ import { uiStore } from '../state/uiStore.js';
7
+ import { formatBytes, formatPercent, formatRelativeTime } from '../utils/formatting.js';
8
+
9
+ /**
10
+ * Render Dashboard Page
11
+ */
12
+ export function renderDashboard(): void {
13
+ const container = document.getElementById('page-container');
14
+ if (!container) return;
15
+
16
+ // Show loading state
17
+ container.innerHTML = `
18
+ <div class="loading-state" style="text-align: center; padding: 60px 20px;">
19
+ <div class="spinner-lg spinner"></div>
20
+ <p class="text-center text-muted mt-4">Loading dashboard...</p>
21
+ </div>
22
+ `;
23
+
24
+ // Load stats
25
+ dashboardStore.refreshStats(true).then(() => {
26
+ const state = dashboardStore.getState();
27
+ const stats = state.stats;
28
+
29
+ if (!stats) {
30
+ container.innerHTML = `
31
+ <div class="alert alert-danger">
32
+ <span>Failed to load dashboard stats</span>
33
+ </div>
34
+ `;
35
+ return;
36
+ }
37
+
38
+ renderDashboardContent(container, stats);
39
+
40
+ // Subscribe to updates
41
+ dashboardStore.subscribe((newState) => {
42
+ if (newState.stats) {
43
+ renderDashboardContent(container, newState.stats);
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Render dashboard content
51
+ */
52
+ function renderDashboardContent(container: HTMLElement, stats: DashboardStats): void {
53
+ container.innerHTML = `
54
+ <div class="dashboard-page">
55
+ <!-- Header -->
56
+ <div class="page-header mb-8">
57
+ <h1 class="text-3xl font-bold">📊 Dashboard</h1>
58
+ <p class="text-secondary mt-2">System overview and app management</p>
59
+ </div>
60
+
61
+ <!-- Stats Grid -->
62
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
63
+ ${renderStatCard('📦 Installed Apps', stats.totalApps.toString(), 'Total applications detected')}
64
+ ${renderStatCard('💾 Total Space Used', formatBytes(stats.totalSpaceUsed), 'Size of all app files')}
65
+ ${renderStatCard('🗑️ Removed (Session)', stats.sessionAppsRemoved.toString(), 'Apps removed this session')}
66
+ ${renderStatCard('📉 Space Freed', formatBytes(stats.sessionSpaceFreed), 'Total space recovered')}
67
+ </div>
68
+
69
+ <!-- System Health -->
70
+ <div class="card mb-8">
71
+ <div class="card-header">
72
+ <h2 class="text-xl font-bold">💻 System Health</h2>
73
+ </div>
74
+ <div class="card-body">
75
+ <div class="health-gauge mb-4">
76
+ <div class="flex-between mb-2">
77
+ <span class="text-sm font-medium">Disk Usage</span>
78
+ <span class="text-sm font-bold">${formatPercent(stats.diskUsagePercent)}</span>
79
+ </div>
80
+ <div class="progress-bar" style="height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden;">
81
+ <div style="
82
+ height: 100%;
83
+ width: ${stats.diskUsagePercent}%;
84
+ background: ${getDiskUsageColor(stats.diskUsagePercent)};
85
+ transition: width 0.3s ease;
86
+ "></div>
87
+ </div>
88
+ </div>
89
+ <p class="text-sm text-muted">
90
+ ${stats.diskUsagePercent < 80
91
+ ? '✓ Disk usage is healthy'
92
+ : stats.diskUsagePercent < 90
93
+ ? '⚠️ Disk usage is getting high'
94
+ : '❌ Disk usage is critical'}
95
+ </p>
96
+ </div>
97
+ </div>
98
+
99
+ <!-- Recent Activity -->
100
+ <div class="card">
101
+ <div class="card-header">
102
+ <h2 class="text-xl font-bold">📜 Recent Activity</h2>
103
+ </div>
104
+ <div class="card-body">
105
+ ${
106
+ stats.recentlyRemoved.length > 0
107
+ ? `
108
+ <div class="activity-list">
109
+ ${stats.recentlyRemoved
110
+ .slice(0, 10)
111
+ .map(
112
+ (removal) => `
113
+ <div class="activity-item flex-between p-3 border-b border-color last:border-0" style="padding: 12px 0;">
114
+ <div class="activity-info">
115
+ <p class="text-sm font-medium">${escapeHtml(removal.appName)}</p>
116
+ <p class="text-xs text-muted">Removed ${formatRelativeTime(removal.timestamp)}</p>
117
+ </div>
118
+ <div class="activity-stats text-right">
119
+ <p class="text-sm font-bold text-success">${formatBytes(removal.freedSpace)}</p>
120
+ <p class="text-xs text-muted">${removal.filesRemoved} files</p>
121
+ </div>
122
+ </div>
123
+ `
124
+ )
125
+ .join('')}
126
+ </div>
127
+ `
128
+ : `
129
+ <p class="text-muted text-center py-8">
130
+ No apps removed yet. Start by searching for apps to remove.
131
+ </p>
132
+ `
133
+ }
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Quick Actions -->
138
+ <div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-4">
139
+ <button class="btn btn-primary btn-lg w-full" onclick="window.location.hash = '#/apps'">
140
+ <span>🔍 Find & Remove Apps</span>
141
+ </button>
142
+ <button class="btn btn-secondary btn-lg w-full" onclick="window.location.hash = '#/settings'">
143
+ <span>⚙️ Settings</span>
144
+ </button>
145
+ </div>
146
+ </div>
147
+ `;
148
+
149
+ // Scroll to top
150
+ window.scrollTo(0, 0);
151
+ }
152
+
153
+ /**
154
+ * Render stat card
155
+ */
156
+ function renderStatCard(label: string, value: string, description: string): string {
157
+ return `
158
+ <div class="card">
159
+ <div class="card-body">
160
+ <p class="text-muted text-sm mb-2">${escapeHtml(label)}</p>
161
+ <p class="text-2xl font-bold text-primary mb-1">${escapeHtml(value)}</p>
162
+ <p class="text-xs text-muted">${escapeHtml(description)}</p>
163
+ </div>
164
+ </div>
165
+ `;
166
+ }
167
+
168
+ /**
169
+ * Get disk usage color
170
+ */
171
+ function getDiskUsageColor(percent: number): string {
172
+ if (percent < 50) return '#10b981'; // Green
173
+ if (percent < 80) return '#f59e0b'; // Amber
174
+ return '#ef4444'; // Red
175
+ }
176
+
177
+ /**
178
+ * Escape HTML to prevent XSS
179
+ */
180
+ function escapeHtml(text: string): string {
181
+ const map: Record<string, string> = {
182
+ '&': '&amp;',
183
+ '<': '&lt;',
184
+ '>': '&gt;',
185
+ '"': '&quot;',
186
+ "'": '&#039;',
187
+ };
188
+ return text.replace(/[&<>"']/g, (m) => map[m]);
189
+ }