Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,14 @@
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
// Note: 'wasm-unsafe-eval' is required for Pubky SDK WebAssembly support
},
"web_accessible_resources": [
{
"resources": ["src/profile/profile-renderer.html"],
"matches": ["<all_urls>"]
// Note: Profile renderer needs to be accessible from any page to display
// pubky:// URLs. This is a core feature requirement.
}
Comment on lines 78 to 88

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Manifest JSON no longer parses with added comments

Manifest v3 files must be valid JSON, but manifest.json now includes // comments in the CSP and web_accessible_resources sections. Chrome rejects manifests containing comments, so the extension will fail to load in any environment until the file is valid JSON again.

Useful? React with 👍 / 👎.

]
}
Expand Down
66 changes: 46 additions & 20 deletions src/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,42 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse({ success: true });
}

if (message.type === 'OPEN_SIDE_PANEL_FOR_ANNOTATION') {
// Open sidepanel in response to annotation click (user gesture preserved)
// Must call sidePanel.open() immediately to preserve user gesture context
const tabId = sender.tab?.id;
if (tabId) {
chrome.sidePanel.open({ tabId }, () => {
if (chrome.runtime.lastError) {
logger.error('Background', 'Failed to open sidepanel', new Error(chrome.runtime.lastError.message));
sendResponse({ success: false, error: chrome.runtime.lastError.message });
} else {
logger.info('Background', 'Sidepanel opened for annotation', {
annotationId: message.annotationId,
tabId
});

// Send scroll message after a short delay to allow sidepanel to load
setTimeout(() => {
chrome.runtime.sendMessage({
type: 'SCROLL_TO_ANNOTATION',
annotationId: message.annotationId,
}).catch(() => {
// Sidepanel might not be ready yet, that's okay
logger.debug('Background', 'Sidepanel not ready for scroll message yet');
});
}, 300);

sendResponse({ success: true });
}
});
} else {
logger.warn('Background', 'No tab ID available for opening sidepanel');
sendResponse({ success: false, error: 'No tab ID available' });
}
return true; // Keep message channel open for async response
}

