diff --git a/.claude/agents/har-fixer.md b/.claude/agents/har-fixer.md new file mode 100644 index 0000000..e752310 --- /dev/null +++ b/.claude/agents/har-fixer.md @@ -0,0 +1,38 @@ +--- +name: har-fixer +description: Use this agent when you need to fix or improve the detection logic for a specific Gitcasso snapshot by testing changes in the har:view development environment. Examples: Context: User has identified issues with comment spot detection in a specific snapshot and wants to test fixes. user: 'The comment detection is missing some spots in snapshot ABC123, can you help fix the enhancer logic?' assistant: 'I'll use the har-fixer agent to investigate and fix the detection issues in that snapshot.' Since the user wants to fix detection logic for a specific snapshot, use the har-fixer agent to run the har:view environment and test changes. Context: User wants to validate that recent changes to an enhancer are working correctly. user: 'I made some changes to the GitHub enhancer, can you test it against snapshot XYZ789?' assistant: 'Let me use the har-fixer agent to test your enhancer changes against that specific snapshot.' The user wants to test enhancer changes against a specific snapshot, so use the har-fixer agent to validate the changes in the har:view environment. +model: inherit +--- + +You are an expert Gitcasso snapshot debugging specialist with deep knowledge of browser extension development. You operate exclusively within the `browser-extension` directory and specialize in using the har:view development environment to diagnose and fix detection logic issues. + +Your primary workflow: + +1. **Environment Setup**: Always start by reading the documentation at the top of the `har-view.ts` file to understand the dev environment. + +2. **Launch Development Environment**: Execute `pnpm har:view` to bring up the har:view development environment. Ensure the environment starts successfully before proceeding. + +3. **Browser Navigation**: Use the Playwright MCP to interact with the development environment. Navigate to the specific Gitcasso snapshot that needs investigation or fixing. + +4. **Code Synchronization**: Always click the button with id `gitcasso-rebuild-btn` to ensure you're testing against the latest code changes. Wait for the rebuild to complete before analyzing results. + +5. **Detection Analysis**: Examine the detected spots in the `gitcasso-comment-spots` element. Analyze what spots are being detected, what might be missing, and identify patterns in the detection logic that need improvement. + +6. **Enhancer Modification**: Based on your analysis, make targeted changes to the specific enhancer's detection logic. Focus on: + - Improving selector accuracy + - Handling edge cases in the DOM structure + - Optimizing detection algorithms for the specific site pattern + - Ensuring compatibility with dynamic content loading + +7. **Iterative Testing**: After making changes, rebuild and test again to validate improvements. Continue this cycle until the detection logic works correctly for the target snapshot. + +8. **Documentation**: Clearly explain what issues you found, what changes you made, and why those changes improve the detection logic. + +Key principles: +- Always work incrementally - make small, targeted changes and test frequently +- Focus on the specific snapshot mentioned by the user unless told otherwise +- Pay attention to browser console errors and network issues that might affect detection +- Consider how your changes might impact other sites or snapshots +- Be methodical in your debugging approach - document what you try and what results you observe + +You have expertise in CSS selectors, DOM manipulation, JavaScript debugging, and understanding how different websites structure their comment systems. Use this knowledge to create robust, reliable detection logic that works across various edge cases. diff --git a/.claude/commands/finish-wc.md b/.claude/commands/finish-wc.md new file mode 100644 index 0000000..b8021a7 --- /dev/null +++ b/.claude/commands/finish-wc.md @@ -0,0 +1 @@ +Run `git status` to see the changes in the working copy. Complete whatever tasks are necessary to complete this change. Make sure that `pnpm -r precommit` succeeds. Don't fix `precommit` just be reverting the changes, the goal is to complete the change. \ No newline at end of file diff --git a/browser-extension/src/entrypoints/content.ts b/browser-extension/src/entrypoints/content.ts index 2093239..b49267f 100644 --- a/browser-extension/src/entrypoints/content.ts +++ b/browser-extension/src/entrypoints/content.ts @@ -6,6 +6,9 @@ import { EnhancerRegistry, TextareaRegistry } from '../lib/registries' const enhancers = new EnhancerRegistry() const enhancedTextareas = new TextareaRegistry() +// Expose for debugging in har:view +;(window as any).gitcassoTextareaRegistry = enhancedTextareas + function sendEventToBackground(type: 'ENHANCED' | 'DESTROYED', spot: CommentSpot): void { const message: CommentEvent = { spot, @@ -85,16 +88,20 @@ function enhanceMaybe(textarea: HTMLTextAreaElement) { logger.debug('activating textarea {}', textarea) injectStyles() - const enhancedTextarea = enhancers.tryToEnhance(textarea) - if (enhancedTextarea) { - logger.debug( - 'Identified textarea:', - enhancedTextarea.spot.type, - enhancedTextarea.spot.unique_key, - ) - enhancedTextareas.register(enhancedTextarea) - } else { - logger.debug('No handler found for textarea') + try { + const enhancedTextarea = enhancers.tryToEnhance(textarea) + if (enhancedTextarea) { + logger.debug( + 'Identified textarea:', + enhancedTextarea.spot.type, + enhancedTextarea.spot.unique_key, + ) + enhancedTextareas.register(enhancedTextarea) + } else { + logger.debug('No handler found for textarea') + } + } catch (e) { + logger.error(e) } } diff --git a/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx b/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx index b6a4f57..e3b195d 100644 --- a/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx +++ b/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx @@ -20,7 +20,10 @@ export class GitHubIssueAddCommentEnhancer implements CommentEnhancer // Patch window.location before loading content script console.log('Patching window.location to simulate original URL...'); @@ -333,27 +336,18 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { // Replace the problematic webextension-polyfill error check const patchedCode = code.replace( - /throw new Error\\("This script should only be loaded in a browser extension\\."/g, - 'console.warn("Webextension-polyfill check bypassed for HAR testing"' + 'throw new Error("This script should only be loaded in a browser extension.")', + 'console.warn("Webextension-polyfill check bypassed for HAR testing")' ); - - // Mock necessary APIs before executing - window.chrome = window.chrome || { - runtime: { - getURL: (path) => 'chrome-extension://gitcasso-test/' + path, - onMessage: { addListener: () => {} }, - sendMessage: () => Promise.resolve(), - id: 'gitcasso-test' - } - }; - window.browser = window.chrome; - - // Execute the patched script + + // Execute the patched script with browser API mocks prepended + const browserMocks = 'window.chrome=window.chrome||{runtime:{getURL:path=>"chrome-extension://gitcasso-test/"+path,onMessage:{addListener:()=>{}},sendMessage:()=>Promise.resolve(),id:"gitcasso-test"}};window.browser=window.chrome;'; const script = document.createElement('script'); - script.textContent = patchedCode; + script.textContent = browserMocks + patchedCode; document.head.appendChild(script); - - console.log('Gitcasso content script loaded with location patching for:', '${urlParts.href}'); + console.log('Gitcasso content script loaded with location patching for:', '` + + urlParts.href + + `'); }) .catch(error => { console.error('Failed to load and patch content script:', error); @@ -442,6 +436,88 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { }); document.body.appendChild(rebuildButton); + + // Create CommentSpot display + const commentSpotDisplay = document.createElement('div'); + commentSpotDisplay.id = 'gitcasso-comment-spots'; + commentSpotDisplay.style.cssText = + 'position: fixed;' + + 'top: 80px;' + + 'right: 20px;' + + 'width: 300px;' + + 'max-height: 400px;' + + 'background: rgba(255, 255, 255, 0.95);' + + 'border: 1px solid #ddd;' + + 'border-radius: 8px;' + + 'padding: 15px;' + + 'font-family: Monaco, Menlo, Ubuntu Mono, monospace;' + + 'font-size: 11px;' + + 'line-height: 1.4;' + + 'overflow-y: auto;' + + 'z-index: 999998;' + + 'box-shadow: 0 4px 12px rgba(0,0,0,0.2);' + + 'backdrop-filter: blur(10px);'; + + // Simplified display formatting + const styles = { + header: 'font-weight: bold; margin-bottom: 8px; color: #333;', + spotContainer: 'margin-bottom: 12px; padding: 8px; border: 1px solid #eee; border-radius: 4px;', + spotTitle: 'font-weight: bold; color: #555;', + jsonPre: 'margin: 4px 0; font-size: 10px;', + textareaHeader: 'font-weight: bold; color: #007acc; margin-top: 8px;', + textareaPre: 'margin: 4px 0; font-size: 10px; color: #666;', + noInfo: 'color: #999; font-style: italic; margin-top: 4px;', + empty: 'color: #666; font-style: italic;' + }; + + function formatSpot(enhanced, index) { + const { textarea, spot } = enhanced; + const rect = textarea.getBoundingClientRect(); + const textareaInfo = { + id: textarea.id || '', + name: textarea.name || '', + className: textarea.className || '', + tagName: textarea.tagName, + placeholder: textarea.placeholder || '', + value: textarea.value ? textarea.value.substring(0, 50) + '...' : '', + parentElement: textarea.parentElement ? textarea.parentElement.tagName + (textarea.parentElement.className ? '.' + textarea.parentElement.className : '') : '', + position: { + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height + } + }; + + return '
' + + '
Spot ' + (index + 1) + ':
' + + '
' + JSON.stringify(spot, null, 2) + '
' + + '
Textarea Info:
' + + '
' + JSON.stringify(textareaInfo, null, 2) + '
' + + '
'; + } + + function updateCommentSpotDisplay() { + const enhanced = window.gitcassoTextareaRegistry ? window.gitcassoTextareaRegistry.getAllEnhanced() : []; + + console.log('Enhanced textareas:', enhanced.length); + console.log('All textareas on page:', document.querySelectorAll('textarea').length); + + const content = enhanced.length > 0 + ? '
CommentSpots (' + enhanced.length + '):
' + + enhanced.map(formatSpot).join('') + : '
No CommentSpots detected yet...
Textareas found: ' + document.querySelectorAll('textarea').length + '
'; + + commentSpotDisplay.innerHTML = content; + } + + // Initial update + setTimeout(updateCommentSpotDisplay, 100); + + // Update display periodically + setInterval(updateCommentSpotDisplay, 2000); + + document.body.appendChild(commentSpotDisplay); ` if (!html.includes('')) { diff --git a/browser-extension/tests/lib/enhancers/github.test.ts b/browser-extension/tests/lib/enhancers/github.test.ts index efdba52..587be2f 100644 --- a/browser-extension/tests/lib/enhancers/github.test.ts +++ b/browser-extension/tests/lib/enhancers/github.test.ts @@ -5,103 +5,134 @@ expect import { EnhancerRegistry } from '../../../src/lib/registries' +const enhancers = new EnhancerRegistry() +function enhancements(document: Document) { + const textareas = document.querySelectorAll('textarea') + const spotsFound = [] + for (const textarea of textareas) { + const enhanced = enhancers.tryToEnhance(textarea) + const forValue = `id=${textarea.id} name=${textarea.name} className=${textarea.className}` + if (enhanced) { + spotsFound.push({ + for: forValue, + spot: enhanced.spot, + title: enhanced.enhancer.tableTitle(enhanced.spot), + upperDecoration: enhanced.enhancer.tableUpperDecoration(enhanced.spot), + }) + } else { + spotsFound.push({ + for: forValue, + spot: 'NO_SPOT', + }) + } + } + return spotsFound +} + describe('github', () => { usingHar('gh_pr').it('should create the correct spot object', async () => { - const enhancers = new EnhancerRegistry() - const textareas = document.querySelectorAll('textarea') - expect(textareas.length).toBe(2) - expect(enhancers.tryToEnhance(textareas[0]!)).toBeNull() - const enhancedTextarea = enhancers.tryToEnhance(textareas[1]!) - expect(enhancedTextarea?.spot).toMatchInlineSnapshot(` - { - "domain": "github.com", - "number": 517, - "slug": "diffplug/selfie", - "title": "TODO_TITLE", - "type": "GH_PR_ADD_COMMENT", - "unique_key": "github.com:diffplug/selfie:517", - } - `) - expect( - enhancedTextarea?.enhancer.tableUpperDecoration(enhancedTextarea.spot), - ).toMatchInlineSnapshot(` - - - diffplug/selfie - - - PR # - 517 - - + expect(enhancements(document)).toMatchInlineSnapshot(` + [ + { + "for": "id=feedback name=feedback className=form-control width-full mb-2", + "spot": "NO_SPOT", + }, + { + "for": "id=new_comment_field name=comment[body] className=js-comment-field js-paste-markdown js-task-list-field js-quick-submit FormControl-textarea CommentBox-input js-size-to-fit size-to-fit js-session-resumable js-saved-reply-shortcut-comment-field overtype-input", + "spot": { + "domain": "github.com", + "number": 517, + "slug": "diffplug/selfie", + "title": "TODO_TITLE", + "type": "GH_PR_ADD_COMMENT", + "unique_key": "github.com:diffplug/selfie:517", + }, + "title": "TITLE_TODO", + "upperDecoration": + + diffplug/selfie + + + PR # + 517 + + , + }, + ] `) }) usingHar('gh_new_pr').it('should create the correct spot object', async () => { - const enhancers = new EnhancerRegistry() - const textareas = document.querySelectorAll('textarea') - expect(textareas.length).toBe(2) - expect(enhancers.tryToEnhance(textareas[0]!)?.spot).toMatchInlineSnapshot(` - { + expect(enhancements(document)).toMatchInlineSnapshot(` + [ + { + "for": "id=feedback name=feedback className=form-control width-full mb-2", + "spot": "NO_SPOT", + }, + { + "for": "id=pull_request_body name=pull_request[body] className=js-comment-field js-paste-markdown js-task-list-field js-quick-submit FormControl-textarea CommentBox-input js-size-to-fit size-to-fit js-session-resumable js-saved-reply-shortcut-comment-field CommentBox-input--large overtype-input", + "spot": { "domain": "github.com", "slug": "diffplug/selfie/main...cavia-porcellus:selfie:main", "type": "GH_PR_NEW_COMMENT", "unique_key": "github.com:diffplug/selfie/main...cavia-porcellus:selfie:main", - } - `) + }, + "title": "TITLE_TODO", + "upperDecoration": + + New PR + + + + diffplug/selfie/main...cavia-porcellus:selfie:main + + + , + }, + ] + `) }) usingHar('gh_issue').it('should create the correct spot object', async () => { - const enhancers = new EnhancerRegistry() - const textareas = document.querySelectorAll('textarea') - expect(textareas.length).toBe(1) - const enhancedTextarea = enhancers.tryToEnhance(textareas[0]!) - expect(enhancedTextarea?.spot).toMatchInlineSnapshot(` - { - "domain": "github.com", - "number": 523, - "slug": "diffplug/selfie", - "title": "TODO_TITLE", - "type": "GH_ISSUE_ADD_COMMENT", - "unique_key": "github.com:diffplug/selfie:523", - } - `) - // Test the tableRow method - expect( - enhancedTextarea?.enhancer.tableUpperDecoration(enhancedTextarea.spot), - ).toMatchInlineSnapshot(` - - - - - # - 523 - - diffplug/selfie - - + expect(enhancements(document)).toMatchInlineSnapshot(` + [ + { + "for": "id=feedback name=feedback className=form-control width-full mb-2", + "spot": "NO_SPOT", + }, + ] `) }) usingHar('gh_new_issue').it('should create the correct spot object', async () => { - const enhancers = new EnhancerRegistry() - const textareas = document.querySelectorAll('textarea') - expect(textareas.length).toBe(1) - expect(enhancers.tryToEnhance(textareas[0]!)?.spot).toMatchInlineSnapshot(` - { + expect(enhancements(document)).toMatchInlineSnapshot(` + [ + { + "for": "id=feedback name=feedback className=form-control width-full mb-2 overtype-input", + "spot": { "domain": "github.com", "slug": "diffplug/selfie", "type": "GH_ISSUE_NEW_COMMENT", "unique_key": "github.com:diffplug/selfie:new", - } - `) + }, + "title": "New Issue", + "upperDecoration": + + New Issue + + + + diffplug/selfie + + + , + }, + ] + `) }) }) diff --git a/browser-extension/tests/playground/claude.tsx b/browser-extension/tests/playground/claude.tsx index 8e8a15b..cabd026 100644 --- a/browser-extension/tests/playground/claude.tsx +++ b/browser-extension/tests/playground/claude.tsx @@ -1,6 +1,5 @@ import { Eye, EyeOff, Search, Settings, Trash2 } from 'lucide-react' import { useMemo, useState } from 'react' -import { twMerge } from 'tailwind-merge' import Badge from '@/components/Badge' import { badgeCVA } from '@/components/design' import MultiSegment from '@/components/MultiSegment' @@ -157,13 +156,11 @@ export const ClaudePrototype = () => {