diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 4f71459..b4cb2cf 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -61,46 +61,178 @@ jobs: - name: Build extension run: bun run build - - name: Zip Extension - run: zip -r chrome-extension.zip dist/ - # Automatically increments the patch version (e.g., 1.0.0 -> 1.0.1) # and creates a release # Only increment patch version for non-major versions - name: Check existing tag id: check_tag + shell: bash run: | - current_version=$(node -p "require('./package.json').version") + set -euo pipefail # Exit on error, undefined vars, and pipe failures + + # Function to validate semver format + # Supports standard versions (x.y.z) and pre-release versions (x.y.z-beta.n) + validate_version() { + local version="$1" + if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then + echo "Error: Invalid version format: $version" + echo "Version must be in format: x.y.z or x.y.z-pre.n" + exit 1 + fi + } + + # Function to update version in JSON files + update_json_version() { + local file="$1" + local cur_ver="$2" + local new_ver="$3" + + if [[ ! -f "$file" ]]; then + echo "Error: File $file not found" + exit 1 + fi + + # Check if version field exists + if ! grep -q "\"version\":" "$file"; then + echo "Error: No version field found in $file" + exit 1 + fi + + # Backup file before modification + cp "$file" "${file}.bak" + + if ! sed -i "s/\"version\": \"$cur_ver\"/\"version\": \"$new_ver\"/" "$file"; then + echo "Error: Failed to update version in $file" + mv "${file}.bak" "$file" # Restore backup + exit 1 + fi + + rm "${file}.bak" # Remove backup if successful + } + + # Function to compare versions + version_gt() { + test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1" + } + + # Get current version with error handling + if [[ ! -f "package.json" ]]; then + echo "Error: package.json not found" + exit 1 + fi + + if ! current_version=$(node -p "try { require('./package.json').version } catch(e) { console.error(e); process.exit(1) }" 2>/dev/null); then + echo "Error: Failed to read version from package.json" + exit 1 + fi + + # Validate version format + validate_version "$current_version" + + # Get highest existing version with error handling + if ! highest_version=$(git ls-remote --tags origin | grep "refs/tags/chrome-extension-v" | sed 's/.*chrome-extension-v//' | sort -V | tail -n 1); then + echo "Warning: Failed to fetch remote tags, proceeding with caution" + fi + + # Prevent version downgrade + # Note: To downgrade versions, you need to manually: + # 1. Delete the higher version tag: git push origin :refs/tags/chrome-extension-v + # 2. Delete the corresponding GitHub release + if [ ! -z "$highest_version" ] && version_gt "$highest_version" "$current_version"; then + echo "Error: Version downgrade not allowed. Current: $current_version, Highest: $highest_version" + exit 1 + fi + + # Handle pre-release versions + if [[ "$current_version" == *"-"* ]]; then + echo "Pre-release version detected: $current_version" + + # Extract base version and pre-release parts + base_version=${current_version%%-*} + pre_release=${current_version#*-} + pre_type=${pre_release%%.*} + pre_num=${pre_release#*.} + + # Check if same pre-release version exists + if git ls-remote --tags origin | grep -q "refs/tags/chrome-extension-v$current_version"; then + # Increment pre-release number + new_pre_num=$((pre_num + 1)) + new_version="${base_version}-${pre_type}.${new_pre_num}" + echo "Incrementing pre-release: $current_version -> $new_version" + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "version_changed=true" >> $GITHUB_OUTPUT + else + echo "Using current pre-release version: $current_version" + echo "version=$current_version" >> $GITHUB_OUTPUT + echo "version_changed=false" >> $GITHUB_OUTPUT + fi + exit 0 + fi + + # Handle major versions (x.0.0) if [[ "$current_version" =~ ^[0-9]+\.0\.0$ ]]; then - # Don't increment major versions (x.0.0) - echo "version=$current_version" >> $GITHUB_OUTPUT - echo "version_changed=false" >> $GITHUB_OUTPUT - elif git ls-remote --tags origin | grep -q "refs/tags/v$current_version"; then - # If tag exists and it's not a major version, increment patch + echo "Major version detected: $current_version" + + # Check if major version tag exists + if git ls-remote --tags origin | grep -q "refs/tags/chrome-extension-v$current_version"; then + # Increment patch version like normal + new_version="${current_version%.*}.1" # x.0.0 -> x.0.1 + echo "Major version exists, incrementing patch: $current_version -> $new_version" + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "version_changed=true" >> $GITHUB_OUTPUT + else + echo "Using new major version: $current_version" + echo "version=$current_version" >> $GITHUB_OUTPUT + echo "version_changed=false" >> $GITHUB_OUTPUT + fi + exit 0 + fi + + # Check if tag exists + if git ls-remote --tags origin | grep -q "refs/tags/chrome-extension-v$current_version"; then + echo "Tag exists, incrementing patch version" + + # Split version into components IFS='.' read -r major minor patch <<< "$current_version" new_version="$major.$minor.$((patch + 1))" - echo "version=$new_version" >> $GITHUB_OUTPUT - echo "version_changed=true" >> $GITHUB_OUTPUT - # Update package.json with new version - sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" package.json - # Update manifest.json with new version - sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" public/manifest.json + validate_version "$new_version" + + echo "Updating version from $current_version to $new_version" + + # Update version in files + update_json_version "package.json" "$current_version" "$new_version" + update_json_version "public/manifest.json" "$current_version" "$new_version" + + # Configure git for fork workflow git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" + + # Commit and push changes git add package.json public/manifest.json git commit -m "chore: bump version to $new_version [skip ci]" - git push + + if ! git push; then + echo "Error: Failed to push changes" + exit 1 + fi + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "version_changed=true" >> $GITHUB_OUTPUT else + echo "Using current version: $current_version" echo "version=$current_version" >> $GITHUB_OUTPUT echo "version_changed=false" >> $GITHUB_OUTPUT fi + + - name: Zip Extension + run: zip -r thinking-claude-chrome-extension-v${{ steps.check_tag.outputs.version }}.zip dist/ + - name: Create Release if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: softprops/action-gh-release@v1 with: name: Chrome Extension v${{ steps.check_tag.outputs.version }} - tag_name: v${{ steps.check_tag.outputs.version }} - files: extensions/chrome/chrome-extension.zip + tag_name: chrome-extension-v${{ steps.check_tag.outputs.version }} + files: extensions/chrome/thinking-claude-chrome-extension-v${{ steps.check_tag.outputs.version }}.zip generate_release_notes: true token: ${{ secrets.GITHUB_TOKEN }} fail_on_unmatched_files: true diff --git a/extensions/changelog.md b/extensions/changelog.md index dc57f7c..ec99043 100644 --- a/extensions/changelog.md +++ b/extensions/changelog.md @@ -1,44 +1,95 @@ -## Changelog of the extensions + -### feat/fix/ref: - 11/27/2024 - @lumpinif +# Changelog of the extensions -#### Performance & Code Quality +## ci: - 11/30/2024 - @lumpinif + +### Chrome Extension CI Improvements + +- Enhanced version management in GitHub Actions workflow + - Added robust semver validation supporting x.y.z and pre-release versions + - Implemented automatic patch version increment for existing versions + - Added support for pre-release versions (beta) with auto-increment + - Added version downgrade prevention with clear error messages + - Improved error handling for file operations and git commands + - Added backup mechanism for safe version updates + - Enhanced logging for better debugging and transparency + +### File Operations + +- Added safe JSON file updates with backup mechanism +- Improved handling of package.json and manifest.json version updates +- Added validation for version field existence in JSON files + +## fix: - 11/30/2024 - @lumpinif + +### Feature Cleanup & Navigation + +- Fixed thinking block toggle not working when navigating between pages +- Improved cleanup and reinitialization of features during page navigation +- Added proper cleanup for mutation observer to prevent memory leaks +- Added background script for better navigation handling between pages + +### Code Quality + +- Removed debug console logs while keeping error logs for better production monitoring +- Added [TC] prefix to error messages for better identification +- Improved error handling and cleanup process + +## feat/fix/ref: - 11/28/2024 - @lumpinif + +### Architecture + +- Implement feature management architecture for better extensibility + - Add ExtensionManager for high-level orchestration + - Create FeatureManager for feature lifecycle + - Convert TCThinkingBlock to new architecture + - Add configurable MutationObserverService + - Remove singleton pattern usage +- Improve code organization and modularity + - Clear separation of concerns + - Dependency injection pattern + - Standardized feature lifecycle + +## feat/fix/ref: - 11/27/2024 - @lumpinif + +### Performance & Code Quality - Extremely streamline code structure and implementation approach - Much optimized performance - Streamline and organize code for thinking-block implementation -#### Bug Fixes +### Bug Fixes - Fix flash of unstyled content (FOUC) - Fix stutter when submitting new replies - Fix FOUC and streaming issues for thinking-block implementation -#### UI Improvements +### UI Improvements - Update chevron icon with transition effect -#### Architecture +### Architecture - Implement ultimate approach with simplest and most effective implementation after experimentation -### fix: - 11/17/2024 - @lumpinif +## fix: - 11/17/2024 - @lumpinif -#### Observer Management and Memory Leak Prevention +### Observer Management and Memory Leak Prevention - Added observer tracking using Set to manage all MutationObservers - Added cleanup on element removal to prevent dangling observers - Added global cleanup on window unload - Added observer cleanup when observed elements are removed from DOM -#### Code Quality +### Code Quality - Fixed code formatting and linting issues flagged by Biome -#### Development Setup +### Development Setup - Added .vscode settings with Biome extension recommendation -#### Platform Updates +### Platform Updates - Updated code in both Chrome and Firefox extensions diff --git a/extensions/chrome/CHANGELOG.md b/extensions/chrome/CHANGELOG.md deleted file mode 100644 index b639290..0000000 --- a/extensions/chrome/CHANGELOG.md +++ /dev/null @@ -1,35 +0,0 @@ -# Changelog - -## 2024-11-23 - -### Important Notice 🔄 - -The original Chrome extension (now referred to as `chrome_v0`) has been deprecated and its development has been discontinued. This changelog tracks the new rewritten version (`chrome`) which offers improved architecture, better performance, and enhanced maintainability. - -### Changed - -- Complete rewrite of the extension with modern tech stack -- Improved CI/CD pipeline with automatic version syncing -- Added git pull to post-commit hook for auto-sync - -## 2024-11-22 - -### Added - -- Initial project structure and configuration setup for complete rewrite -- Deprecated old extension (`chrome_v0`) and started fresh development -- GitHub Actions workflow for Chrome extension -- Husky git hooks for code quality -- Automatic version bumping and release workflow -- Automatic GitHub release creation - -### Changed - -- Reorganized extension directory structure -- Improved development environment configuration - -### Technical - -- Set up manifest.json with required permissions and content scripts -- Configured extension icons -- Implemented storage permission for future feature development diff --git a/extensions/chrome/package.json b/extensions/chrome/package.json index 8548e12..bb5026b 100644 --- a/extensions/chrome/package.json +++ b/extensions/chrome/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "3.1.3", + "version": "3.1.4", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome/public/manifest.json b/extensions/chrome/public/manifest.json index 498d1e9..7754eb9 100644 --- a/extensions/chrome/public/manifest.json +++ b/extensions/chrome/public/manifest.json @@ -1,8 +1,11 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "3.1.3", + "version": "3.1.4", "description": "Chrome extension for letting Claude think like a real human", + "background": { + "service_worker": "background.js" + }, "content_scripts": [ { "matches": ["https://*.claude.ai/*"], @@ -23,5 +26,5 @@ }, "default_title": "Thinking Claude" }, - "permissions": ["storage"] + "permissions": ["storage", "webNavigation", "tabs"] } diff --git a/extensions/chrome/src/background/index.ts b/extensions/chrome/src/background/index.ts new file mode 100644 index 0000000..7331e34 --- /dev/null +++ b/extensions/chrome/src/background/index.ts @@ -0,0 +1,45 @@ +// Message types for type safety +type RouteChangeMessage = { + type: "ROUTE_CHANGED" + url: string +} + +// Track the last URL to prevent duplicate notifications +let lastUrl: string | null = null + +// Function to notify content script about route changes +function notifyRouteChange(tabId: number, url: string) { + if (url === lastUrl) { + return // Skip if URL hasn't changed + } + + lastUrl = url + chrome.tabs.sendMessage( + tabId, + { type: "ROUTE_CHANGED", url } as RouteChangeMessage, + // Chrome handles connection errors automatically + () => chrome.runtime.lastError // Acknowledge any error + ) +} + +// Listen for tab updates (handles both regular navigation and SPA changes) +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + // Only process when URL changes and page is complete + if (changeInfo.url || (changeInfo.status === "complete" && tab.url)) { + const url = changeInfo.url || tab.url + if (url?.includes("claude.ai")) { + notifyRouteChange(tabId, url) + } + } +}) + +// Listen for history state updates (catches some SPA navigations that tabs API might miss) +chrome.webNavigation.onHistoryStateUpdated.addListener((details) => { + // Only handle main frame + if (details.frameId === 0 && details.url.includes("claude.ai")) { + notifyRouteChange(details.tabId, details.url) + } +}) + +// Log that service worker has started +console.log("[TC] Background service worker started") diff --git a/extensions/chrome/src/content/index.ts b/extensions/chrome/src/content/index.ts index bd2e857..d1df735 100644 --- a/extensions/chrome/src/content/index.ts +++ b/extensions/chrome/src/content/index.ts @@ -2,5 +2,18 @@ import "@/styles/globals.css" import { ExtensionManager } from "./v3/managers/extension-manager" +// Create a single instance of ExtensionManager const extensionManager = new ExtensionManager() +// Initialize on first load extensionManager.initialize() + +// Listen for route changes from background script +chrome.runtime.onMessage.addListener( + (message: { type: string; url: string }) => { + if (message.type === "ROUTE_CHANGED") { + // Reinitialize the extension for the new route + extensionManager.cleanup() + extensionManager.initialize() + } + } +) diff --git a/extensions/chrome/src/content/v3/managers/extension-manager.ts b/extensions/chrome/src/content/v3/managers/extension-manager.ts index 7508204..1c32103 100644 --- a/extensions/chrome/src/content/v3/managers/extension-manager.ts +++ b/extensions/chrome/src/content/v3/managers/extension-manager.ts @@ -14,9 +14,6 @@ export class ExtensionManager { constructor() { this.mutationObserver = new MutationObserverService() this.featureManager = new FeatureManager() - - this.registerFeatures() - this.setupNavigationListener() } /** @@ -38,28 +35,20 @@ export class ExtensionManager { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { + this.registerFeatures() this.featureManager.initialize() }) } else { + this.registerFeatures() this.featureManager.initialize() } } - /** - * Set up listener for navigation events - */ - private setupNavigationListener(): void { - chrome.runtime.onMessage.addListener((message) => { - if (message.type === "NAVIGATION") { - this.featureManager.cleanup() - } - }) - } - /** * Clean up all features and services */ cleanup(): void { this.featureManager.cleanup() + this.mutationObserver.cleanup() } } diff --git a/extensions/chrome/src/content/v3/managers/feature-manager.ts b/extensions/chrome/src/content/v3/managers/feature-manager.ts index 3df0ac5..8e99581 100644 --- a/extensions/chrome/src/content/v3/managers/feature-manager.ts +++ b/extensions/chrome/src/content/v3/managers/feature-manager.ts @@ -11,7 +11,7 @@ export class FeatureManager { */ register(feature: Feature): void { if (this.features.has(feature.id)) { - throw new Error(`Feature with id ${feature.id} already exists`) + throw new Error(`[TC] Feature with id ${feature.id} already exists`) } this.features.set(feature.id, feature) } @@ -27,7 +27,7 @@ export class FeatureManager { this.cleanupFunctions.set(id, cleanup) } } catch (error) { - console.error(`Failed to initialize feature ${id}:`, error) + console.error(`[TC] Failed to initialize feature ${id}:`, error) } }) } @@ -36,11 +36,16 @@ export class FeatureManager { * Clean up all features */ cleanup(): void { + // Remove data-tc-processed attributes from thinking block controls + document.querySelectorAll("[data-tc-processed]").forEach((element) => { + element.removeAttribute("data-tc-processed") + }) + this.cleanupFunctions.forEach((cleanup, id) => { try { cleanup() } catch (error) { - console.error(`Failed to cleanup feature ${id}:`, error) + console.error(`[TC] Failed to cleanup feature ${id}:`, error) } }) this.cleanupFunctions.clear() diff --git a/extensions/chrome/src/services/mutation-observer.ts b/extensions/chrome/src/services/mutation-observer.ts index 0380e71..6c9f9f3 100644 --- a/extensions/chrome/src/services/mutation-observer.ts +++ b/extensions/chrome/src/services/mutation-observer.ts @@ -55,7 +55,7 @@ export class MutationObserverService { }) } - /* service-level cleanup but we don't usually need this */ + /* service-level cleanup for cleaning the observer */ cleanup() { // 1. Disconnect the MutationObserver this.observer?.disconnect() diff --git a/extensions/chrome/test.txt b/extensions/chrome/test.txt index 0196648..e1b08d0 100644 --- a/extensions/chrome/test.txt +++ b/extensions/chrome/test.txt @@ -1,2 +1,2 @@ Version Bumping Test File -v3.1.1 \ No newline at end of file +v3.1.4 \ No newline at end of file diff --git a/extensions/chrome/webpack/webpack.common.js b/extensions/chrome/webpack/webpack.common.js index 1cfcc23..c5a9f6b 100644 --- a/extensions/chrome/webpack/webpack.common.js +++ b/extensions/chrome/webpack/webpack.common.js @@ -9,7 +9,7 @@ const __dirname = path.dirname(__filename) export default { entry: { // popup: path.resolve(__dirname, '..', 'src', 'popup', 'index.tsx'), //popup is not being developed yet - // background: path.resolve(__dirname, '..', 'src', 'background', 'index.ts'), //background is not being developed yet + background: path.resolve(__dirname, "..", "src", "background", "index.ts"), content: path.resolve(__dirname, "..", "src", "content", "index.ts"), }, module: {