From cf2d3dd2d68e364ebbcc18a8cb03ea9a7baf2204 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 16:48:36 -0700 Subject: [PATCH 01/11] Claude's first pass. --- browser-extension/README.md | 53 ++-- browser-extension/package.json | 4 +- browser-extension/src/lib/config.ts | 2 +- .../tests/{har-view.ts => corpus-view.ts} | 234 ++++++++++-------- .../tests/corpus/_corpus-index.ts | 34 +++ .../tests/{ => corpus}/har/gh_issue.har | 0 .../tests/{ => corpus}/har/gh_new_issue.har | 0 .../tests/{ => corpus}/har/gh_new_pr.har | 0 .../tests/{ => corpus}/har/gh_pr.har | 0 browser-extension/tests/har-record.ts | 56 +++-- browser-extension/tests/har/_har-index.ts | 7 - 11 files changed, 237 insertions(+), 153 deletions(-) rename browser-extension/tests/{har-view.ts => corpus-view.ts} (73%) create mode 100644 browser-extension/tests/corpus/_corpus-index.ts rename browser-extension/tests/{ => corpus}/har/gh_issue.har (100%) rename browser-extension/tests/{ => corpus}/har/gh_new_issue.har (100%) rename browser-extension/tests/{ => corpus}/har/gh_new_pr.har (100%) rename browser-extension/tests/{ => corpus}/har/gh_pr.har (100%) delete mode 100644 browser-extension/tests/har/_har-index.ts diff --git a/browser-extension/README.md b/browser-extension/README.md index 79275aa..d4cfe15 100644 --- a/browser-extension/README.md +++ b/browser-extension/README.md @@ -69,17 +69,42 @@ When the `textarea` gets removed from the page, the `TextareaRegistry` is notifi ## Testing - `npm run playground` gives you a test environment where you can tinker with the popup with various test data, supports hot reload -- `npm run har:view` gives you recordings of various web pages which you can see with and without enhancement by the browser extension - -### Recording new HAR files - -- the har recordings live in `tests/har`, they are complete recordings of the network requests of a single page load -- you can add or change URLs in `tests/har-index.ts` -- `npx playwright codegen https://github.com/login --save-storage=playwright/.auth/gh.json` will store new auth tokens - - login manually, then close the browser - - ***these cookies are very sensitive! we only run this script using a test account that has no permissions or memberships to anything, recommend you do the same!*** -- `pnpm run har:record` this records new snapshots using those auth tokens (it needs args, run it with no args for docs) - - DO NOT COMMIT AND PUSH NEW OR CHANGED `har` files! - - we try to sanitize these (see `har-record.ts` for details) but there may be important PII in them - - if you need new HAR files for something, let us know and we will generate them ourselves using a dummy account - - IF YOUR PR CHANGES OR ADDS HAR FILES WE WILL CLOSE IT. Ask for HAR files and we'll be happy to generate clean ones you can test against. +- `npm run corpus:view` gives you recordings of various web pages which you can see with and without enhancement by the browser extension + +### Test Corpus + +We maintain a corpus of test pages in two formats for testing the browser extension: + +#### HAR Corpus (Automated) + +- For testing initial page loads and network requests +- HAR recordings live in `tests/corpus/har/`, complete recordings of the network requests of a single page load +- You can add or change URLs in `tests/corpus/_corpus-index.ts` +- **Recording new HAR files:** + - `npx playwright codegen https://github.com/login --save-storage=playwright/.auth/gh.json` will store new auth tokens + - login manually, then close the browser + - ***these cookies are very sensitive! we only run this script using a test account that has no permissions or memberships to anything, recommend you do the same!*** + - `pnpm run corpus:record:har` records new HAR files using those auth tokens (it needs args, run it with no args for docs) + - DO NOT COMMIT AND PUSH NEW OR CHANGED HAR files! + - we try to sanitize these (see `har-record.ts` for details) but there may be important PII in them + - if you need new HAR files for something, let us know and we will generate them ourselves using a dummy account + - IF YOUR PR CHANGES OR ADDS HAR FILES WE WILL CLOSE IT. Ask for HAR files and we'll be happy to generate clean ones you can test against. + +#### HTML Corpus (Manual) + +- For testing post-interaction states (e.g., expanded textareas, modal dialogs, dynamic content) +- HTML snapshots live in `tests/corpus/html/`, manually captured using SingleFile browser extension +- All assets are inlined in a single HTML file by SingleFile +- **Creating new HTML corpus files:** + 1. Navigate to the desired page state (click buttons, expand textareas, etc.) + 2. Use SingleFile browser extension to save the complete page + 3. Save the `.html` file to `tests/corpus/html/` with a descriptive name + 4. Add an entry to `tests/corpus/_corpus-index.ts` with `type: 'html'` and a description of the captured state + +#### Viewing Corpus Files + +- Run `pnpm run corpus:view` to start the test server at http://localhost:3001 +- Select any corpus file to view in two modes: + - **Clean**: Original page without extension + - **Gitcasso**: Page with extension injected for testing +- Both HAR and HTML corpus types are supported diff --git a/browser-extension/package.json b/browser-extension/package.json index 9097cba..eb4fef4 100644 --- a/browser-extension/package.json +++ b/browser-extension/package.json @@ -62,8 +62,8 @@ "test": "vitest run", "playground": "vite --config vite.playground.config.ts", "playground:build": "vite build --config vite.playground.config.ts", - "har:record": "tsx tests/har-record.ts", - "har:view": "tsx tests/har-view.ts" + "corpus:record:har": "tsx tests/har-record.ts", + "corpus:view": "tsx tests/corpus-view.ts" }, "type": "module", "version": "0.0.1" diff --git a/browser-extension/src/lib/config.ts b/browser-extension/src/lib/config.ts index 73f1820..74b71b2 100644 --- a/browser-extension/src/lib/config.ts +++ b/browser-extension/src/lib/config.ts @@ -9,6 +9,6 @@ export type LogLevel = (typeof LOG_LEVELS)[number] export const CONFIG = { ADDED_OVERTYPE_CLASS: 'gitcasso-overtype', EXTENSION_NAME: 'gitcasso', // decorates logs - LOG_LEVEL: 'INFO' satisfies LogLevel, + LOG_LEVEL: 'DEBUG' satisfies LogLevel, MODE: 'PROD' satisfies ModeType, } as const diff --git a/browser-extension/tests/har-view.ts b/browser-extension/tests/corpus-view.ts similarity index 73% rename from browser-extension/tests/har-view.ts rename to browser-extension/tests/corpus-view.ts index 6586eba..add93c7 100644 --- a/browser-extension/tests/har-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -1,13 +1,14 @@ /** - * HAR Page Viewer Test Server + * Corpus Viewer Test Server * - * This Express server serves recorded HAR files as live web pages for testing. + * This Express server serves recorded corpus files (both HAR and HTML) as live web pages for testing. * It provides two viewing modes: 'clean' (original page) and 'gitcasso' (with extension injected). * * Key components: - * - Loads HAR files from ./har/ directory based on PAGES index in `./har/_har_index.ts` - * - Patches URLs in HTML to serve assets locally from HAR data - * - Handles asset serving by matching HAR entries to requested paths + * - Loads HAR files from ./corpus/har/ and HTML files from ./corpus/html/ based on CORPUS index in `./_corpus-index.ts` + * - For HAR: Patches URLs in HTML to serve assets locally from HAR data + * - For HTML: Serves SingleFile-captured HTML directly (assets already inlined) + * - Handles asset serving by matching HAR entries to requested paths (HAR corpus only) * * Development notes: * - Injects Gitcasso content script in 'gitcasso' mode with location patching @@ -26,7 +27,7 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import express from 'express' import type { Har } from 'har-format' -import { PAGES } from './har/_har-index' +import { CORPUS, type CorpusEntry } from './corpus/_corpus-index' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const app = express() @@ -36,11 +37,11 @@ const PORT = 3001 app.use(express.json()) // Store HAR json -const harCache = new Map() +const harCache = new Map() // Extract URL parts for location patching -function getUrlParts(key: keyof typeof PAGES) { - const originalUrl = PAGES[key] +function getUrlParts(key: string) { + const originalUrl = CORPUS[key].url const url = new URL(originalUrl) return { host: url.host, @@ -51,42 +52,42 @@ function getUrlParts(key: keyof typeof PAGES) { } // Load and cache HAR file -async function loadHar(key: keyof typeof PAGES): Promise { +async function loadHar(key: string): Promise { if (harCache.has(key)) { return harCache.get(key)! } - const harPath = path.join(__dirname, 'har', `${key}.har`) + const harPath = path.join(__dirname, 'corpus', 'har', `${key}.har`) const harContent = await fs.readFile(harPath, 'utf-8') const harData = JSON.parse(harContent) harCache.set(key, harData) return harData } -// Add redirect routes for each PAGES URL to handle refreshes -Object.entries(PAGES).forEach(([key, url]) => { - const urlObj = new URL(url) +// Add redirect routes for each CORPUS URL to handle refreshes +Object.entries(CORPUS).forEach(([key, entry]) => { + const urlObj = new URL(entry.url) app.get(urlObj.pathname, (_req, res) => { - res.redirect(`/har/${key}/gitcasso`) + res.redirect(`/corpus/${key}/gitcasso`) }) }) -// List available HAR files +// List available corpus files app.get('/', async (_req, res) => { try { - const harDir = path.join(__dirname, 'har') - const files = await fs.readdir(harDir) - const harFiles = files.filter((file) => file.endsWith('.har')) - - const links = harFiles - .map((file) => { - const basename = path.basename(file, '.har') + const links = Object.entries(CORPUS) + .map(([key, entry]) => { + const description = entry.description ? `
${entry.description}
` : '' return `
  • -
    ${basename}
    +
    +
    ${key}
    +
    ${entry.type.toUpperCase()}
    + ${description} +
  • ` @@ -97,23 +98,23 @@ app.get('/', async (_req, res) => { - HAR Page Viewer + Corpus Viewer -

    📄 HAR Page Viewer

    +

    📄 Corpus Viewer

    Select a recorded page to view:

      ${links}
    +
    +

    Corpus Types

    +

    HAR: Automated network captures of initial page loads

    +

    HTML: Manual SingleFile captures of post-interaction states

    +
    `) } catch (_error) { - res.status(500).send('Error listing HAR files') + res.status(500).send('Error listing corpus files') } }) -// Serve the main HTML page from HAR -app.get('/har/:key/:mode(clean|gitcasso)', async (req, res) => { +// Serve the main page from corpus +app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { try { - // biome-ignore lint/complexity/useLiteralKeys: type comes from path string - const key = req.params['key'] as keyof typeof PAGES - // biome-ignore lint/complexity/useLiteralKeys: type comes from path string - const mode = req.params['mode'] as 'clean' | 'gitcasso' - if (!(key in PAGES)) { - return res.status(400).send('Invalid key - not found in PAGES') - } + const key = req.params.key + const mode = req.params.mode as 'clean' | 'gitcasso' - // Find the main HTML response - const harData = await loadHar(key) - const originalUrl = PAGES[key] - const mainEntry = - harData.log.entries.find( - (entry) => - entry.request.url === originalUrl && - entry.response.content.mimeType?.includes('text/html') && - entry.response.content.text, - ) || - harData.log.entries.find( - (entry) => - entry.response.status === 200 && - entry.response.content.mimeType?.includes('text/html') && - entry.response.content.text, - ) - if (!mainEntry) { - return res.status(404).send('No HTML content found in HAR file') + if (!(key in CORPUS)) { + return res.status(400).send('Invalid key - not found in CORPUS') } - // Extract all domains from HAR entries for dynamic replacement - const domains = new Set() - harData.log.entries.forEach((entry) => { - try { - const url = new URL(entry.request.url) - domains.add(url.hostname) - } catch { - // Skip invalid URLs + const entry = CORPUS[key] + + if (entry.type === 'har') { + // Handle HAR corpus + const harData = await loadHar(key) + const originalUrl = entry.url + const mainEntry = + harData.log.entries.find( + (entry) => + entry.request.url === originalUrl && + entry.response.content.mimeType?.includes('text/html') && + entry.response.content.text, + ) || + harData.log.entries.find( + (entry) => + entry.response.status === 200 && + entry.response.content.mimeType?.includes('text/html') && + entry.response.content.text, + ) + if (!mainEntry) { + return res.status(404).send('No HTML content found in HAR file') } - }) - // Replace external URLs with local asset URLs - let html = mainEntry.response.content.text! - domains.forEach((domain) => { - const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const regex = new RegExp(`https?://${escapedDomain}`, 'g') - html = html.replace(regex, `/asset/${key}`) - }) - if (mode === 'gitcasso') { - html = injectGitcassoScript(key, html) + // Extract all domains from HAR entries for dynamic replacement + const domains = new Set() + harData.log.entries.forEach((entry) => { + try { + const url = new URL(entry.request.url) + domains.add(url.hostname) + } catch { + // Skip invalid URLs + } + }) + + // Replace external URLs with local asset URLs + let html = mainEntry.response.content.text! + domains.forEach((domain) => { + const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const regex = new RegExp(`https?://${escapedDomain}`, 'g') + html = html.replace(regex, `/asset/${key}`) + }) + if (mode === 'gitcasso') { + html = injectGitcassoScript(key, html) + } + return res.send(html) + } else if (entry.type === 'html') { + // Handle HTML corpus + const htmlPath = path.join(__dirname, 'corpus', 'html', `${key}.html`) + let html = await fs.readFile(htmlPath, 'utf-8') + if (mode === 'gitcasso') { + html = injectGitcassoScript(key, html) + } + return res.send(html) + } else { + return res.status(400).send('Unknown corpus type') } - return res.send(html) } catch (error) { console.error('Error serving page:', error) return res.status(500).send('Error loading page') } }) -// Serve assets from HAR file +// Serve assets from HAR file (only for HAR corpus) app.get('/asset/:key/*', async (req, res) => { try { - const key = req.params.key as keyof typeof PAGES - if (!(key in PAGES)) { - return res.status(400).send('Invalid key - not found in PAGES') + const key = req.params.key + if (!(key in CORPUS)) { + return res.status(400).send('Invalid key - not found in CORPUS') } + + const entry = CORPUS[key] + if (entry.type !== 'har') { + return res.status(400).send('Asset serving only available for HAR corpus') + } + const assetPath = (req.params as any)[0] as string const harData = await loadHar(key) @@ -244,6 +269,7 @@ app.get('/asset/:key/*', async (req, res) => { return res.status(404).send('Asset not found') } }) + // Serve extension assets from filesystem app.use('/chrome-mv3-dev', express.static(path.join(__dirname, '..', '.output', 'chrome-mv3-dev'))) @@ -252,7 +278,7 @@ app.post('/rebuild', async (_req, res) => { try { console.log('Rebuild triggered via API') - // Run pnpm run rebuild:dev + // Run pnpm run build:dev const buildProcess = spawn('pnpm', ['run', 'build:dev'], { cwd: path.join(__dirname, '..', '..'), stdio: ['pipe', 'pipe', 'pipe'], @@ -304,11 +330,11 @@ app.post('/rebuild', async (_req, res) => { }) app.listen(PORT, () => { - console.log(`HAR Page Viewer running at http://localhost:${PORT}`) + console.log(`Corpus Viewer running at http://localhost:${PORT}`) console.log('Click the links to view recorded pages') }) -function injectGitcassoScript(key: keyof typeof PAGES, html: string) { +function injectGitcassoScript(key: string, html: string) { const urlParts = getUrlParts(key) // Inject patched content script with location patching @@ -326,7 +352,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { // Replace the problematic webextension-polyfill error check let patchedCode = code.replace( 'throw new Error("This script should only be loaded in a browser extension.")', - 'console.warn("Webextension-polyfill check bypassed for HAR testing")' + 'console.warn("Webextension-polyfill check bypassed for corpus testing")' ); window.gitcassoMockLocation = { host: '${urlParts.host}', @@ -345,7 +371,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { .catch(error => { console.error('Failed to load and patch content script:', error); }); - + // Create floating rebuild button const rebuildButton = document.createElement('div'); rebuildButton.id = 'gitcasso-rebuild-btn'; @@ -371,35 +397,35 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { transition: all 0.2s ease; font-family: system-ui, -apple-system, sans-serif; \`; - + rebuildButton.addEventListener('mouseenter', () => { rebuildButton.style.transform = 'scale(1.1)'; rebuildButton.style.backgroundColor = '#005a9e'; }); - + rebuildButton.addEventListener('mouseleave', () => { rebuildButton.style.transform = 'scale(1)'; rebuildButton.style.backgroundColor = '#007acc'; }); - + rebuildButton.addEventListener('click', async () => { try { rebuildButton.innerHTML = '⏳'; rebuildButton.style.backgroundColor = '#ffa500'; rebuildButton.title = 'Rebuilding...'; - + const response = await fetch('/rebuild', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); - + const result = await response.json(); - + if (result.success) { rebuildButton.innerHTML = '✅'; rebuildButton.style.backgroundColor = '#28a745'; rebuildButton.title = 'Build successful! Reloading...'; - + setTimeout(() => { location.reload(true); }, 1000); @@ -407,7 +433,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { rebuildButton.innerHTML = '❌'; rebuildButton.style.backgroundColor = '#dc3545'; rebuildButton.title = 'Build failed: ' + (result.error || result.message); - + setTimeout(() => { rebuildButton.innerHTML = '🔄'; rebuildButton.style.backgroundColor = '#007acc'; @@ -419,7 +445,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { rebuildButton.innerHTML = '❌'; rebuildButton.style.backgroundColor = '#dc3545'; rebuildButton.title = 'Network error: ' + error.message; - + setTimeout(() => { rebuildButton.innerHTML = '🔄'; rebuildButton.style.backgroundColor = '#007acc'; @@ -427,7 +453,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { }, 3000); } }); - + document.body.appendChild(rebuildButton); // Create CommentSpot display @@ -502,4 +528,4 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) { throw error('No closing body tag, nowhere to put the content script!') } return html.replace('', `${contentScriptTag}`) -} +} \ No newline at end of file diff --git a/browser-extension/tests/corpus/_corpus-index.ts b/browser-extension/tests/corpus/_corpus-index.ts new file mode 100644 index 0000000..d864b8e --- /dev/null +++ b/browser-extension/tests/corpus/_corpus-index.ts @@ -0,0 +1,34 @@ +export type CorpusType = 'har' | 'html' + +export interface CorpusEntry { + url: string + type: CorpusType + description?: string // Helpful for HTML corpus to describe the captured state +} + +export const CORPUS: Record = { + // HAR corpus (initial page loads) + gh_issue: { + url: 'https://github.com/diffplug/selfie/issues/523', + type: 'har' + }, + gh_new_issue: { + url: 'https://github.com/diffplug/selfie/issues/new', + type: 'har' + }, + gh_new_pr: { + url: 'https://github.com/diffplug/selfie/compare/main...cavia-porcellus:selfie:main?expand=1', + type: 'har' + }, + gh_pr: { + url: 'https://github.com/diffplug/selfie/pull/517', + type: 'har' + }, + // HTML corpus (captured after user interactions via SingleFile) + // Add new entries here as needed, e.g.: + // gh_issue_with_comment_preview: { + // url: 'https://github.com/diffplug/selfie/issues/523', + // type: 'html', + // description: 'Issue page with comment textarea expanded and preview tab active' + // } +} as const \ No newline at end of file diff --git a/browser-extension/tests/har/gh_issue.har b/browser-extension/tests/corpus/har/gh_issue.har similarity index 100% rename from browser-extension/tests/har/gh_issue.har rename to browser-extension/tests/corpus/har/gh_issue.har diff --git a/browser-extension/tests/har/gh_new_issue.har b/browser-extension/tests/corpus/har/gh_new_issue.har similarity index 100% rename from browser-extension/tests/har/gh_new_issue.har rename to browser-extension/tests/corpus/har/gh_new_issue.har diff --git a/browser-extension/tests/har/gh_new_pr.har b/browser-extension/tests/corpus/har/gh_new_pr.har similarity index 100% rename from browser-extension/tests/har/gh_new_pr.har rename to browser-extension/tests/corpus/har/gh_new_pr.har diff --git a/browser-extension/tests/har/gh_pr.har b/browser-extension/tests/corpus/har/gh_pr.har similarity index 100% rename from browser-extension/tests/har/gh_pr.har rename to browser-extension/tests/corpus/har/gh_pr.har diff --git a/browser-extension/tests/har-record.ts b/browser-extension/tests/har-record.ts index 8125ed3..eb9af1b 100644 --- a/browser-extension/tests/har-record.ts +++ b/browser-extension/tests/har-record.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { chromium } from '@playwright/test' -import { PAGES } from './har/_har-index' +import { CORPUS } from './corpus/_corpus-index' // Convert glob pattern to regex function globToRegex(pattern: string): RegExp { @@ -12,10 +12,12 @@ function globToRegex(pattern: string): RegExp { return new RegExp(`^${regexPattern}$`) } -// Filter pages based on pattern -function filterPages(pattern: string) { +// Filter HAR corpus entries based on pattern +function filterHarEntries(pattern: string) { const regex = globToRegex(pattern) - return Object.entries(PAGES).filter(([name]) => regex.test(name)) + return Object.entries(CORPUS) + .filter(([name, entry]) => regex.test(name) && entry.type === 'har') + .map(([name, entry]) => [name, entry.url] as const) } const FILTER = @@ -35,7 +37,7 @@ async function record(name: string, url: string) { const context = await browser.newContext({ recordHar: { mode: 'minimal', // smaller; omits cookies etc. - path: `tests/har/${name}.har`, + path: `tests/corpus/har/${name}.har`, urlFilter: FILTER, // restrict scope to GitHub + assets }, storageState: 'playwright/.auth/gh.json', // local-only; never commit @@ -63,7 +65,7 @@ function stripHeaders(headers?: any[]) { async function sanitize(filename: string) { console.log('Sanitizing:', filename) - const p = path.join('tests/har', filename) + const p = path.join('tests/corpus/har', filename) const har = JSON.parse(await fs.readFile(p, 'utf8')) for (const e of har.log?.entries ?? []) { @@ -87,47 +89,51 @@ async function sanitize(filename: string) { // If no argument provided, show available keys if (!pattern) { - console.log('Available recording targets:') - for (const [name] of Object.entries(PAGES)) { - console.log(` ${name}`) + console.log('Available HAR recording targets:') + for (const [name, entry] of Object.entries(CORPUS)) { + if (entry.type === 'har') { + console.log(` ${name}`) + } } - console.log('\nUsage: pnpm run har:record ') + console.log('\nUsage: pnpm run corpus:record:har ') console.log('Examples:') - console.log(' pnpm run har:record "*" # Record all') - console.log(' pnpm run har:record "github_*" # Record all github_*') - console.log(' pnpm run har:record "github_issue" # Record specific target') + console.log(' pnpm run corpus:record:har "*" # Record all HAR targets') + console.log(' pnpm run corpus:record:har "gh_*" # Record all gh_* targets') + console.log(' pnpm run corpus:record:har "gh_issue" # Record specific target') return } - // Filter pages based on pattern - const pagesToRecord = filterPages(pattern) + // Filter HAR entries based on pattern + const entriesToRecord = filterHarEntries(pattern) - if (pagesToRecord.length === 0) { - console.log(`No targets match pattern: ${pattern}`) - console.log('Available targets:') - for (const [name] of Object.entries(PAGES)) { - console.log(` ${name}`) + if (entriesToRecord.length === 0) { + console.log(`No HAR targets match pattern: ${pattern}`) + console.log('Available HAR targets:') + for (const [name, entry] of Object.entries(CORPUS)) { + if (entry.type === 'har') { + console.log(` ${name}`) + } } return } - console.log(`Recording ${pagesToRecord.length} target(s) matching "${pattern}":`) - for (const [name] of pagesToRecord) { + console.log(`Recording ${entriesToRecord.length} HAR target(s) matching "${pattern}":`) + for (const [name] of entriesToRecord) { console.log(` ${name}`) } console.log() - await fs.mkdir('tests/har', { recursive: true }) + await fs.mkdir('tests/corpus/har', { recursive: true }) // Record filtered HAR files - for (const [name, url] of pagesToRecord) { + for (const [name, url] of entriesToRecord) { await record(name, url) } console.log('Recording complete. Sanitizing...') // Sanitize recorded HAR files - for (const [name] of pagesToRecord) { + for (const [name] of entriesToRecord) { await sanitize(`${name}.har`) } diff --git a/browser-extension/tests/har/_har-index.ts b/browser-extension/tests/har/_har-index.ts deleted file mode 100644 index dfd73e2..0000000 --- a/browser-extension/tests/har/_har-index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const PAGES = { - gh_issue: 'https://github.com/diffplug/selfie/issues/523', - gh_new_issue: 'https://github.com/diffplug/selfie/issues/new', - gh_new_pr: - 'https://github.com/diffplug/selfie/compare/main...cavia-porcellus:selfie:main?expand=1', - gh_pr: 'https://github.com/diffplug/selfie/pull/517', -} as const From 1f20c7d4987e00adcf60d0d81de93e36d4e788fd Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 16:51:37 -0700 Subject: [PATCH 02/11] Fixup --- browser-extension/tests/corpus-view.ts | 28 ++++++++++++------- .../tests/corpus/_corpus-index.ts | 10 +++---- browser-extension/tests/har-fixture-utils.ts | 20 +++++++++---- browser-extension/tests/har-fixture.ts | 6 ++-- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/browser-extension/tests/corpus-view.ts b/browser-extension/tests/corpus-view.ts index add93c7..fcfeb0e 100644 --- a/browser-extension/tests/corpus-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -27,7 +27,7 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import express from 'express' import type { Har } from 'har-format' -import { CORPUS, type CorpusEntry } from './corpus/_corpus-index' +import { CORPUS } from './corpus/_corpus-index' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const app = express() @@ -41,7 +41,11 @@ const harCache = new Map() // Extract URL parts for location patching function getUrlParts(key: string) { - const originalUrl = CORPUS[key].url + const entry = CORPUS[key] + if (!entry) { + throw new Error(`Corpus entry not found: ${key}`) + } + const originalUrl = entry.url const url = new URL(originalUrl) return { host: url.host, @@ -77,7 +81,9 @@ app.get('/', async (_req, res) => { try { const links = Object.entries(CORPUS) .map(([key, entry]) => { - const description = entry.description ? `
    ${entry.description}
    ` : '' + const description = entry.description + ? `
    ${entry.description}
    ` + : '' return `
  • @@ -143,14 +149,16 @@ app.get('/', async (_req, res) => { // Serve the main page from corpus app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { try { - const key = req.params.key - const mode = req.params.mode as 'clean' | 'gitcasso' + // biome-ignore lint/complexity/useLiteralKeys: type comes from path string + const key = req.params['key'] + // biome-ignore lint/complexity/useLiteralKeys: type comes from path string + const mode = req.params['mode'] as 'clean' | 'gitcasso' - if (!(key in CORPUS)) { + if (!key || !(key in CORPUS)) { return res.status(400).send('Invalid key - not found in CORPUS') } - const entry = CORPUS[key] + const entry = CORPUS[key]! if (entry.type === 'har') { // Handle HAR corpus @@ -216,11 +224,11 @@ app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { app.get('/asset/:key/*', async (req, res) => { try { const key = req.params.key - if (!(key in CORPUS)) { + if (!key || !(key in CORPUS)) { return res.status(400).send('Invalid key - not found in CORPUS') } - const entry = CORPUS[key] + const entry = CORPUS[key]! if (entry.type !== 'har') { return res.status(400).send('Asset serving only available for HAR corpus') } @@ -528,4 +536,4 @@ function injectGitcassoScript(key: string, html: string) { throw error('No closing body tag, nowhere to put the content script!') } return html.replace('', `${contentScriptTag}`) -} \ No newline at end of file +} diff --git a/browser-extension/tests/corpus/_corpus-index.ts b/browser-extension/tests/corpus/_corpus-index.ts index d864b8e..0f311eb 100644 --- a/browser-extension/tests/corpus/_corpus-index.ts +++ b/browser-extension/tests/corpus/_corpus-index.ts @@ -9,20 +9,20 @@ export interface CorpusEntry { export const CORPUS: Record = { // HAR corpus (initial page loads) gh_issue: { + type: 'har', url: 'https://github.com/diffplug/selfie/issues/523', - type: 'har' }, gh_new_issue: { + type: 'har', url: 'https://github.com/diffplug/selfie/issues/new', - type: 'har' }, gh_new_pr: { + type: 'har', url: 'https://github.com/diffplug/selfie/compare/main...cavia-porcellus:selfie:main?expand=1', - type: 'har' }, gh_pr: { + type: 'har', url: 'https://github.com/diffplug/selfie/pull/517', - type: 'har' }, // HTML corpus (captured after user interactions via SingleFile) // Add new entries here as needed, e.g.: @@ -31,4 +31,4 @@ export const CORPUS: Record = { // type: 'html', // description: 'Issue page with comment textarea expanded and preview tab active' // } -} as const \ No newline at end of file +} as const diff --git a/browser-extension/tests/har-fixture-utils.ts b/browser-extension/tests/har-fixture-utils.ts index 15faa4d..ef593c9 100644 --- a/browser-extension/tests/har-fixture-utils.ts +++ b/browser-extension/tests/har-fixture-utils.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import type { Har as HarFile } from 'har-format' import { parseHTML } from 'linkedom' -import { PAGES } from './har/_har-index' +import { CORPUS } from './corpus/_corpus-index' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -32,9 +32,13 @@ export interface TestDOMContext { let currentDOMInstance: any = null let originalGlobals: Partial = {} -export async function loadHtmlFromHar(key: keyof typeof PAGES): Promise { - const url = PAGES[key] - const harPath = path.join(__dirname, 'har', `${key}.har`) +export async function loadHtmlFromHar(key: keyof typeof CORPUS): Promise { + const entry = CORPUS[key] + if (!entry || entry.type !== 'har') { + throw new Error(`Invalid HAR corpus key: ${String(key)}`) + } + const url = entry.url + const harPath = path.join(__dirname, 'corpus', 'har', `${String(key)}.har`) const harContent = await fs.readFile(harPath, 'utf-8') const harData: HarFile = JSON.parse(harContent) const mainEntry = harData.log.entries.find((entry) => entry.request.url === url) @@ -101,9 +105,13 @@ export function cleanupDOM(): void { } } -export async function setupHarDOM(key: keyof typeof PAGES): Promise { +export async function setupHarDOM(key: keyof typeof CORPUS): Promise { const html = await loadHtmlFromHar(key) - const url = PAGES[key] + const entry = CORPUS[key] + if (!entry || entry.type !== 'har') { + throw new Error(`Invalid HAR corpus key: ${String(key)}`) + } + const url = entry.url const domGlobals = createDOMFromHar(html, url) setupDOMFromHar(domGlobals) return domGlobals diff --git a/browser-extension/tests/har-fixture.ts b/browser-extension/tests/har-fixture.ts index befcdf9..216c491 100644 --- a/browser-extension/tests/har-fixture.ts +++ b/browser-extension/tests/har-fixture.ts @@ -30,7 +30,7 @@ vi.mock('overtype', () => { }) import { describe as baseDescribe, test as baseTest, expect } from 'vitest' -import type { PAGES } from './har/_har-index' +import type { CORPUS } from './corpus/_corpus-index' import { cleanupDOM, setupHarDOM } from './har-fixture-utils' export const describe = baseDescribe @@ -39,10 +39,10 @@ export const describe = baseDescribe export { expect } // Fluent interface for HAR-based tests -export function usingHar(harKey: keyof typeof PAGES) { +export function usingHar(harKey: keyof typeof CORPUS) { return { it: (name: string, fn: () => void | Promise) => { - return baseTest(`${harKey}:${name}`, async () => { + return baseTest(`${String(harKey)}:${name}`, async () => { // Setup HAR DOM before test await setupHarDOM(harKey) From 95fcd14878dbf91a6a17b97d607de10974b50711 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 17:24:21 -0700 Subject: [PATCH 03/11] Try to add our first HTML corpus. --- browser-extension/tests/corpus-view.ts | 14 +- .../tests/corpus/_corpus-index.ts | 5 + .../html/gh_issue_populated_comment.html | 1578 +++++++++++++++++ 3 files changed, 1591 insertions(+), 6 deletions(-) create mode 100644 browser-extension/tests/corpus/html/gh_issue_populated_comment.html diff --git a/browser-extension/tests/corpus-view.ts b/browser-extension/tests/corpus-view.ts index fcfeb0e..35a80a0 100644 --- a/browser-extension/tests/corpus-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -524,16 +524,18 @@ function injectGitcassoScript(key: string, html: string) { } // Initial update + updateCommentSpotDisplay() setTimeout(updateCommentSpotDisplay, 100); - - // Update display periodically - setInterval(updateCommentSpotDisplay, 2000); + setTimeout(updateCommentSpotDisplay, 200); + setTimeout(updateCommentSpotDisplay, 400); + setTimeout(updateCommentSpotDisplay, 800); document.body.appendChild(commentSpotDisplay); ` - if (!html.includes('')) { - throw error('No closing body tag, nowhere to put the content script!') + if (html.includes('')) { + return html.replace('', `${contentScriptTag}`) + } else { + return html + contentScriptTag } - return html.replace('', `${contentScriptTag}`) } diff --git a/browser-extension/tests/corpus/_corpus-index.ts b/browser-extension/tests/corpus/_corpus-index.ts index 0f311eb..103a4d7 100644 --- a/browser-extension/tests/corpus/_corpus-index.ts +++ b/browser-extension/tests/corpus/_corpus-index.ts @@ -12,6 +12,11 @@ export const CORPUS: Record = { type: 'har', url: 'https://github.com/diffplug/selfie/issues/523', }, + gh_issue_populated_comment: { + type: 'html', + url: 'https://github.com/diffplug/selfie/issues/523', + description: 'comment text box has some text' + }, gh_new_issue: { type: 'har', url: 'https://github.com/diffplug/selfie/issues/new', diff --git a/browser-extension/tests/corpus/html/gh_issue_populated_comment.html b/browser-extension/tests/corpus/html/gh_issue_populated_comment.html new file mode 100644 index 0000000..77811a3 --- /dev/null +++ b/browser-extension/tests/corpus/html/gh_issue_populated_comment.html @@ -0,0 +1,1578 @@ + + + + + + + + + + + + +[jvm] docs for VCR · Issue #523 · diffplug/selfie + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + Skip to content + + + + + + + + +
    +
    + + + + + + +
    +
    +
    + +
    + + + +
    +
    +
    + + + + + + +

    [jvm] docs for VCR #523

    Activity

    oss-test-user

    Add a comment

    new Comment
    Markdown input: edit mode selected.

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      Type

      No type

      Projects

      No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Notifications

      You're not receiving notifications from this thread.

      Issue actions

        +
        +
        +
        +
        + + + + + + + + +
        +
        [jvm] docs for VCR · Issue #523 · diffplug/selfie
        +
        + + \ No newline at end of file From eccf5488888607b083c6d0aa4218b21a27650751 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 17:29:29 -0700 Subject: [PATCH 04/11] Works for HTML, broke HAR. --- browser-extension/src/entrypoints/content.ts | 2 - browser-extension/tests/corpus-view.ts | 109 ++++++++++++------ .../tests/corpus/_corpus-index.ts | 2 +- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/browser-extension/src/entrypoints/content.ts b/browser-extension/src/entrypoints/content.ts index c21c33a..3136329 100644 --- a/browser-extension/src/entrypoints/content.ts +++ b/browser-extension/src/entrypoints/content.ts @@ -99,9 +99,7 @@ function enhanceMaybe(textarea: HTMLTextAreaElement) { return } - logger.debug('activating textarea {}', textarea) injectStyles() - try { const location = detectLocation() logger.debug('[gitcasso] Calling tryToEnhance with location:', location) diff --git a/browser-extension/tests/corpus-view.ts b/browser-extension/tests/corpus-view.ts index 35a80a0..b598d97 100644 --- a/browser-extension/tests/corpus-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -21,7 +21,6 @@ */ import { spawn } from 'node:child_process' -import { error } from 'node:console' import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' @@ -200,16 +199,28 @@ app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { html = html.replace(regex, `/asset/${key}`) }) if (mode === 'gitcasso') { - html = injectGitcassoScript(key, html) + html = await injectGitcassoScript(key, html) } return res.send(html) } else if (entry.type === 'html') { // Handle HTML corpus const htmlPath = path.join(__dirname, 'corpus', 'html', `${key}.html`) let html = await fs.readFile(htmlPath, 'utf-8') + + // Strip CSP headers that might block our injected scripts + html = stripCSPFromHTML(html) + if (mode === 'gitcasso') { - html = injectGitcassoScript(key, html) + html = await injectGitcassoScript(key, html) } + + // Set permissive headers for HTML corpus + res.set({ + 'Content-Security-Policy': + "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:;", + 'X-Content-Type-Options': 'nosniff', + }) + return res.send(html) } else { return res.status(400).send('Unknown corpus type') @@ -342,43 +353,73 @@ app.listen(PORT, () => { console.log('Click the links to view recorded pages') }) -function injectGitcassoScript(key: string, html: string) { +// Strip CSP meta tags and headers from HTML that might block our scripts +function stripCSPFromHTML(html: string): string { + // Remove CSP meta tags + html = html.replace(/]*http-equiv\s*=\s*["']content-security-policy["'][^>]*>/gi, '') + html = html.replace(/]*name\s*=\s*["']content-security-policy["'][^>]*>/gi, '') + + // Remove any other restrictive security meta tags + html = html.replace(/]*http-equiv\s*=\s*["']x-content-type-options["'][^>]*>/gi, '') + + return html +} + +async function injectGitcassoScript(key: string, html: string): Promise { const urlParts = getUrlParts(key) + // Read and embed the content script directly to avoid CSP issues + let contentScriptCode = '' + try { + const contentScriptPath = path.join( + __dirname, + '..', + '.output', + 'chrome-mv3-dev', + 'content-scripts', + 'content.js', + ) + contentScriptCode = await fs.readFile(contentScriptPath, 'utf-8') + + // Patch the content script to remove webextension-polyfill issues + contentScriptCode = contentScriptCode.replace( + 'throw new Error("This script should only be loaded in a browser extension.")', + 'console.warn("Webextension-polyfill check bypassed for corpus testing")', + ) + } catch (error) { + console.warn('Could not read content script, using fallback:', error) + contentScriptCode = 'console.warn("Content script not found - extension may not be built");' + } + // Inject patched content script with location patching - const contentScriptTag = - ` + const contentScriptTag = ` + ` + if (html.includes('')) { + return html.replace('', `${contentScriptTag}`) + } else { + return html + contentScriptTag + } +} + +// HTML version - embeds content script directly to avoid CSP issues +async function injectGitcassoScriptForHTML(key: string, html: string): Promise { const urlParts = getUrlParts(key) // Read and embed the content script directly to avoid CSP issues From 4e6748c2f1d54ea21faffcb4242ec39f112219c7 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 19:36:43 -0700 Subject: [PATCH 06/11] Simplify --- browser-extension/tests/corpus-view.ts | 635 ++++++++++--------------- 1 file changed, 254 insertions(+), 381 deletions(-) diff --git a/browser-extension/tests/corpus-view.ts b/browser-extension/tests/corpus-view.ts index c996f6a..a2fdd29 100644 --- a/browser-extension/tests/corpus-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -32,6 +32,50 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) const app = express() const PORT = 3001 +// Constants +const WEBEXTENSION_POLYFILL_PATCH = + 'throw new Error("This script should only be loaded in a browser extension.")' +const WEBEXTENSION_POLYFILL_REPLACEMENT = + 'console.warn("Webextension-polyfill check bypassed for corpus testing")' +const BROWSER_API_MOCKS = + '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 PERMISSIVE_CSP = "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:;" + +// UI Styles +const REBUILD_BUTTON_STYLES = ` + position: fixed; + top: 20px; + right: 20px; + width: 50px; + height: 50px; + background: #007acc; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 999999; + user-select: none; + transition: all 0.2s ease; + font-family: system-ui, -apple-system, sans-serif; +` + +const COMMENT_SPOT_STYLES = { + container: + 'position: fixed; top: 80px; right: 20px; width: 400px; 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);', + empty: 'color: #666; font-style: italic;', + header: 'font-weight: bold; margin-bottom: 8px; color: #333;', + jsonPre: 'margin: 4px 0; font-size: 10px;', + noInfo: 'color: #999; font-style: italic; margin-top: 4px;', + spotContainer: 'margin-bottom: 12px; padding: 8px; border: 1px solid #eee; border-radius: 4px;', + spotTitle: 'font-weight: bold; color: #555;', + textareaHeader: 'font-weight: bold; color: #007acc; margin-top: 8px;', + textareaPre: 'margin: 4px 0; font-size: 10px; color: #666;', +} + // Middleware to parse JSON bodies app.use(express.json()) @@ -216,8 +260,7 @@ app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { // Set permissive headers for HTML corpus res.set({ - 'Content-Security-Policy': - "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:;", + 'Content-Security-Policy': PERMISSIVE_CSP, 'X-Content-Type-Options': 'nosniff', }) @@ -252,20 +295,7 @@ app.get('/asset/:key/*', async (req, res) => { const assetEntry = harData.log.entries.find((entry) => { try { const url = new URL(entry.request.url) - // First try exact path match - if (url.pathname === `/${assetPath}`) { - return true - } - // Then try path ending match (for nested paths) - if (url.pathname.endsWith(`/${assetPath}`)) { - return true - } - // Handle query parameters - check if path without query matches - const pathWithoutQuery = url.pathname + url.search - if (pathWithoutQuery === `/${assetPath}` || pathWithoutQuery.endsWith(`/${assetPath}`)) { - return true - } - return false + return matchAssetPath(url, assetPath) } catch { return false } @@ -365,197 +395,221 @@ function stripCSPFromHTML(html: string): string { return html } -// HAR version - uses fetch() to load content script (original approach) -function injectGitcassoScriptForHAR(key: string, html: string): string { - const urlParts = getUrlParts(key) +// Shared UI Component Functions +function createRebuildButtonScript(): string { + return ` + // Create floating rebuild button + const rebuildButton = document.createElement('div'); + rebuildButton.id = 'gitcasso-rebuild-btn'; + rebuildButton.innerHTML = '🔄'; + rebuildButton.title = 'Rebuild Extension'; + rebuildButton.style.cssText = \`${REBUILD_BUTTON_STYLES}\`; + + rebuildButton.addEventListener('mouseenter', () => { + rebuildButton.style.transform = 'scale(1.1)'; + rebuildButton.style.backgroundColor = '#005a9e'; + }); + + rebuildButton.addEventListener('mouseleave', () => { + rebuildButton.style.transform = 'scale(1)'; + rebuildButton.style.backgroundColor = '#007acc'; + }); + + rebuildButton.addEventListener('click', async () => { + try { + rebuildButton.innerHTML = '⏳'; + rebuildButton.style.backgroundColor = '#ffa500'; + rebuildButton.title = 'Rebuilding...'; + + const response = await fetch('/rebuild', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const result = await response.json(); + + if (result.success) { + rebuildButton.innerHTML = '✅'; + rebuildButton.style.backgroundColor = '#28a745'; + rebuildButton.title = 'Build successful! Reloading...'; + + setTimeout(() => { + location.reload(true); + }, 1000); + } else { + rebuildButton.innerHTML = '❌'; + rebuildButton.style.backgroundColor = '#dc3545'; + rebuildButton.title = 'Build failed: ' + (result.error || result.message); + + setTimeout(() => { + rebuildButton.innerHTML = '🔄'; + rebuildButton.style.backgroundColor = '#007acc'; + rebuildButton.title = 'Rebuild Extension'; + }, 3000); + } + } catch (error) { + console.error('Rebuild failed:', error); + rebuildButton.innerHTML = '❌'; + rebuildButton.style.backgroundColor = '#dc3545'; + rebuildButton.title = 'Network error: ' + error.message; - // Inject patched content script with location patching - const contentScriptTag = - ` - + // Execute the patched script with browser API mocks prepended + const script = document.createElement('script'); + script.textContent = '${BROWSER_API_MOCKS}' + patchedCode; + document.head.appendChild(script); + console.log('Gitcasso content script loaded with location patching for:', '${urlParts.href}'); + }) + .catch(error => { + console.error('Failed to load and patch content script:', error); + }); ` + + return ` + + ` +} + +// HAR version - uses fetch() to load content script (original approach) +function injectGitcassoScriptForHAR(key: string, html: string): string { + const urlParts = getUrlParts(key) + const contentScriptTag = createGitcassoScript(urlParts) + if (html.includes('')) { return html.replace('', `${contentScriptTag}`) } else { @@ -582,197 +636,16 @@ async function injectGitcassoScriptForHTML(key: string, html: string): Promise - console.log('Loading Gitcasso with mocked location:', '${urlParts.href}'); + const contentScriptTag = createGitcassoScript(urlParts, contentScriptCode) - // Set up mocked location - window.gitcassoMockLocation = { - host: '${urlParts.host}', - pathname: '${urlParts.pathname}' - }; - - // Set up browser API mocks - 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 content script directly - try { - ${contentScriptCode} - console.log('Gitcasso content script loaded with location patching for:', '${urlParts.href}'); - } catch (error) { - console.error('Failed to execute content script:', error); - } - - // Create floating rebuild button - const rebuildButton = document.createElement('div'); - rebuildButton.id = 'gitcasso-rebuild-btn'; - rebuildButton.innerHTML = '🔄'; - rebuildButton.title = 'Rebuild Extension'; - rebuildButton.style.cssText = \` - position: fixed; - top: 20px; - right: 20px; - width: 50px; - height: 50px; - background: #007acc; - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - z-index: 999999; - user-select: none; - transition: all 0.2s ease; - font-family: system-ui, -apple-system, sans-serif; - \`; - - rebuildButton.addEventListener('mouseenter', () => { - rebuildButton.style.transform = 'scale(1.1)'; - rebuildButton.style.backgroundColor = '#005a9e'; - }); - - rebuildButton.addEventListener('mouseleave', () => { - rebuildButton.style.transform = 'scale(1)'; - rebuildButton.style.backgroundColor = '#007acc'; - }); - - rebuildButton.addEventListener('click', async () => { - try { - rebuildButton.innerHTML = '⏳'; - rebuildButton.style.backgroundColor = '#ffa500'; - rebuildButton.title = 'Rebuilding...'; - - const response = await fetch('/rebuild', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const result = await response.json(); - - if (result.success) { - rebuildButton.innerHTML = '✅'; - rebuildButton.style.backgroundColor = '#28a745'; - rebuildButton.title = 'Build successful! Reloading...'; - - setTimeout(() => { - location.reload(true); - }, 1000); - } else { - rebuildButton.innerHTML = '❌'; - rebuildButton.style.backgroundColor = '#dc3545'; - rebuildButton.title = 'Build failed: ' + (result.error || result.message); - - setTimeout(() => { - rebuildButton.innerHTML = '🔄'; - rebuildButton.style.backgroundColor = '#007acc'; - rebuildButton.title = 'Rebuild Extension'; - }, 3000); - } - } catch (error) { - console.error('Rebuild failed:', error); - rebuildButton.innerHTML = '❌'; - rebuildButton.style.backgroundColor = '#dc3545'; - rebuildButton.title = 'Network error: ' + error.message; - - setTimeout(() => { - rebuildButton.innerHTML = '🔄'; - rebuildButton.style.backgroundColor = '#007acc'; - rebuildButton.title = 'Rebuild Extension'; - }, 3000); - } - }); - - 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: 400px;' + - '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 updateCommentSpotDisplay() { - const textareas = document.querySelectorAll('textarea'); - const spotsFound = []; - - for (const textarea of textareas) { - const forValue = 'id=' + textarea.id + ' name=' + textarea.name + ' className=' + textarea.className; - const enhancedItem = window.gitcassoTextareaRegistry ? window.gitcassoTextareaRegistry.get(textarea) : undefined; - if (enhancedItem) { - spotsFound.push({ - for: forValue, - spot: enhancedItem.spot, - title: enhancedItem.enhancer.tableTitle(enhancedItem.spot), - }); - } else { - spotsFound.push({ - for: forValue, - spot: 'NO_SPOT', - }); - } - } - - console.log('Enhanced textareas:', spotsFound.filter(s => s.spot !== 'NO_SPOT').length); - console.log('All textareas on page:', textareas.length); - commentSpotDisplay.innerHTML = '
        ${urlParts.href}\\n' + JSON.stringify(spotsFound, null, 2) + '
        '; - } - - // Initial update - updateCommentSpotDisplay() - setTimeout(updateCommentSpotDisplay, 100); - setTimeout(updateCommentSpotDisplay, 200); - setTimeout(updateCommentSpotDisplay, 400); - setTimeout(updateCommentSpotDisplay, 800); - - document.body.appendChild(commentSpotDisplay); - - ` if (html.includes('')) { return html.replace('', `${contentScriptTag}`) } else { From d11593a778a8cde251a310719fa8615e5ef92722 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 19:48:18 -0700 Subject: [PATCH 07/11] Adapt `har-fixture` to work for `html`. --- browser-extension/tests/har-fixture-utils.ts | 36 +++++++++++++++++ browser-extension/tests/har-fixture.ts | 21 +++++++++- .../tests/lib/enhancers/github.test.ts | 39 ++++++++++++++++++- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/browser-extension/tests/har-fixture-utils.ts b/browser-extension/tests/har-fixture-utils.ts index ef593c9..e48f837 100644 --- a/browser-extension/tests/har-fixture-utils.ts +++ b/browser-extension/tests/har-fixture-utils.ts @@ -105,6 +105,27 @@ export function cleanupDOM(): void { } } +export async function loadHtmlFromHtml(key: keyof typeof CORPUS): Promise { + const entry = CORPUS[key] + if (!entry || entry.type !== 'html') { + throw new Error(`Invalid HTML corpus key: ${String(key)}`) + } + const htmlPath = path.join(__dirname, 'corpus', 'html', `${String(key)}.html`) + return await fs.readFile(htmlPath, 'utf-8') +} + +export async function setupHtmlDOM(key: keyof typeof CORPUS): Promise { + const html = await loadHtmlFromHtml(key) + const entry = CORPUS[key] + if (!entry || entry.type !== 'html') { + throw new Error(`Invalid HTML corpus key: ${String(key)}`) + } + const url = entry.url + const domGlobals = createDOMFromHar(html, url) + setupDOMFromHar(domGlobals) + return domGlobals +} + export async function setupHarDOM(key: keyof typeof CORPUS): Promise { const html = await loadHtmlFromHar(key) const entry = CORPUS[key] @@ -116,3 +137,18 @@ export async function setupHarDOM(key: keyof typeof CORPUS): Promise { + const entry = CORPUS[key] + if (!entry) { + throw new Error(`Invalid corpus key: ${String(key)}`) + } + + if (entry.type === 'har') { + return await setupHarDOM(key) + } else if (entry.type === 'html') { + return await setupHtmlDOM(key) + } else { + throw new Error(`Unsupported corpus type: ${entry.type}`) + } +} diff --git a/browser-extension/tests/har-fixture.ts b/browser-extension/tests/har-fixture.ts index 216c491..50863c6 100644 --- a/browser-extension/tests/har-fixture.ts +++ b/browser-extension/tests/har-fixture.ts @@ -31,7 +31,7 @@ vi.mock('overtype', () => { import { describe as baseDescribe, test as baseTest, expect } from 'vitest' import type { CORPUS } from './corpus/_corpus-index' -import { cleanupDOM, setupHarDOM } from './har-fixture-utils' +import { cleanupDOM, setupDOM, setupHarDOM } from './har-fixture-utils' export const describe = baseDescribe @@ -56,3 +56,22 @@ export function usingHar(harKey: keyof typeof CORPUS) { }, } } + +// Fluent interface for any corpus type (HAR or HTML) +export function using(corpusKey: keyof typeof CORPUS) { + return { + it: (name: string, fn: () => void | Promise) => { + return baseTest(`${String(corpusKey)}:${name}`, async () => { + // Setup DOM for any corpus type + await setupDOM(corpusKey) + + try { + return await fn() + } finally { + // Cleanup after test + cleanupDOM() + } + }) + }, + } +} diff --git a/browser-extension/tests/lib/enhancers/github.test.ts b/browser-extension/tests/lib/enhancers/github.test.ts index 35692c5..7a93fde 100644 --- a/browser-extension/tests/lib/enhancers/github.test.ts +++ b/browser-extension/tests/lib/enhancers/github.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, usingHar } from '../../har-fixture' +import { describe, expect, using, usingHar } from '../../har-fixture' // must import fixture **first** for mocks, the `expect` keeps biome from changing sort-order expect @@ -102,7 +102,7 @@ describe('github', () => { ] `) }) - usingHar('gh_issue').it('should create the correct spot object', async () => { + usingHar('gh_issue').it('no enhancement on initial page load', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { @@ -112,6 +112,41 @@ describe('github', () => { ] `) }) + using('gh_issue_populated_comment').it('should create the correct spot object', async () => { + expect(enhancements(document, window)).toMatchInlineSnapshot(` + [ + { + "for": "id=:rn: name=null className=prc-Textarea-TextArea-13q4j overtype-input", + "spot": { + "domain": "github.com", + "number": 523, + "slug": "diffplug/selfie", + "title": "TODO_TITLE", + "type": "GH_ISSUE_ADD_COMMENT", + "unique_key": "github.com:diffplug/selfie:523", + }, + "title": "TITLE_TODO", + "upperDecoration": + + + + # + 523 + + diffplug/selfie + + , + }, + ] + `) + }) usingHar('gh_new_issue').it('should create the correct spot object', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ From c5e9635f8248d118eedb4ab216a4fbd986087e95 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 19:49:01 -0700 Subject: [PATCH 08/11] Adapt the `har-fixer` agent to the `corpus-fixer`. --- .claude/agents/corpus-fixer.md | 39 ++++++++++++++++++++++++++++++++++ .claude/agents/har-fixer.md | 38 --------------------------------- 2 files changed, 39 insertions(+), 38 deletions(-) create mode 100644 .claude/agents/corpus-fixer.md delete mode 100644 .claude/agents/har-fixer.md diff --git a/.claude/agents/corpus-fixer.md b/.claude/agents/corpus-fixer.md new file mode 100644 index 0000000..44ede88 --- /dev/null +++ b/.claude/agents/corpus-fixer.md @@ -0,0 +1,39 @@ +--- +name: corpus-fixer +description: Use this agent when you need to fix or improve the detection logic for a specific Gitcasso corpus by testing changes in the corpus:view development environment. Examples: Context: User has identified issues with comment spot detection in a specific corpus and wants to test fixes. user: 'The comment detection is missing some spots in corpus ABC123, can you help fix the enhancer logic?' assistant: 'I'll use the corpus-fixer agent to investigate and fix the detection issues in that corpus.' Since the user wants to fix detection logic for a specific corpus, use the corpus-fixer agent to run the corpus: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 corpus XYZ789?' assistant: 'Let me use the corpus-fixer agent to test your enhancer changes against that specific corpus.' The user wants to test enhancer changes against a specific corpus, so use the corpus-fixer agent to validate the changes in the corpus:view environment. +model: inherit +--- + +You are an expert Gitcasso corpus debugging specialist with deep knowledge of browser extension development. You operate exclusively within the `browser-extension` directory and specialize in using the corpus: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 `corpus-view.ts` file to understand the dev environment. + +2. **Launch Development Environment**: Execute `pnpm corpus:view` to bring up the corpus: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 corpus 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 corpus. + +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 corpus 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 corpus entries +- Be methodical in your debugging approach - document what you try and what results you observe +- Understand that corpus can be either HAR files (for initial page loads) or HTML snapshots (for post-interaction states) + +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/agents/har-fixer.md b/.claude/agents/har-fixer.md deleted file mode 100644 index e752310..0000000 --- a/.claude/agents/har-fixer.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -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. From 566162cb0d23d12ff0cb17641e00d4fe3401a698 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 20:37:00 -0700 Subject: [PATCH 09/11] Rename `har-fixture` to `corpus-fixture` --- .../{har-fixture.ts => corpus-fixture.ts} | 25 +----- .../{har-fixture-utils.ts => corpus-utils.ts} | 84 +++++++------------ .../tests/lib/enhancers/github.test.ts | 12 +-- 3 files changed, 40 insertions(+), 81 deletions(-) rename browser-extension/tests/{har-fixture.ts => corpus-fixture.ts} (69%) rename browser-extension/tests/{har-fixture-utils.ts => corpus-utils.ts} (75%) diff --git a/browser-extension/tests/har-fixture.ts b/browser-extension/tests/corpus-fixture.ts similarity index 69% rename from browser-extension/tests/har-fixture.ts rename to browser-extension/tests/corpus-fixture.ts index 50863c6..d7a28c7 100644 --- a/browser-extension/tests/har-fixture.ts +++ b/browser-extension/tests/corpus-fixture.ts @@ -31,38 +31,19 @@ vi.mock('overtype', () => { import { describe as baseDescribe, test as baseTest, expect } from 'vitest' import type { CORPUS } from './corpus/_corpus-index' -import { cleanupDOM, setupDOM, setupHarDOM } from './har-fixture-utils' +import { cleanupDOM, setupDOM } from './corpus-utils' export const describe = baseDescribe // Re-export expect from vitest export { expect } -// Fluent interface for HAR-based tests -export function usingHar(harKey: keyof typeof CORPUS) { - return { - it: (name: string, fn: () => void | Promise) => { - return baseTest(`${String(harKey)}:${name}`, async () => { - // Setup HAR DOM before test - await setupHarDOM(harKey) - - try { - return await fn() - } finally { - // Cleanup after test - cleanupDOM() - } - }) - }, - } -} - // Fluent interface for any corpus type (HAR or HTML) -export function using(corpusKey: keyof typeof CORPUS) { +export function forCorpus(corpusKey: keyof typeof CORPUS) { return { it: (name: string, fn: () => void | Promise) => { return baseTest(`${String(corpusKey)}:${name}`, async () => { - // Setup DOM for any corpus type + // Setup DOM for any corpus type (delegates to HAR or HTML based on type) await setupDOM(corpusKey) try { diff --git a/browser-extension/tests/har-fixture-utils.ts b/browser-extension/tests/corpus-utils.ts similarity index 75% rename from browser-extension/tests/har-fixture-utils.ts rename to browser-extension/tests/corpus-utils.ts index e48f837..d2a9b47 100644 --- a/browser-extension/tests/har-fixture-utils.ts +++ b/browser-extension/tests/corpus-utils.ts @@ -32,7 +32,26 @@ export interface TestDOMContext { let currentDOMInstance: any = null let originalGlobals: Partial = {} -export async function loadHtmlFromHar(key: keyof typeof CORPUS): Promise { +export async function setupDOM(key: keyof typeof CORPUS): Promise { + const entry = CORPUS[key] + if (!entry) { + throw new Error(`Invalid corpus key: ${String(key)}`) + } + + let html: string + if (entry.type === 'har') { + html = await loadRootHtmlStringFromHar(key) + } else if (entry.type === 'html') { + html = await loadHtmlStringFromHtml(key) + } else { + throw new Error(`Unsupported corpus type: ${entry.type}`) + } + const domGlobals = createDOMFromString(html, entry.url) + setupDOMFromHar(domGlobals) + return domGlobals +} + +async function loadRootHtmlStringFromHar(key: keyof typeof CORPUS): Promise { const entry = CORPUS[key] if (!entry || entry.type !== 'har') { throw new Error(`Invalid HAR corpus key: ${String(key)}`) @@ -42,15 +61,22 @@ export async function loadHtmlFromHar(key: keyof typeof CORPUS): Promise const harContent = await fs.readFile(harPath, 'utf-8') const harData: HarFile = JSON.parse(harContent) const mainEntry = harData.log.entries.find((entry) => entry.request.url === url) - if (!mainEntry) { throw new Error(`No entry found for URL: ${url} in HAR file: ${harPath}`) } + return mainEntry.response.content.text! +} - return mainEntry.response.content.text || '' +async function loadHtmlStringFromHtml(key: keyof typeof CORPUS): Promise { + const entry = CORPUS[key] + if (!entry || entry.type !== 'html') { + throw new Error(`Invalid HTML corpus key: ${String(key)}`) + } + const htmlPath = path.join(__dirname, 'corpus', 'html', `${String(key)}.html`) + return await fs.readFile(htmlPath, 'utf-8') } -export function createDOMFromHar(html: string, url: string): TestDOMGlobals { +function createDOMFromString(html: string, url: string): TestDOMGlobals { const dom = parseHTML(html) return { @@ -72,7 +98,7 @@ export function createDOMFromHar(html: string, url: string): TestDOMGlobals { } } -export function setupDOMFromHar(domGlobals: TestDOMGlobals): void { +function setupDOMFromHar(domGlobals: TestDOMGlobals): void { // Store original globals for cleanup originalGlobals = { Document: (globalThis as any).Document, @@ -104,51 +130,3 @@ export function cleanupDOM(): void { originalGlobals = {} } } - -export async function loadHtmlFromHtml(key: keyof typeof CORPUS): Promise { - const entry = CORPUS[key] - if (!entry || entry.type !== 'html') { - throw new Error(`Invalid HTML corpus key: ${String(key)}`) - } - const htmlPath = path.join(__dirname, 'corpus', 'html', `${String(key)}.html`) - return await fs.readFile(htmlPath, 'utf-8') -} - -export async function setupHtmlDOM(key: keyof typeof CORPUS): Promise { - const html = await loadHtmlFromHtml(key) - const entry = CORPUS[key] - if (!entry || entry.type !== 'html') { - throw new Error(`Invalid HTML corpus key: ${String(key)}`) - } - const url = entry.url - const domGlobals = createDOMFromHar(html, url) - setupDOMFromHar(domGlobals) - return domGlobals -} - -export async function setupHarDOM(key: keyof typeof CORPUS): Promise { - const html = await loadHtmlFromHar(key) - const entry = CORPUS[key] - if (!entry || entry.type !== 'har') { - throw new Error(`Invalid HAR corpus key: ${String(key)}`) - } - const url = entry.url - const domGlobals = createDOMFromHar(html, url) - setupDOMFromHar(domGlobals) - return domGlobals -} - -export async function setupDOM(key: keyof typeof CORPUS): Promise { - const entry = CORPUS[key] - if (!entry) { - throw new Error(`Invalid corpus key: ${String(key)}`) - } - - if (entry.type === 'har') { - return await setupHarDOM(key) - } else if (entry.type === 'html') { - return await setupHtmlDOM(key) - } else { - throw new Error(`Unsupported corpus type: ${entry.type}`) - } -} diff --git a/browser-extension/tests/lib/enhancers/github.test.ts b/browser-extension/tests/lib/enhancers/github.test.ts index 7a93fde..7e47a14 100644 --- a/browser-extension/tests/lib/enhancers/github.test.ts +++ b/browser-extension/tests/lib/enhancers/github.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, using, usingHar } from '../../har-fixture' +import { describe, expect, forCorpus as withCorpus } from '../../corpus-fixture' // must import fixture **first** for mocks, the `expect` keeps biome from changing sort-order expect @@ -35,7 +35,7 @@ function enhancements(document: Document, window: Window) { } describe('github', () => { - usingHar('gh_pr').it('should create the correct spot object', async () => { + withCorpus('gh_pr').it('should create the correct spot object', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { @@ -70,7 +70,7 @@ describe('github', () => { ] `) }) - usingHar('gh_new_pr').it('should create the correct spot object', async () => { + withCorpus('gh_new_pr').it('should create the correct spot object', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { @@ -102,7 +102,7 @@ describe('github', () => { ] `) }) - usingHar('gh_issue').it('no enhancement on initial page load', async () => { + withCorpus('gh_issue').it('no enhancement on initial page load', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { @@ -112,7 +112,7 @@ describe('github', () => { ] `) }) - using('gh_issue_populated_comment').it('should create the correct spot object', async () => { + withCorpus('gh_issue_populated_comment').it('should create the correct spot object', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { @@ -147,7 +147,7 @@ describe('github', () => { ] `) }) - usingHar('gh_new_issue').it('should create the correct spot object', async () => { + withCorpus('gh_new_issue').it('should create the correct spot object', async () => { expect(enhancements(document, window)).toMatchInlineSnapshot(` [ { From 66f366600c3637e1b5e50bbef3f2fe6e1c765fd6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 20:37:11 -0700 Subject: [PATCH 10/11] Improve the `finish-wc` command. --- .claude/commands/finish-wc.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.claude/commands/finish-wc.md b/.claude/commands/finish-wc.md index b8021a7..7f8127c 100644 --- a/.claude/commands/finish-wc.md +++ b/.claude/commands/finish-wc.md @@ -1 +1,5 @@ -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 +- run `git status` to see the changes in the working copy +- **figure out the intent of the change** +- complete whatever tasks are necessary to complete this change +- make sure that `pnpm precommit` succeeds when you are done +- don't fix `precommit` by reverting the changes, it is more important to complete the initial change \ No newline at end of file From a72dd03c14336173e5f125c1db92d26e39ff25fc Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Sep 2025 20:40:54 -0700 Subject: [PATCH 11/11] Flatten the corpus into one folder. --- browser-extension/tests/corpus-utils.ts | 4 ++-- browser-extension/tests/corpus-view.ts | 4 ++-- browser-extension/tests/corpus/{har => }/gh_issue.har | 0 .../tests/corpus/{html => }/gh_issue_populated_comment.html | 0 browser-extension/tests/corpus/{har => }/gh_new_issue.har | 0 browser-extension/tests/corpus/{har => }/gh_new_pr.har | 0 browser-extension/tests/corpus/{har => }/gh_pr.har | 0 7 files changed, 4 insertions(+), 4 deletions(-) rename browser-extension/tests/corpus/{har => }/gh_issue.har (100%) rename browser-extension/tests/corpus/{html => }/gh_issue_populated_comment.html (100%) rename browser-extension/tests/corpus/{har => }/gh_new_issue.har (100%) rename browser-extension/tests/corpus/{har => }/gh_new_pr.har (100%) rename browser-extension/tests/corpus/{har => }/gh_pr.har (100%) diff --git a/browser-extension/tests/corpus-utils.ts b/browser-extension/tests/corpus-utils.ts index d2a9b47..9ad40a1 100644 --- a/browser-extension/tests/corpus-utils.ts +++ b/browser-extension/tests/corpus-utils.ts @@ -57,7 +57,7 @@ async function loadRootHtmlStringFromHar(key: keyof typeof CORPUS): Promise entry.request.url === url) @@ -72,7 +72,7 @@ async function loadHtmlStringFromHtml(key: keyof typeof CORPUS): Promise if (!entry || entry.type !== 'html') { throw new Error(`Invalid HTML corpus key: ${String(key)}`) } - const htmlPath = path.join(__dirname, 'corpus', 'html', `${String(key)}.html`) + const htmlPath = path.join(__dirname, 'corpus', `${String(key)}.html`) return await fs.readFile(htmlPath, 'utf-8') } diff --git a/browser-extension/tests/corpus-view.ts b/browser-extension/tests/corpus-view.ts index a2fdd29..b7fdd00 100644 --- a/browser-extension/tests/corpus-view.ts +++ b/browser-extension/tests/corpus-view.ts @@ -104,7 +104,7 @@ async function loadHar(key: string): Promise { return harCache.get(key)! } - const harPath = path.join(__dirname, 'corpus', 'har', `${key}.har`) + const harPath = path.join(__dirname, 'corpus', `${key}.har`) const harContent = await fs.readFile(harPath, 'utf-8') const harData = JSON.parse(harContent) harCache.set(key, harData) @@ -248,7 +248,7 @@ app.get('/corpus/:key/:mode(clean|gitcasso)', async (req, res) => { return res.send(html) } else if (entry.type === 'html') { // Handle HTML corpus - const htmlPath = path.join(__dirname, 'corpus', 'html', `${key}.html`) + const htmlPath = path.join(__dirname, 'corpus', `${key}.html`) let html = await fs.readFile(htmlPath, 'utf-8') // Strip CSP headers that might block our injected scripts diff --git a/browser-extension/tests/corpus/har/gh_issue.har b/browser-extension/tests/corpus/gh_issue.har similarity index 100% rename from browser-extension/tests/corpus/har/gh_issue.har rename to browser-extension/tests/corpus/gh_issue.har diff --git a/browser-extension/tests/corpus/html/gh_issue_populated_comment.html b/browser-extension/tests/corpus/gh_issue_populated_comment.html similarity index 100% rename from browser-extension/tests/corpus/html/gh_issue_populated_comment.html rename to browser-extension/tests/corpus/gh_issue_populated_comment.html diff --git a/browser-extension/tests/corpus/har/gh_new_issue.har b/browser-extension/tests/corpus/gh_new_issue.har similarity index 100% rename from browser-extension/tests/corpus/har/gh_new_issue.har rename to browser-extension/tests/corpus/gh_new_issue.har diff --git a/browser-extension/tests/corpus/har/gh_new_pr.har b/browser-extension/tests/corpus/gh_new_pr.har similarity index 100% rename from browser-extension/tests/corpus/har/gh_new_pr.har rename to browser-extension/tests/corpus/gh_new_pr.har diff --git a/browser-extension/tests/corpus/har/gh_pr.har b/browser-extension/tests/corpus/gh_pr.har similarity index 100% rename from browser-extension/tests/corpus/har/gh_pr.har rename to browser-extension/tests/corpus/gh_pr.har