this repo has no description
0
fork

Configure Feed

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

Implement show emojis feature with clean data separation

- Updated ServiceConfig interface with separate emoji/name fields
- Modified buildDestinations() to accept showEmojis parameter
- Created shared options utility with caching
- Updated popup to load options and pass to buildDestinations
- Added comprehensive tests for emoji enabled/disabled scenarios
- All existing tests continue to pass (60 total)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

TKTK 5e7cb9cf bd6a8328

+135 -38
+49 -20
CLAUDE.md
··· 311 311 312 312 No other code changes should be required! 313 313 314 - ## Options Page Implementation Plan 314 + ## ✅ Options Page Implementation - COMPLETED 315 + 316 + ### Status: COMPLETED ✅ 317 + 318 + Basic options page infrastructure has been successfully implemented with a single "show emojis" checkbox setting. 319 + 320 + **Completed work:** 321 + 322 + - ✅ `src/options/options.html` - Basic options page structure with checkbox 323 + - ✅ `src/options/options.css` - Clean, extension-appropriate styling 324 + - ✅ `src/options/options.ts` - Checkbox logic and storage integration 325 + - ✅ `public/manifest.json` - Added `options_ui` configuration 326 + - ✅ All validation commands pass (lint, typecheck, tests, build) 327 + 328 + **Implementation details:** 329 + 330 + - Uses `chrome.storage.sync` for cross-device synchronization 331 + - Storage key: `showEmojis` (boolean, defaults to true) 332 + - Follows existing storage patterns from cache system 333 + - Chrome/Firefox compatible 334 + - Proper TypeScript types and error handling 335 + 336 + **Next step:** Implement the actual "show emojis" feature functionality. 315 337 316 - ### Goal 338 + ## ✅ Show Emojis Feature Implementation - COMPLETED 317 339 318 - Add a simple options page with a single "show emojis" checkbox setting. This is foundational work for future UI preferences. 340 + ### Feature Status: COMPLETED ✅ 319 341 320 - ### Implementation Strategy 342 + The "show emojis" feature has been successfully implemented with clean data separation and proper integration. 321 343 322 - **Files to create:** 344 + **Completed work:** 323 345 324 - - `src/options/options.html` - Basic options page structure 325 - - `src/options/options.css` - Clean, extension-appropriate styling 326 - - `src/options/options.ts` - Checkbox logic and storage integration 346 + - ✅ **Updated ServiceConfig interface** - Added separate `emoji` and `name` fields 347 + - ✅ **Updated all service configurations** - Split labels into emoji + name parts 348 + - ✅ **Modified buildDestinations()** - Added optional `showEmojis` parameter (defaults to true) 349 + - ✅ **Created shared options utility** (`src/shared/options.ts`) - Centralized options loading with caching 350 + - ✅ **Updated popup integration** - Loads options and passes `showEmojis` to buildDestinations 351 + - ✅ **Added comprehensive tests** - Tests for both emoji enabled/disabled scenarios 352 + - ✅ **All validation commands pass** - Lint, typecheck, tests, and build all successful 327 353 328 - **Manifest changes:** 354 + **Implementation details:** 329 355 330 - - Add `options_ui` configuration pointing to options page 331 - - Leverage existing `storage` permission for settings persistence 356 + - **Clean data structure**: Each service now has separate `emoji` and `name` fields 357 + - **Backward compatibility**: `buildDestinations()` defaults to showing emojis (existing behavior) 358 + - **Efficient options loading**: Options are cached to avoid repeated storage calls 359 + - **Comprehensive testing**: Added tests to verify emoji display behavior 360 + - **Type safety**: Full TypeScript support with proper interfaces 332 361 333 - **Storage approach:** 362 + **How it works:** 334 363 335 - - Use `chrome.storage.sync` for cross-device synchronization 336 - - Storage key: `showEmojis` (boolean, defaults to true) 337 - - Follow existing storage patterns from cache system 364 + 1. **Options page**: User toggles "show emojis" checkbox → setting saved to `chrome.storage.sync` 365 + 2. **Popup initialization**: Loads options via `loadOptions()` utility 366 + 3. **Destination building**: Passes `showEmojis` setting to `buildDestinations()` 367 + 4. **Label generation**: `${showEmojis ? service.emoji : ''} ${service.name}` 338 368 339 - **Design principles:** 369 + **Testing results:** 340 370 341 - - Minimal, focused implementation 342 - - Chrome/Firefox compatibility 343 - - Follow extension UI best practices 344 - - No feature implementation yet - just the settings infrastructure 371 + - All 60 existing tests continue to pass 372 + - New tests verify emoji functionality works correctly 373 + - Both enabled (🦋 bsky.app) and disabled (bsky.app) scenarios tested
+6 -4
src/popup/popup.ts
··· 1 1 import { parseInput } from '../shared/parser'; 2 2 import { buildDestinations } from '../shared/services'; 3 + import { loadOptions } from '../shared/options'; 3 4 import Debug from '../shared/debug'; 4 5 import type { TransformInfo, BrowserWithTheme, DebugConfig, Destination, WindowWithDebug } from '../shared/types'; 5 6 ··· 129 130 */ 130 131 document.addEventListener('DOMContentLoaded', () => { 131 132 void (async () => { 132 - // Load debug configuration 133 + // Load debug configuration and options 133 134 await Debug.loadRuntimeConfig(); 135 + const options = await loadOptions(); 134 136 Debug.popup('Popup initialized'); 135 137 136 138 // Apply Firefox theme if available ··· 187 189 return; 188 190 } 189 191 190 - let ds = buildDestinations(info); 192 + let ds = buildDestinations(info, options.showEmojis); 191 193 render(ds); 192 194 193 195 if (info.did && !info.handle) { ··· 219 221 // After attempting to get handle from cache or by fetching: 220 222 if (handleToUse) { 221 223 info.handle = handleToUse; 222 - ds = buildDestinations(info); // Re-build destinations with the handle 224 + ds = buildDestinations(info, options.showEmojis); // Re-build destinations with the handle 223 225 render(ds); // Re-render the list 224 226 } else { 225 227 // Handle was not obtained. An error status might have already been set. ··· 256 258 } 257 259 if (didToUse) { 258 260 info.did = didToUse; 259 - ds = buildDestinations(info); 261 + ds = buildDestinations(info, options.showEmojis); 260 262 render(ds); 261 263 } else if (!ds.length && !errorStatusWasSet) { 262 264 showStatus('No actions available');
+41
src/shared/options.ts
··· 1 + interface OptionsData { 2 + showEmojis: boolean; 3 + } 4 + 5 + const DEFAULT_OPTIONS: OptionsData = { 6 + showEmojis: true, 7 + }; 8 + 9 + const STORAGE_KEY = 'wormhole-options'; 10 + 11 + let cachedOptions: OptionsData | null = null; 12 + 13 + export async function loadOptions(): Promise<OptionsData> { 14 + if (cachedOptions) { 15 + return cachedOptions; 16 + } 17 + 18 + try { 19 + const result = await chrome.storage.sync.get(STORAGE_KEY); 20 + const data: unknown = result[STORAGE_KEY]; 21 + 22 + if (data && typeof data === 'object') { 23 + const options = data as Record<string, unknown>; 24 + cachedOptions = { 25 + showEmojis: typeof options.showEmojis === 'boolean' ? options.showEmojis : DEFAULT_OPTIONS.showEmojis, 26 + }; 27 + } else { 28 + cachedOptions = DEFAULT_OPTIONS; 29 + } 30 + 31 + return cachedOptions; 32 + } catch (error: unknown) { 33 + console.warn('Failed to load options:', error); 34 + cachedOptions = DEFAULT_OPTIONS; 35 + return cachedOptions; 36 + } 37 + } 38 + 39 + export function clearOptionsCache(): void { 40 + cachedOptions = null; 41 + }
+27 -14
src/shared/services.ts
··· 1 1 import type { TransformInfo } from './types'; 2 2 3 3 export interface ServiceConfig { 4 - label: string; 4 + emoji: string; 5 + name: string; 5 6 6 7 // Input parsing configuration 7 8 parsing?: { ··· 35 36 36 37 export const SERVICES: Record<string, ServiceConfig> = { 37 38 DEER_SOCIAL: { 38 - label: '🦌 deer.social', 39 + emoji: '🦌', 40 + name: 'deer.social', 39 41 parsing: { 40 42 hostname: 'deer.social', 41 43 patterns: { ··· 47 49 }, 48 50 49 51 BSKY_APP: { 50 - label: '🦋 bsky.app', 52 + emoji: '🦋', 53 + name: 'bsky.app', 51 54 parsing: { 52 55 hostname: 'bsky.app', 53 56 patterns: { ··· 59 62 }, 60 63 61 64 PDSLS_DEV: { 62 - label: '⚙️ pdsls.dev', 65 + emoji: '⚙️', 66 + name: 'pdsls.dev', 63 67 parsing: { 64 68 hostname: 'pdsls.dev', 65 69 patterns: { ··· 74 78 }, 75 79 76 80 ATP_TOOLS: { 77 - label: '🛠️ atp.tools', 81 + emoji: '🛠️', 82 + name: 'atp.tools', 78 83 parsing: { 79 84 hostname: 'atp.tools', 80 85 patterns: { ··· 93 98 }, 94 99 95 100 CLEARSKY: { 96 - label: '☀️ clearsky', 101 + emoji: '☀️', 102 + name: 'clearsky', 97 103 parsing: { 98 104 hostname: 'clearsky.app', 99 105 patterns: { ··· 105 111 }, 106 112 107 113 SKYTHREAD: { 108 - label: '☁️ skythread', 114 + emoji: '☁️', 115 + name: 'skythread', 109 116 parsing: { 110 117 hostname: 'blue.mackuba.eu', 111 118 patterns: { ··· 127 134 }, 128 135 129 136 CRED_BLUE: { 130 - label: '🍥 cred.blue', 137 + emoji: '🍥', 138 + name: 'cred.blue', 131 139 parsing: { 132 140 hostname: 'cred.blue', 133 141 patterns: { ··· 140 148 }, 141 149 142 150 TANGLED_SH: { 143 - label: '🪢 tangled.sh', 151 + emoji: '🪢', 152 + name: 'tangled.sh', 144 153 parsing: { 145 154 hostname: 'tangled.sh', 146 155 patterns: { ··· 153 162 }, 154 163 155 164 FRONTPAGE_FYI: { 156 - label: '📰 frontpage.fyi', 165 + emoji: '📰', 166 + name: 'frontpage.fyi', 157 167 parsing: { 158 168 hostname: 'frontpage.fyi', 159 169 patterns: { ··· 166 176 }, 167 177 168 178 BOAT_KELINCI: { 169 - label: '⛵ boat.kelinci', 179 + emoji: '⛵', 180 + name: 'boat.kelinci', 170 181 parsing: { 171 182 hostname: 'boat.kelinci.net', 172 183 patterns: { ··· 179 190 }, 180 191 181 192 PLC_DIRECTORY: { 182 - label: '🪪 plc.directory', 193 + emoji: '🪪', 194 + name: 'plc.directory', 183 195 parsing: { 184 196 hostname: 'plc.directory', 185 197 patterns: { ··· 260 272 /** 261 273 * Builds a list of destination link objects from canonical info using service configuration. 262 274 */ 263 - export function buildDestinations(info: TransformInfo): { label: string; url: string }[] { 275 + export function buildDestinations(info: TransformInfo, showEmojis = true): { label: string; url: string }[] { 264 276 const isDidWeb = info.did?.startsWith('did:web:') ?? false; 265 277 const destinations: { label: string; url: string }[] = []; 266 278 ··· 274 286 275 287 const url = service.buildUrl(info); 276 288 if (url) { 277 - destinations.push({ label: service.label, url }); 289 + const label = showEmojis ? `${service.emoji} ${service.name}` : service.name; 290 + destinations.push({ label, url }); 278 291 } 279 292 } 280 293
+12
tests/transform.test.ts
··· 321 321 ); 322 322 expect(plcUrls).toHaveLength(0); 323 323 }); 324 + 325 + test('should include emojis in labels when showEmojis is true (default)', () => { 326 + const destinations = buildDestinations(realPostInfo); 327 + const bskyDestination = destinations.find((dest) => dest.url.includes('bsky.app')); 328 + expect(bskyDestination?.label).toBe('🦋 bsky.app'); 329 + }); 330 + 331 + test('should exclude emojis in labels when showEmojis is false', () => { 332 + const destinations = buildDestinations(realPostInfo, false); 333 + const bskyDestination = destinations.find((dest) => dest.url.includes('bsky.app')); 334 + expect(bskyDestination?.label).toBe('bsky.app'); 335 + }); 324 336 });