experiments in a post-browser web
10
fork

Configure Feed

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

fix(extension): map visit sources to typed/bookmark/link enum, use _sync metadata

+89 -23
+25 -3
backend/extension/bookmarks.js
··· 6 6 * Behind a test feature toggle (peek_bookmarks_enabled). 7 7 */ 8 8 9 - import { addItem, queryItems, getOrCreateTag, tagItem, getItemsByTag } from './datastore.js'; 9 + import { addItem, queryItems, getOrCreateTag, tagItem, getItemsByTag, recordItemVisit } from './datastore.js'; 10 + import { getDeviceId } from './environment.js'; 10 11 11 12 const CONFIG_KEY = 'peek_bookmarks_enabled'; 12 13 const BOOKMARK_TAG = 'from:bookmark'; ··· 75 76 if (tagResult.success) { 76 77 await tagItem(existing.id, tagResult.data.tag.id); 77 78 } 79 + // Record a bookmark visit for frecency 80 + await recordItemVisit(existing.id, { 81 + timestamp: dateAdded || Date.now(), 82 + source: 'bookmark', 83 + }); 78 84 return false; 79 85 } 80 86 87 + const deviceId = await getDeviceId(); 88 + const now = Date.now(); 81 89 const result = await addItem('url', { 82 90 content: url, 83 - metadata: { importSource: 'bookmark', title, dateAdded }, 91 + metadata: { 92 + _sync: { 93 + createdBy: deviceId, 94 + createdAt: dateAdded || now, 95 + modifiedBy: deviceId, 96 + modifiedAt: now, 97 + }, 98 + title, 99 + dateAdded, 100 + }, 84 101 createdAt: dateAdded || undefined, 85 102 }); 86 103 ··· 89 106 if (tagResult.success) { 90 107 await tagItem(result.data.id, tagResult.data.tag.id); 91 108 } 109 + // Record a bookmark visit for frecency 110 + await recordItemVisit(result.data.id, { 111 + timestamp: dateAdded || now, 112 + source: 'bookmark', 113 + }); 92 114 existingUrlMap.set(url, { id: result.data.id, content: url }); 93 115 } 94 116 ··· 121 143 /** 122 144 * Returns { browserBookmarks, imported, synced }. 123 145 * browserBookmarks = total URLs in the bookmark tree 124 - * imported = Peek items with metadata.importSource 'bookmark' 146 + * imported = Peek items tagged with from:bookmark 125 147 * synced = subset of imported that have been synced (syncedAt > 0) 126 148 */ 127 149 export async function getBookmarkStats() {
+39 -17
backend/extension/history.js
··· 9 9 */ 10 10 11 11 import { addItem, queryItems, getOrCreateTag, tagItem, updateItem, getItemsByTag, recordItemVisit } from './datastore.js'; 12 + import { getDeviceId } from './environment.js'; 12 13 13 14 const CONFIG_KEY = 'peek_history_enabled'; 14 15 const HISTORY_TAG = 'from:history'; ··· 63 64 64 65 // ==================== Visit Metadata ==================== 65 66 66 - function buildHistoryMetadata(historyItem, visits) { 67 + async function buildHistoryMetadata(historyItem, visits) { 68 + const deviceId = await getDeviceId(); 69 + const now = Date.now(); 67 70 return { 68 - importSource: 'history', 71 + _sync: { 72 + createdBy: deviceId, 73 + createdAt: historyItem.lastVisitTime || now, 74 + modifiedBy: deviceId, 75 + modifiedAt: now, 76 + }, 69 77 title: historyItem.title || '', 70 78 lastVisitTime: historyItem.lastVisitTime || 0, 71 79 visitCount: historyItem.visitCount || 0, ··· 90 98 async function addOrUpdateHistoryItem(url, historyItem, visits, existingUrlMap) { 91 99 if (isInternalUrl(url)) return 'skipped'; 92 100 93 - const metadata = buildHistoryMetadata(historyItem, visits); 101 + const metadata = await buildHistoryMetadata(historyItem, visits); 94 102 const existing = existingUrlMap.get(url); 95 103 let itemId; 96 104 ··· 103 111 } 104 112 } 105 113 114 + // Use earliest visit time for _sync.createdAt to preserve original history date 115 + if (earliestVisitTime > 0) { 116 + metadata._sync.createdAt = earliestVisitTime; 117 + } 118 + 106 119 if (existing) { 107 120 itemId = existing.id; 108 121 // Update existing item with fresh metadata ··· 150 163 } 151 164 152 165 /** 153 - * Map Chrome's transition types to our source types 166 + * Map browser visit types to our source enum. 167 + * 168 + * Chrome uses string transition types. 169 + * Firefox uses numeric visitType values: 170 + * 1 = TRANSITION_LINK, 2 = TRANSITION_TYPED, 3 = TRANSITION_BOOKMARK, 171 + * 4 = TRANSITION_EMBED, 5 = TRANSITION_REDIRECT_PERMANENT, 172 + * 6 = TRANSITION_REDIRECT_TEMPORARY, 7 = TRANSITION_DOWNLOAD, 173 + * 8 = TRANSITION_FRAMED_LINK 154 174 */ 155 175 function mapTransitionToSource(transition) { 176 + // Firefox numeric visit types 177 + if (typeof transition === 'number') { 178 + switch (transition) { 179 + case 1: return 'link'; // TRANSITION_LINK 180 + case 2: return 'typed'; // TRANSITION_TYPED 181 + case 3: return 'bookmark'; // TRANSITION_BOOKMARK 182 + default: return 'direct'; 183 + } 184 + } 185 + 186 + // Chrome string transition types 156 187 switch (transition) { 157 188 case 'link': 158 189 return 'link'; 159 190 case 'typed': 160 - return 'direct'; 191 + return 'typed'; 161 192 case 'auto_bookmark': 193 + case 'keyword': 162 194 return 'bookmark'; 163 - case 'auto_subframe': 164 - case 'manual_subframe': 165 - return 'frame'; 166 - case 'generated': 167 - case 'auto_toplevel': 168 - return 'generated'; 169 - case 'form_submit': 170 - return 'form'; 171 - case 'reload': 172 - return 'reload'; 173 195 default: 174 - return 'other'; 196 + return 'direct'; 175 197 } 176 198 } 177 199 ··· 212 234 /** 213 235 * Returns { historyItems, imported, synced }. 214 236 * historyItems = count from chrome.history.search() excluding internal URLs 215 - * imported = Peek items with metadata.importSource 'history' 237 + * imported = Peek items tagged with from:history 216 238 * synced = subset of imported that have been synced (syncedAt > 0) 217 239 */ 218 240 export async function getHistoryStats() {
+25 -3
backend/extension/tabs.js
··· 6 6 * Behind a test feature toggle (peek_tabs_enabled). 7 7 */ 8 8 9 - import { addItem, queryItems, getOrCreateTag, tagItem, getItemsByTag } from './datastore.js'; 9 + import { addItem, queryItems, getOrCreateTag, tagItem, getItemsByTag, recordItemVisit } from './datastore.js'; 10 + import { getDeviceId } from './environment.js'; 10 11 11 12 const CONFIG_KEY = 'peek_tabs_enabled'; 12 13 const TAB_TAG = 'from:tab'; ··· 113 114 if (groupTagResult.success) { 114 115 await tagItem(existing.id, groupTagResult.data.tag.id); 115 116 } 117 + // Record a tab visit for frecency 118 + await recordItemVisit(existing.id, { 119 + timestamp: Date.now(), 120 + source: 'direct', 121 + }); 116 122 return false; 117 123 } 118 124 ··· 132 138 133 139 const metadata = buildTabMetadata(tab, groupName !== 'unfiled' ? groupName : undefined, groupColor); 134 140 141 + const deviceId = await getDeviceId(); 142 + const now = Date.now(); 135 143 const result = await addItem('url', { 136 144 content: url, 137 - metadata: { ...metadata, importSource: 'tab' }, 145 + metadata: { 146 + ...metadata, 147 + _sync: { 148 + createdBy: deviceId, 149 + createdAt: now, 150 + modifiedBy: deviceId, 151 + modifiedAt: now, 152 + }, 153 + }, 138 154 }); 139 155 140 156 if (result.success) { ··· 150 166 if (groupTagResult.success) { 151 167 await tagItem(result.data.id, groupTagResult.data.tag.id); 152 168 } 169 + 170 + // Record a tab visit for frecency 171 + await recordItemVisit(result.data.id, { 172 + timestamp: now, 173 + source: 'direct', 174 + }); 153 175 154 176 existingUrlMap.set(url, { id: result.data.id, content: url }); 155 177 } ··· 188 210 /** 189 211 * Returns { openTabs, imported, synced }. 190 212 * openTabs = count from chrome.tabs.query (excluding internal URLs) 191 - * imported = Peek items with metadata.importSource 'tab' 213 + * imported = Peek items tagged with from:tab 192 214 * synced = subset of imported that have been synced (syncedAt > 0) 193 215 */ 194 216 export async function getTabStats() {