experiments in a post-browser web
10
fork

Configure Feed

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

ok more scripts

+243
+16
scripts/kill-dev.sh
··· 1 + #!/bin/bash 2 + # Kill all dev Electron processes from this project 3 + # Usage: ./scripts/kill-dev.sh 4 + 5 + pkill -9 -f "/Users/dietrich/misc/peek/node_modules/electron" 2>/dev/null || true 6 + pkill -9 -f "PROFILE=test" 2>/dev/null || true 7 + 8 + # Verify 9 + sleep 0.5 10 + REMAINING=$(pgrep -fl electron 2>/dev/null | grep -c "/Users/dietrich/misc/peek" || true) 11 + if [ "$REMAINING" -eq 0 ]; then 12 + echo "All dev Electron processes killed" 13 + else 14 + echo "Warning: $REMAINING processes may still be running" 15 + pgrep -fl electron 2>/dev/null | grep "/Users/dietrich/misc/peek" 16 + fi
+159
scripts/migrate-normalize-urls.mjs
··· 1 + /** 2 + * Migration script to normalize URLs and merge duplicate addresses 3 + * Run with: node scripts/migrate-normalize-urls.mjs 4 + */ 5 + 6 + import sqlite3 from 'sqlite3'; 7 + import path from 'path'; 8 + import os from 'os'; 9 + 10 + const dbPath = process.argv[2] || path.join( 11 + os.homedir(), 12 + 'Library/Application Support/Peek/dev/datastore.sqlite' 13 + ); 14 + 15 + console.log('Opening database:', dbPath); 16 + 17 + // Normalize URL helper 18 + const normalizeUrl = (uri) => { 19 + try { 20 + const url = new URL(uri); 21 + if (!url.pathname || url.pathname === '') { 22 + url.pathname = '/'; 23 + } 24 + return url.toString(); 25 + } catch (e) { 26 + return uri; 27 + } 28 + }; 29 + 30 + const run = () => new Promise((resolve, reject) => { 31 + const db = new sqlite3.Database(dbPath); 32 + 33 + db.get('SELECT store FROM tinybase', (err, row) => { 34 + if (err) { 35 + reject(err); 36 + return; 37 + } 38 + 39 + const data = JSON.parse(row.store)[0]; 40 + 41 + console.log('\n=== Before Migration ==='); 42 + console.log('Addresses:', Object.keys(data.addresses).length); 43 + console.log('Visits:', Object.keys(data.visits).length); 44 + console.log('Address tags:', Object.keys(data.address_tags || {}).length); 45 + 46 + // Find duplicates (same normalized URL) 47 + const normalizedMap = new Map(); 48 + for (const [id, addr] of Object.entries(data.addresses)) { 49 + const normalized = normalizeUrl(addr.uri); 50 + if (!normalizedMap.has(normalized)) { 51 + normalizedMap.set(normalized, []); 52 + } 53 + normalizedMap.get(normalized).push({ id, ...addr }); 54 + } 55 + 56 + // Find entries with duplicates 57 + const duplicates = [...normalizedMap.entries()].filter(([, addrs]) => addrs.length > 1); 58 + console.log('\nDuplicates found:', duplicates.length); 59 + 60 + for (const [normalizedUri, addrs] of duplicates) { 61 + console.log(`\nDuplicate: ${normalizedUri}`); 62 + addrs.forEach(a => console.log(` - ${a.id}: ${a.uri} (${a.visitCount} visits)`)); 63 + 64 + // Check which one has tags 65 + const addressTags = data.address_tags || {}; 66 + const withTags = addrs.filter(a => 67 + Object.values(addressTags).some(link => link.addressId === a.id) 68 + ); 69 + 70 + // Keep: prefer one with tags, then one already normalized, then most visits 71 + const sorted = addrs.sort((a, b) => { 72 + const aHasTags = withTags.some(t => t.id === a.id); 73 + const bHasTags = withTags.some(t => t.id === b.id); 74 + if (aHasTags && !bHasTags) return -1; 75 + if (bHasTags && !aHasTags) return 1; 76 + if (a.uri === normalizedUri && b.uri !== normalizedUri) return -1; 77 + if (b.uri === normalizedUri && a.uri !== normalizedUri) return 1; 78 + return (b.visitCount || 0) - (a.visitCount || 0); 79 + }); 80 + 81 + const keep = sorted[0]; 82 + const remove = sorted.slice(1); 83 + 84 + console.log(` Keeping: ${keep.id} (${keep.uri})`); 85 + console.log(` Removing: ${remove.map(r => r.id).join(', ')}`); 86 + 87 + let totalVisits = keep.visitCount || 0; 88 + let latestVisit = keep.lastVisitAt || 0; 89 + 90 + for (const r of remove) { 91 + totalVisits += r.visitCount || 0; 92 + latestVisit = Math.max(latestVisit, r.lastVisitAt || 0); 93 + 94 + // Update visits to point to kept address 95 + for (const [visitId, visit] of Object.entries(data.visits)) { 96 + if (visit.addressId === r.id) { 97 + console.log(` Updating visit ${visitId} -> ${keep.id}`); 98 + data.visits[visitId].addressId = keep.id; 99 + } 100 + } 101 + 102 + // Update address_tags 103 + for (const [linkId, link] of Object.entries(data.address_tags || {})) { 104 + if (link.addressId === r.id) { 105 + const alreadyHasTag = Object.values(data.address_tags).some( 106 + l => l.addressId === keep.id && l.tagId === link.tagId 107 + ); 108 + if (alreadyHasTag) { 109 + console.log(` Removing duplicate tag link ${linkId}`); 110 + delete data.address_tags[linkId]; 111 + } else { 112 + console.log(` Updating tag link ${linkId} -> ${keep.id}`); 113 + data.address_tags[linkId].addressId = keep.id; 114 + } 115 + } 116 + } 117 + 118 + delete data.addresses[r.id]; 119 + } 120 + 121 + data.addresses[keep.id].uri = normalizedUri; 122 + data.addresses[keep.id].visitCount = totalVisits; 123 + data.addresses[keep.id].lastVisitAt = latestVisit; 124 + data.addresses[keep.id].updatedAt = Date.now(); 125 + 126 + console.log(` Merged: ${totalVisits} total visits`); 127 + } 128 + 129 + // Normalize remaining URIs 130 + let normalizedCount = 0; 131 + for (const [id, addr] of Object.entries(data.addresses)) { 132 + const normalized = normalizeUrl(addr.uri); 133 + if (addr.uri !== normalized) { 134 + console.log(`\nNormalizing: ${addr.uri} -> ${normalized}`); 135 + data.addresses[id].uri = normalized; 136 + normalizedCount++; 137 + } 138 + } 139 + 140 + console.log('\n=== After Migration ==='); 141 + console.log('Addresses:', Object.keys(data.addresses).length); 142 + console.log('Visits:', Object.keys(data.visits).length); 143 + console.log('Normalized:', normalizedCount); 144 + 145 + // Write back 146 + const newJson = JSON.stringify([data, {}]); 147 + db.run('UPDATE tinybase SET store = ?', [newJson], (err) => { 148 + if (err) { 149 + reject(err); 150 + return; 151 + } 152 + console.log('\nMigration complete!'); 153 + db.close(); 154 + resolve(); 155 + }); 156 + }); 157 + }); 158 + 159 + run().catch(console.error);
+68
scripts/smoke-test.sh
··· 1 + #!/bin/bash 2 + # Smoke test script - runs the app briefly to verify it starts correctly 3 + # Usage: ./scripts/smoke-test.sh [duration_seconds] [extra_args...] 4 + # Example: ./scripts/smoke-test.sh 10 5 + # Example: ./scripts/smoke-test.sh 15 --some-flag 6 + 7 + DURATION=${1:-10} 8 + shift 2>/dev/null || true 9 + 10 + # Generate unique profile name for this test run 11 + PROFILE="test-smoke-$$" 12 + PIDFILE="/tmp/peek-smoke-$$.pid" 13 + LOGFILE="/tmp/peek-smoke-$$.log" 14 + 15 + cleanup() { 16 + if [ -f "$PIDFILE" ]; then 17 + PID=$(cat "$PIDFILE") 18 + kill "$PID" 2>/dev/null 19 + rm -f "$PIDFILE" 20 + fi 21 + # Also kill any remaining electron processes with our test profile 22 + pkill -f "PROFILE=$PROFILE" 2>/dev/null || true 23 + pkill -f "$PROFILE" 2>/dev/null || true 24 + } 25 + 26 + trap cleanup EXIT 27 + 28 + echo "Starting smoke test (profile: $PROFILE, duration: ${DURATION}s)..." 29 + 30 + # Start the app in background 31 + PROFILE="$PROFILE" DEBUG=1 yarn start "$@" > "$LOGFILE" 2>&1 & 32 + echo $! > "$PIDFILE" 33 + 34 + # Wait for specified duration 35 + sleep "$DURATION" 36 + 37 + # Check if process is still running (good sign) 38 + if kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then 39 + echo "App started successfully and ran for ${DURATION}s" 40 + 41 + # Check log for common errors 42 + if grep -qi "error\|exception\|fatal\|cannot find" "$LOGFILE" 2>/dev/null; then 43 + echo "" 44 + echo "Warnings found in log:" 45 + grep -i "error\|exception\|fatal\|cannot find" "$LOGFILE" | head -20 46 + echo "" 47 + fi 48 + 49 + # Show key startup messages 50 + echo "" 51 + echo "Key startup messages:" 52 + grep -E "^(PROFILE|onReady|PREFS|\[ext)" "$LOGFILE" | head -20 53 + echo "" 54 + 55 + cleanup 56 + rm -f "$LOGFILE" 57 + echo "Smoke test PASSED" 58 + exit 0 59 + else 60 + echo "App crashed or exited early!" 61 + echo "" 62 + echo "Last 50 lines of log:" 63 + tail -50 "$LOGFILE" 64 + rm -f "$LOGFILE" 65 + echo "" 66 + echo "Smoke test FAILED" 67 + exit 1 68 + fi