if (message.type === MESSAGE_TYPES.CREATE_ANNOTATION) {
// Handle annotation creation
handleCreateAnnotation(message.annotation)
Expand Down Expand Up @@ -643,54 +679,47 @@ chrome.webNavigation.onBeforeNavigate.addListener((details) => {

// Handle keyboard commands
// NOTE: Must NOT use async/await here to preserve user gesture context
console.log('[Graphiti] Registering keyboard command listener');
logger.info('Background', 'Registering keyboard command listener');
chrome.commands.onCommand.addListener((command) => {
// Use console.log directly for immediate visibility in service worker console
console.log('[Graphiti] Command received:', command);
logger.info('Background', 'Command received', { command });

if (command === COMMAND_NAMES.TOGGLE_SIDEPANEL) {
console.log('[Graphiti] toggle-sidepanel command triggered');
logger.info('Background', 'toggle-sidepanel command triggered');
// Open the side panel - must call open() immediately to preserve user gesture
// Using windowId instead of tabId since it's available synchronously
chrome.windows.getCurrent((window) => {
if (!window?.id) {
console.warn('[Graphiti] No current window found');
logger.warn('Background', 'No current window found for side panel toggle');
return;
}

console.log('[Graphiti] Opening side panel for window:', window.id);
logger.info('Background', 'Opening side panel', { windowId: window.id });
chrome.sidePanel.open({ windowId: window.id }, () => {
if (chrome.runtime.lastError) {
console.error('[Graphiti] Failed to open:', chrome.runtime.lastError.message);
logger.error('Background', 'Failed to open side panel', new Error(chrome.runtime.lastError.message));
} else {
console.log('[Graphiti] Side panel opened successfully');
logger.info('Background', 'Side panel opened via keyboard shortcut', { windowId: window.id });
}
});
});
}

if (command === COMMAND_NAMES.OPEN_ANNOTATIONS) {
console.log('[Graphiti] open-annotations command triggered');
logger.info('Background', 'open-annotations command triggered');
// Open side panel and switch to annotations tab
// Must call open() immediately to preserve user gesture
chrome.windows.getCurrent((window) => {
if (!window?.id) {
console.warn('[Graphiti] No current window found');
logger.warn('Background', 'No current window found for annotations');
return;
}

console.log('[Graphiti] Opening side panel for annotations, window:', window.id);
logger.info('Background', 'Opening side panel for annotations', { windowId: window.id });
chrome.sidePanel.open({ windowId: window.id }, () => {
if (chrome.runtime.lastError) {
console.error('[Graphiti] Failed to open:', chrome.runtime.lastError.message);
logger.error('Background', 'Failed to open annotations', new Error(chrome.runtime.lastError.message));
} else {
console.log('[Graphiti] Side panel opened, switching to annotations tab...');
logger.info('Background', 'Side panel opened, switching to annotations tab');
// Send message to sidebar to switch to annotations tab
setTimeout(() => {
chrome.runtime.sendMessage({
Expand All @@ -709,19 +738,18 @@ chrome.commands.onCommand.addListener((command) => {
}

if (command === COMMAND_NAMES.TOGGLE_DRAWING) {
console.log('[Graphiti] toggle-drawing command triggered');
logger.info('Background', 'toggle-drawing command triggered');
// Toggle drawing mode on the current tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
console.log('[Graphiti] Active tab for drawing:', tab?.id, tab?.url);
logger.info('Background', 'Active tab for drawing', { tabId: tab?.id, url: tab?.url });

if (tab?.id && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('about:') && !tab.url.startsWith('chrome-extension://')) {
console.log('[Graphiti] Sending TOGGLE_DRAWING_MODE to tab', tab.id);
logger.info('Background', 'Sending TOGGLE_DRAWING_MODE to tab', { tabId: tab.id });
chrome.tabs.sendMessage(tab.id, {
type: 'TOGGLE_DRAWING_MODE',
}, (response) => {
if (chrome.runtime.lastError) {
console.error('[Graphiti] Drawing mode error:', chrome.runtime.lastError.message);
logger.error('Background', 'Failed to toggle drawing mode - content script may not be ready', new Error(chrome.runtime.lastError.message));
// Try to notify user
chrome.notifications?.create({
Expand All @@ -731,15 +759,13 @@ chrome.commands.onCommand.addListener((command) => {
message: 'Please refresh the page to use drawing mode on this site.'
});
} else {
console.log('[Graphiti] Drawing mode toggled successfully:', response?.active);
logger.info('Background', 'Drawing mode toggled via keyboard shortcut', {
tabId: tab.id,
active: response?.active
});
}
});
} else {
console.warn('[Graphiti] Cannot use drawing mode on this page:', tab?.url);
logger.warn('Background', 'Cannot use drawing mode on this page', { url: tab?.url });
chrome.notifications?.create({
type: 'basic',
Expand All @@ -752,7 +778,7 @@ chrome.commands.onCommand.addListener((command) => {
}
});

console.log('[Graphiti] Background script command listeners registered');
logger.info('Background', 'Command listeners registered');

// Handle errors
self.addEventListener('error', (event) => {
Expand Down
3 changes: 2 additions & 1 deletion src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const DEFAULT_CONFIG: AppConfig = {
function loadConfig(): AppConfig {
// Vite exposes env variables via import.meta.env
// In Vite projects, import.meta.env is always available at build time
// @ts-ignore - import.meta is a Vite feature
// @ts-ignore - import.meta is a Vite feature (not in standard TypeScript lib)
// This is safe as Vite transforms this at build time
const viteEnv = (globalThis as any).import?.meta?.env ||
(typeof window !== 'undefined' && (window as any).__VITE_ENV__) ||
{};
Expand Down
4 changes: 3 additions & 1 deletion src/content/AnnotationManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { contentLogger as logger } from './logger';
// @ts-ignore - No type definitions available
// @ts-ignore - dom-anchor-text-quote lacks TypeScript definitions
// Tracking: https://github.com/nicksellen/dom-anchor-text-quote
// TODO: Create PR with type definitions or find alternative library
import * as textQuote from 'dom-anchor-text-quote';
import {
validateSelectedText,
Expand Down Expand Up @@ -379,7 +381,7 @@
cursor: not-allowed;
}
`;
document.head.appendChild(style);

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.

Check failure on line 384 in src/content/AnnotationManager.ts

View workflow job for this annotation

GitHub Actions / Test

Unhandled error

TypeError: Cannot read properties of undefined (reading 'appendChild') ❯ AnnotationManager.injectStyles src/content/AnnotationManager.ts:384:19 ❯ AnnotationManager.init src/content/AnnotationManager.ts:68:10 ❯ new AnnotationManager src/content/AnnotationManager.ts:61:10 ❯ src/content/__tests__/AnnotationManager.test.ts:120:25 This error originated in "src/content/__tests__/AnnotationManager.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should re-render highlights after content changes". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
}

private handleTextSelection(event: MouseEvent) {
Expand Down
23 changes: 21 additions & 2 deletions src/content/PubkyURLHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { contentLogger as logger } from './logger';
import DOMPurify from 'dompurify';

export class PubkyURLHandler {
private domObserver: MutationObserver | null = null;

constructor() {
this.init();
}
Expand Down Expand Up @@ -202,7 +204,12 @@ export class PubkyURLHandler {
private observeDOMForPubkyURLs() {
let isProcessing = false;

const observer = new MutationObserver((mutations) => {
// Disconnect existing observer if any
if (this.domObserver) {
this.domObserver.disconnect();
}

this.domObserver = new MutationObserver((mutations) => {
const isOurMutation = mutations.some(mutation => {
return Array.from(mutation.addedNodes).some(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
Expand Down Expand Up @@ -232,10 +239,22 @@ export class PubkyURLHandler {
}
});

observer.observe(document.body, {
this.domObserver.observe(document.body, {
childList: true,
subtree: true,
});
}

/**
* Cleanup method to disconnect observer and remove event listeners
* Should be called when the handler is no longer needed
*/
cleanup(): void {
if (this.domObserver) {
this.domObserver.disconnect();
this.domObserver = null;
}
document.removeEventListener('click', this.handleClick, true);
}
}

29 changes: 15 additions & 14 deletions src/offscreen/offscreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { storage } from '../utils/storage';
import { logger } from '../utils/logger';

// Import Pubky SDK types
type Client = any;
Expand Down Expand Up @@ -52,18 +53,18 @@ class OffscreenHandler {
*/
private async initialize(): Promise<void> {
try {
console.log('[Graphiti Offscreen] Initializing Pubky client...');
logger.info('Offscreen', 'Initializing Pubky client');

const { getPubkyClientAsync } = await import('../utils/pubky-client-factory');
this.client = await getPubkyClientAsync();
this.isInitialized = true;

console.log('[Graphiti Offscreen] Pubky client initialized successfully');
logger.info('Offscreen', 'Pubky client initialized successfully');

// Set up message listener
this.setupMessageListener();
} catch (error) {
console.error('[Graphiti Offscreen] Failed to initialize Pubky client:', error);
logger.error('Offscreen', 'Failed to initialize Pubky client', error as Error);
}
}

Expand All @@ -90,20 +91,20 @@ class OffscreenHandler {
return false;
}

console.log('[Graphiti Offscreen] Received message:', message.type);
logger.info('Offscreen', 'Received message', { type: message.type });

// Handle async operations
this.handleMessage(message)
.then(sendResponse)
.catch((error) => {
console.error('[Graphiti Offscreen] Error handling message:', error);
logger.error('Offscreen', 'Error handling message', error as Error);
sendResponse({ success: false, error: error.message });
});

return true; // Keep channel open for async response
});

console.log('[Graphiti Offscreen] Message listener registered');
logger.info('Offscreen', 'Message listener registered');
}

/**
Expand Down Expand Up @@ -201,11 +202,11 @@ class OffscreenHandler {
});
}

console.log('[Graphiti Offscreen] Annotation synced:', fullPath);
logger.info('Offscreen', 'Annotation synced', { fullPath });

return { success: true, data: { postUri: fullPath } };
} catch (error) {
console.error('[Graphiti Offscreen] Failed to sync annotation:', error);
logger.error('Offscreen', 'Failed to sync annotation', error as Error);
return { success: false, error: (error as Error).message };
}
}
Expand Down Expand Up @@ -259,11 +260,11 @@ class OffscreenHandler {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

console.log('[Graphiti Offscreen] Drawing synced:', fullPath);
logger.info('Offscreen', 'Drawing synced', { fullPath });

return { success: true, data: { pubkyUrl: fullPath } };
} catch (error) {
console.error('[Graphiti Offscreen] Failed to sync drawing:', error);
logger.error('Offscreen', 'Failed to sync drawing', error as Error);
return { success: false, error: (error as Error).message };
}
}
Expand Down Expand Up @@ -335,14 +336,14 @@ class OffscreenHandler {
}
}

console.log('[Graphiti Offscreen] Sync complete:', { annotationsSynced, drawingsSynced });
logger.info('Offscreen', 'Sync complete', { annotationsSynced, drawingsSynced });

return {
success: true,
data: { annotationsSynced, drawingsSynced }
};
} catch (error) {
console.error('[Graphiti Offscreen] Failed to sync all pending:', error);
logger.error('Offscreen', 'Failed to sync all pending', error as Error);
return { success: false, error: (error as Error).message };
}
}
Expand Down Expand Up @@ -385,7 +386,7 @@ class OffscreenHandler {
}
};
} catch (error) {
console.error('[Graphiti Offscreen] Failed to get sync status:', error);
logger.error('Offscreen', 'Failed to get sync status', error as Error);
return { success: false, error: (error as Error).message };
}
}
Expand All @@ -394,5 +395,5 @@ class OffscreenHandler {
// Initialize the offscreen handler
new OffscreenHandler();

console.log('[Graphiti Offscreen] Offscreen document loaded');
logger.info('Offscreen', 'Offscreen document loaded');

25 changes: 24 additions & 1 deletion src/sidepanel/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ function App() {
const [hasMorePosts, setHasMorePosts] = useState(true);
const [postsPage, setPostsPage] = useState(0);
// @ts-ignore - postsCursor is set for future cursor-based pagination
// This will be used when implementing cursor-based pagination for large feeds
const [postsCursor, setPostsCursor] = useState<string | undefined>(undefined);
const POSTS_PER_PAGE = 20;
const sentinelRef = useRef<HTMLDivElement>(null);
const [highlightedAnnotationId, setHighlightedAnnotationId] = useState<string | null>(null);

useEffect(() => {
initializePanel();
Expand Down Expand Up @@ -87,8 +89,9 @@ function App() {
// Listen for messages to scroll to annotations or switch tabs
const handleMessage = (message: any) => {
if (message.type === 'SCROLL_TO_ANNOTATION') {
logger.info('SidePanel', 'Scroll to annotation requested', { annotationId: message.annotationId });
setActiveTab('annotations');
// Scroll logic will be handled in the render
setHighlightedAnnotationId(message.annotationId);
}

if (message.type === 'SWITCH_TO_ANNOTATIONS') {
Expand All @@ -103,6 +106,26 @@ function App() {
};
}, []);

// Scroll to annotation when it's loaded and highlighted
useEffect(() => {
if (highlightedAnnotationId && annotations.length > 0) {
// Wait for DOM to update, then scroll
setTimeout(() => {
const annotationElement = document.querySelector(`[data-annotation-id="${highlightedAnnotationId}"]`);
if (annotationElement) {
annotationElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Add a highlight class for visual feedback
annotationElement.classList.add('ring-2', 'ring-[#667eea]', 'ring-offset-2', 'ring-offset-[#1F1F1F]');
// Remove highlight after a few seconds
setTimeout(() => {
annotationElement.classList.remove('ring-2', 'ring-[#667eea]', 'ring-offset-2', 'ring-offset-[#1F1F1F]');
setHighlightedAnnotationId(null);
}, 3000);
}
}, 100);
}
}, [highlightedAnnotationId, annotations]);

// Keyboard shortcut listener for Shift+?
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand Down
Loading
Loading