experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: clean up settings and diagnostic UI

- Remove unused "Features" section from core settings pane
- Update datastore viewer stats to show Items/Tags/Visits instead of
old Addresses/Visits/Content
- Add totalItems and totalTags to DatastoreStats type
- Remove outdated localStorage migration tools from diagnostic pane
(kept backup tools which are still useful)

+9 -250
+2 -2
app/datastore/viewer.js
··· 116 116 statsArea.innerHTML = ''; 117 117 118 118 const statItems = [ 119 - { label: 'Addresses', value: stats.totalAddresses || 0 }, 119 + { label: 'Items', value: stats.totalItems || stats.totalAddresses || 0 }, 120 + { label: 'Tags', value: stats.totalTags || 0 }, 120 121 { label: 'Visits', value: stats.totalVisits || 0 }, 121 - { label: 'Content', value: stats.totalContent || 0 }, 122 122 ]; 123 123 124 124 statItems.forEach(({ label, value }) => {
-188
app/diagnostic.html
··· 27 27 </div> 28 28 29 29 <div class="section" style="margin-top: 20px; padding: 15px; background: #2d2d2d; border-radius: 5px;"> 30 - <h3 style="margin-top: 0; color: #ff9800;">Migration Tools</h3> 31 - <p style="color: #aaa; font-size: 12px;">Use these to fix migration issues:</p> 32 - <button onclick="forceMigrateAll()" style="background: #ff9800; font-weight: bold;">Migrate ALL to Datastore (Recommended)</button> 33 - <button onclick="forceMigratePeeks()">Migrate Peeks</button> 34 - <button onclick="forceMigrateSlides()">Migrate Slides</button> 35 - <button onclick="clearExtensionSettings()" style="background: #f44336;">Clear extension_settings</button> 36 - </div> 37 - 38 - <div class="section" style="margin-top: 20px; padding: 15px; background: #2d2d2d; border-radius: 5px;"> 39 30 <h3 style="margin-top: 0; color: #4fc3f7;">Backup Tools</h3> 40 31 <p style="color: #aaa; font-size: 12px;">Create and manage database backups:</p> 41 32 <button onclick="createBackup()">Backup Now</button> ··· 149 140 navigator.clipboard.writeText(text).then(() => { 150 141 alert('Copied to clipboard!'); 151 142 }); 152 - }; 153 - 154 - // Extension UUID to shortname mapping (from app/config.js) 155 - const extensionMap = { 156 - 'ef3bd271-d408-421f-9338-47b615571e43': 'peeks', 157 - '434108f3-18a6-437a-b507-2f998f693bb2': 'slides', 158 - '82de735f-a4b7-4fe6-a458-ec29939ae00d': 'groups', 159 - 'cee1225d-40ac-41e5-a34c-e2edba69d599': 'cmd', 160 - '30c25027-d367-4595-b37f-9db3de853c37': 'scripts' 161 - }; 162 - 163 - // Fix function: Copy from UUID keys to shortname keys in localStorage 164 - window.fixPeeksAndSlides = function() { 165 - clearOutput(); 166 - let results = []; 167 - 168 - // Peeks: copy from UUID to shortname 169 - const peeksUuidItems = localStorage.getItem('ef3bd271-d408-421f-9338-47b615571e43+items'); 170 - const peeksUuidPrefs = localStorage.getItem('ef3bd271-d408-421f-9338-47b615571e43+prefs'); 171 - 172 - if (peeksUuidItems) { 173 - localStorage.setItem('peeks+items', peeksUuidItems); 174 - const items = JSON.parse(peeksUuidItems); 175 - results.push(`Peeks items: Copied ${items.length} items from UUID key to shortname key`); 176 - } else { 177 - results.push('Peeks items: No UUID data found'); 178 - } 179 - 180 - if (peeksUuidPrefs) { 181 - localStorage.setItem('peeks+prefs', peeksUuidPrefs); 182 - results.push(`Peeks prefs: Copied from UUID key to shortname key`); 183 - } 184 - 185 - // Slides: copy from UUID to shortname 186 - const slidesUuidItems = localStorage.getItem('434108f3-18a6-437a-b507-2f998f693bb2+items'); 187 - const slidesUuidPrefs = localStorage.getItem('434108f3-18a6-437a-b507-2f998f693bb2+prefs'); 188 - 189 - if (slidesUuidItems) { 190 - localStorage.setItem('slides+items', slidesUuidItems); 191 - const items = JSON.parse(slidesUuidItems); 192 - results.push(`Slides items: Copied ${items.length} items from UUID key to shortname key`); 193 - } else { 194 - results.push('Slides items: No UUID data found'); 195 - } 196 - 197 - if (slidesUuidPrefs) { 198 - localStorage.setItem('slides+prefs', slidesUuidPrefs); 199 - results.push(`Slides prefs: Copied from UUID key to shortname key`); 200 - } 201 - 202 - // Groups: copy from UUID to shortname 203 - const groupsUuidPrefs = localStorage.getItem('82de735f-a4b7-4fe6-a458-ec29939ae00d+prefs'); 204 - if (groupsUuidPrefs) { 205 - localStorage.setItem('groups+prefs', groupsUuidPrefs); 206 - results.push(`Groups prefs: Copied from UUID key to shortname key`); 207 - } 208 - 209 - log('Fix Results', results.join('\n')); 210 - log('Next Steps', 'Close Settings and reopen it. Your peeks/slides should now show the correct data.\n\nIf using the datastore, also click "Force Migrate" buttons.'); 211 - 212 - // Refresh display 213 - window.dumpLocalStorage(); 214 - }; 215 - 216 - window.clearExtensionSettings = async function() { 217 - if (!confirm('This will clear all extension settings from the datastore. Continue?')) return; 218 - 219 - try { 220 - // Get all rows and delete them one by one 221 - const result = await api.datastore.getTable('extension_settings'); 222 - if (result.success) { 223 - const ids = Object.keys(result.data); 224 - for (const id of ids) { 225 - // Use raw SQL via evaluate if available, or just log 226 - console.log('Would delete:', id); 227 - } 228 - log('Clear Result', `Found ${ids.length} rows. Note: Direct deletion not implemented yet.\nPlease restart app after clearing to re-run migration.`); 229 - } 230 - } catch (err) { 231 - log('Clear Error', err.message); 232 - } 233 - 234 - // For now, show what would be cleared 235 - await window.dumpDatastore(); 236 - }; 237 - 238 - async function migrateExtension(uuid, shortname) { 239 - // Try both UUID keys and shortname keys (prefer UUID as it's the original) 240 - const uuidPrefsKey = `${uuid}+prefs`; 241 - const uuidItemsKey = `${uuid}+items`; 242 - const shortPrefsKey = `${shortname}+prefs`; 243 - const shortItemsKey = `${shortname}+items`; 244 - 245 - // Prefer UUID keys, fallback to shortname keys 246 - const prefsStr = localStorage.getItem(uuidPrefsKey) || localStorage.getItem(shortPrefsKey); 247 - const itemsStr = localStorage.getItem(uuidItemsKey) || localStorage.getItem(shortItemsKey); 248 - 249 - let migrated = []; 250 - const now = Date.now(); 251 - 252 - const prefsSource = localStorage.getItem(uuidPrefsKey) ? uuidPrefsKey : 253 - localStorage.getItem(shortPrefsKey) ? shortPrefsKey : null; 254 - const itemsSource = localStorage.getItem(uuidItemsKey) ? uuidItemsKey : 255 - localStorage.getItem(shortItemsKey) ? shortItemsKey : null; 256 - 257 - if (prefsStr) { 258 - try { 259 - const prefs = JSON.parse(prefsStr); 260 - await api.datastore.setRow('extension_settings', `${shortname}:prefs`, { 261 - extensionId: shortname, 262 - key: 'prefs', 263 - value: JSON.stringify(prefs), 264 - updatedAt: now 265 - }); 266 - migrated.push(`prefs: migrated from ${prefsSource}`); 267 - } catch (e) { 268 - migrated.push(`prefs error: ${e.message}`); 269 - } 270 - } else { 271 - migrated.push(`prefs: not found in localStorage`); 272 - } 273 - 274 - if (itemsStr) { 275 - try { 276 - const items = JSON.parse(itemsStr); 277 - await api.datastore.setRow('extension_settings', `${shortname}:items`, { 278 - extensionId: shortname, 279 - key: 'items', 280 - value: JSON.stringify(items), 281 - updatedAt: now 282 - }); 283 - migrated.push(`items: Array[${items.length}] migrated from ${itemsSource}`); 284 - } catch (e) { 285 - migrated.push(`items error: ${e.message}`); 286 - } 287 - } else { 288 - migrated.push(`items: not found in localStorage`); 289 - } 290 - 291 - return migrated; 292 - } 293 - 294 - window.forceMigratePeeks = async function() { 295 - clearOutput(); 296 - const result = await migrateExtension('ef3bd271-d408-421f-9338-47b615571e43', 'peeks'); 297 - log('Peeks Migration', result.join('\n')); 298 - 299 - // Notify extension to reload 300 - api.publish('peeks:settings-changed', {}, api.scopes.GLOBAL); 301 - log('Notification', 'Sent peeks:settings-changed to reload extension'); 302 - 303 - await window.dumpDatastore(); 304 - }; 305 - 306 - window.forceMigrateSlides = async function() { 307 - clearOutput(); 308 - const result = await migrateExtension('434108f3-18a6-437a-b507-2f998f693bb2', 'slides'); 309 - log('Slides Migration', result.join('\n')); 310 - 311 - // Notify extension to reload 312 - api.publish('slides:settings-changed', {}, api.scopes.GLOBAL); 313 - log('Notification', 'Sent slides:settings-changed to reload extension'); 314 - 315 - await window.dumpDatastore(); 316 - }; 317 - 318 - window.forceMigrateAll = async function() { 319 - clearOutput(); 320 - 321 - for (const [uuid, shortname] of Object.entries(extensionMap)) { 322 - const result = await migrateExtension(uuid, shortname); 323 - log(`${shortname} Migration`, result.join('\n')); 324 - 325 - // Notify extension 326 - api.publish(`${shortname}:settings-changed`, {}, api.scopes.GLOBAL); 327 - } 328 - 329 - log('Notification', 'Sent settings-changed for all extensions'); 330 - await window.dumpDatastore(); 331 143 }; 332 144 333 145 // ==================== Backup Functions ====================
-59
app/settings/settings.js
··· 157 157 container.appendChild(prefsSection); 158 158 } 159 159 160 - // Features (core only - extensions are managed in Extensions section) 161 - const extensionNames = ['groups', 'peeks', 'slides']; 162 - const coreFeatures = features ? features.filter(f => !extensionNames.includes(f.name.toLowerCase())) : []; 163 - if (coreFeatures.length > 0) { 164 - const featuresSection = document.createElement('div'); 165 - featuresSection.className = 'form-section'; 166 - 167 - const title = document.createElement('h3'); 168 - title.className = 'form-section-title'; 169 - title.textContent = 'Features'; 170 - featuresSection.appendChild(title); 171 - 172 - coreFeatures.forEach((feature) => { 173 - // Find original index for saving 174 - const i = features.findIndex(f => f.id === feature.id); 175 - 176 - const item = document.createElement('div'); 177 - item.className = 'feature-item'; 178 - 179 - const header = document.createElement('div'); 180 - header.className = 'feature-header'; 181 - 182 - const name = document.createElement('div'); 183 - name.className = 'feature-name'; 184 - name.textContent = feature.name; 185 - header.appendChild(name); 186 - 187 - const wrapper = document.createElement('div'); 188 - wrapper.className = 'checkbox-wrapper'; 189 - 190 - const checkbox = document.createElement('input'); 191 - checkbox.type = 'checkbox'; 192 - checkbox.checked = feature.enabled; 193 - checkbox.addEventListener('change', (e) => { 194 - features[i].enabled = e.target.checked; 195 - save(); 196 - api.publish('core:feature:toggle', { 197 - featureId: feature.id, 198 - enabled: e.target.checked 199 - }); 200 - }); 201 - 202 - wrapper.appendChild(checkbox); 203 - header.appendChild(wrapper); 204 - item.appendChild(header); 205 - 206 - if (feature.description) { 207 - const desc = document.createElement('div'); 208 - desc.className = 'feature-description'; 209 - desc.textContent = feature.description; 210 - item.appendChild(desc); 211 - } 212 - 213 - featuresSection.appendChild(item); 214 - }); 215 - 216 - container.appendChild(featuresSection); 217 - } 218 - 219 160 return container; 220 161 }; 221 162
+3 -1
backend/electron/datastore.ts
··· 1900 1900 totalVisits: (d.prepare('SELECT COUNT(*) as count FROM visits').get() as { count: number }).count, 1901 1901 avgVisitDuration: (d.prepare('SELECT AVG(duration) as avg FROM visits').get() as { avg: number | null }).avg || 0, 1902 1902 totalContent: (d.prepare('SELECT COUNT(*) as count FROM content').get() as { count: number }).count, 1903 - syncedContent: (d.prepare('SELECT COUNT(*) as count FROM content WHERE synced = 1').get() as { count: number }).count 1903 + syncedContent: (d.prepare('SELECT COUNT(*) as count FROM content WHERE synced = 1').get() as { count: number }).count, 1904 + totalItems: (d.prepare('SELECT COUNT(*) as count FROM items').get() as { count: number }).count, 1905 + totalTags: (d.prepare('SELECT COUNT(*) as count FROM tags').get() as { count: number }).count, 1904 1906 }; 1905 1907 } 1906 1908
+2
backend/types/api.ts
··· 292 292 avgVisitDuration: number; 293 293 totalContent: number; 294 294 syncedContent: number; 295 + totalItems: number; 296 + totalTags: number; 295 297 } 296 298 297 299 export interface IDatastoreApi {
+2
backend/types/index.ts
··· 190 190 avgVisitDuration: number; 191 191 totalContent: number; 192 192 syncedContent: number; 193 + totalItems: number; 194 + totalTags: number; 193 195 } 194 196 195 197 // ==================== Filter Types ====================