diff --git a/.github/workflows/promote-staging.yml b/.github/workflows/promote-staging.yml index 3cb89f3..57d0a4c 100644 --- a/.github/workflows/promote-staging.yml +++ b/.github/workflows/promote-staging.yml @@ -40,21 +40,39 @@ jobs: echo "Commits to be promoted:" echo "$COMMITS" - # Skip if only chore/docs commits - NON_CHORE=$(echo "$COMMITS" | grep -vE "^(chore|docs|ci|style|refactor)(\(.+\))?:" || true) - if [ -z "$NON_CHORE" ]; then - echo "Only chore/docs commits. Skipping version bump." + # Check if any extension files actually changed + EXTENSION_FILES=$(git diff --name-only origin/main..staging -- extension/ || true) + echo "Extension files changed:" + echo "$EXTENSION_FILES" + + if [ -z "$EXTENSION_FILES" ]; then + echo "No extension files changed. Skipping version bump." echo "should_bump=false" >> $GITHUB_OUTPUT exit 0 fi - # Determine bump type from conventional commits + # Filter out non-extension commits (chore, docs, landing page changes) + # These don't trigger version bumps + EXTENSION_COMMITS=$(echo "$COMMITS" | grep -vE "^(chore|docs|ci|style|refactor)(\(.+\))?:" | grep -vE "^(feat|fix)\(landing\):" || true) + + if [ -z "$EXTENSION_COMMITS" ]; then + echo "Only chore/docs/landing commits. Skipping version bump." + echo "should_bump=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Extension-relevant commits:" + echo "$EXTENSION_COMMITS" + + # Determine bump type from extension commits only BUMP="patch" # Default - if echo "$COMMITS" | grep -qE "^feat(\(.+\))?!:|BREAKING CHANGE:"; then + # Check for breaking changes (exclude landing scope) + if echo "$EXTENSION_COMMITS" | grep -qE "^feat(\([^)]*\))?!:|BREAKING CHANGE:"; then BUMP="major" echo "Found breaking change - major bump" - elif echo "$COMMITS" | grep -qE "^feat(\(.+\))?:"; then + # Check for features (exclude landing scope) + elif echo "$EXTENSION_COMMITS" | grep -qE "^feat(\([^)]*\))?:"; then BUMP="minor" echo "Found feat commit - minor bump" else @@ -99,7 +117,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add extension/manifest.json - git commit -m "chore: bump version to $NEW_VERSION [skip ci]" + git commit -m "chore: bump version to $NEW_VERSION" git push origin staging echo "Bumped version to $NEW_VERSION on staging" diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index be4e5e9..988adfe 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -56,11 +56,12 @@ jobs: git push origin "$TAG" # Get recent commits for release notes (from previous tag) + # Filter out: merge commits, chore, docs, ci, style, refactor LAST_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") if [ -n "$LAST_TAG" ]; then - NOTES=$(git log $LAST_TAG..HEAD --pretty=format:"- %s" | grep -vE "^- chore:" | head -20) + NOTES=$(git log $LAST_TAG..HEAD --pretty=format:"- %s" | grep -vE "^- (Merge|chore|docs|ci|style|refactor)" | head -20) else - NOTES=$(git log --pretty=format:"- %s" -10 | grep -vE "^- chore:") + NOTES=$(git log --pretty=format:"- %s" -10 | grep -vE "^- (Merge|chore|docs|ci|style|refactor)") fi # Write release notes to temp file to avoid YAML parsing issues @@ -111,3 +112,23 @@ jobs: client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} publish: true + + sync-staging: + needs: [tag-and-release, build-and-publish] + if: always() && needs.tag-and-release.result == 'success' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: staging + fetch-depth: 0 + token: ${{ secrets.STAGING_PAT }} + + - name: Sync staging with main + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch origin main + git merge origin/main -m "chore: sync staging with main after release" + git push origin staging + echo "✅ Staging synced with main" diff --git a/CLAUDE.md b/CLAUDE.md index f1a62a3..671b1ec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -183,15 +183,32 @@ gh pr create --base staging --title "feat: add new feature" --body "Description" ### Conventional Commits (Auto-Versioning) -Version bumps are automatic based on commit message prefixes: +Version bumps are automatic based on commit message prefixes. + +**CI enforces conventional commit format** - PRs with invalid messages will be rejected. | Prefix | Version Bump | Example | |--------|--------------|---------| | `fix:` | Patch (0.0.X) | `fix: resolve scroll issue` | | `feat:` | Minor (0.X.0) | `feat: add dark mode` | | `feat!:` | Major (X.0.0) | `feat!: redesign entire UI` | -| `chore:` | No bump | `chore: update docs` | +| `chore:` | No bump | `chore: update deps` | | `docs:` | No bump | `docs: fix typo in README` | +| `ci:` | No bump | `ci: update workflow` | +| `style:` | No bump | `style: format code` | +| `refactor:` | No bump | `refactor: extract helper` | +| `test:` | No bump | `test: add unit tests` | + +### Landing Page Changes + +Use `(landing)` scope for landing page changes - **these don't trigger Chrome extension releases**: + +| Commit | Version Bump | Releases Extension? | +|--------|--------------|---------------------| +| `feat(landing): redesign hero` | None | No | +| `fix(landing): mobile layout` | None | No | +| `feat: add dark mode` | Minor | Yes | +| `fix: resolve quiz crash` | Patch | Yes | ### CI/CD Pipeline @@ -220,16 +237,36 @@ feature/* ──PR──→ staging ────────────── 1. Create `hotfix/*` branch from main 2. Make fix AND bump version in `extension/manifest.json` manually 3. PR to main → CI validates version was bumped -4. Merge → auto-publishes like normal release +4. Merge → auto-publishes like normal release + auto-syncs staging + +**Manual promotion (don't want to wait for 6pm):** +1. Go to: Actions → "Promote Staging to Main" +2. Click "Run workflow" → Select branch: `staging` → Run +3. Same process as daily, just triggered manually ### Workflows | File | Trigger | Purpose | |------|---------|---------| -| `ci.yml` | PR to main/staging | Validate branch, run tests, check hotfix version | -| `publish-extension.yml` | Push to main | Tag, release, publish (reads version from manifest) | +| `ci.yml` | PR to main/staging | Validate branch, lint commits, run tests, check hotfix version | +| `publish-extension.yml` | Push to main | Tag, release, publish, sync staging with main | | `promote-staging.yml` | Daily 6pm EST / manual | Bump version on staging, PR to main | +**CI checks (`ci.yml`):** +- `check-branch-name` - Validates branch naming (feature/*, fix/* → staging; staging, hotfix/* → main) +- `check-commit-messages` - Enforces conventional commit format (skipped for staging → main) +- `check-hotfix-version` - Ensures hotfix PRs include manual version bump +- `run-tests` - Runs `npm test` + +**Secrets required:** +| Secret | Purpose | +|--------|---------| +| `CHROME_EXTENSION_ID` | Chrome Web Store extension ID | +| `CHROME_CLIENT_ID` | OAuth client ID | +| `CHROME_CLIENT_SECRET` | OAuth client secret | +| `CHROME_REFRESH_TOKEN` | OAuth refresh token | +| `STAGING_PAT` | Personal Access Token for pushing to staging (bypasses branch protection) | + --- ## Architecture Principles @@ -325,9 +362,13 @@ npm test -- --watch # Watch mode - **Re-runs use original commit code** - Push fix to new branch, don't just re-run - **Release creation is idempotent** - Skips if tag already exists - **Chrome Store review** - First public publish needs manual approval -- **Version bumped on staging** - Not main (main is protected, staging isn't) +- **Version bumped on staging** - Uses STAGING_PAT to bypass branch protection - **Hotfixes need manual version bump** - CI checks that hotfix PRs bump the version - **promote-staging calculates version** - From commits since last tag, highest bump wins +- **Only extension/ changes trigger release** - Changes to README, tests, scripts alone won't publish +- **`[skip ci]` hangs required checks** - Never use in commits that will be in PRs +- **STAGING_PAT expires** - Fine-grained PAT needs rotation (check expiration date) +- **Landing page changes use `(landing)` scope** - Won't trigger extension release --- diff --git a/extension/manifest.json b/extension/manifest.json index 3111362..b8a0aa8 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "PatternPulse - LeetCode Pattern Trainer", - "version": "0.10.0", + "version": "0.182.0", "description": "Train your pattern recognition! Quiz yourself on algorithmic patterns before seeing LeetCode hints, tags, and solutions.", "icons": { "16": "icons/icon-16.png", diff --git a/landing_page/app/page.tsx b/landing_page/app/page.tsx index 591c8f9..660f13e 100644 --- a/landing_page/app/page.tsx +++ b/landing_page/app/page.tsx @@ -1,44 +1,13 @@ 'use client' import { motion } from 'framer-motion' -import { BarChart3, Chrome, Sparkles, Target, Brain, Mail, CheckCircle, Loader2 } from 'lucide-react' +import { BarChart3, Chrome, Sparkles, Target, Brain, Shield, Zap } from 'lucide-react' import { useState } from 'react' -// TODO: Replace with your actual Chrome Web Store URL after publishing -const CHROME_STORE_URL = 'https://chrome.google.com/webstore/detail/patternpulse/YOUR_EXTENSION_ID' - -// Google Form configuration -const GOOGLE_FORM_ACTION = 'https://docs.google.com/forms/d/e/1FAIpQLScSMueGAnRqxrQuC9ziNgi72TlOM8uh-fSYVZS0J2SFw12q1A/formResponse' -const GOOGLE_FORM_EMAIL_ENTRY = 'entry.1669615047' +const CHROME_STORE_URL = 'https://chromewebstore.google.com/detail/patternpulse-leetcode-pat/mnfiladjdapefilfdimnombhfanpmepg' export default function Home() { const [openFaq, setOpenFaq] = useState(null) - const [email, setEmail] = useState('') - const [submitStatus, setSubmitStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle') - - const handleEmailSubmit = async (e: React.FormEvent) => { - e.preventDefault() - if (!email || submitStatus === 'loading') return - - setSubmitStatus('loading') - - try { - // Submit to Google Form - const formData = new FormData() - formData.append(GOOGLE_FORM_EMAIL_ENTRY, email) - - await fetch(GOOGLE_FORM_ACTION, { - method: 'POST', - mode: 'no-cors', - body: formData - }) - - setSubmitStatus('success') - setEmail('') - } catch (error) { - setSubmitStatus('error') - } - } const faqs = [ { @@ -47,7 +16,7 @@ export default function Home() { }, { q: 'What problems are covered?', - a: 'LeetCode\'s first 100 problems, NeetCode 150, and LeetCode 75. We\'re continuously adding more based on feedback.' + a: 'Currently 256 problems including NeetCode 150, LeetCode 75, and the first 100 LeetCode problems. We\'re actively adding more every week — the goal is full LeetCode coverage.' }, { q: 'How do the AI hints work?', @@ -78,10 +47,10 @@ export default function Home() { initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.5, delay: 0.2 }} - className="inline-flex items-center gap-2 px-4 py-2 bg-electric-purple/10 border border-electric-purple/30 rounded-full text-electric-purple text-sm font-medium mb-6" + className="inline-flex items-center gap-2 px-4 py-2 bg-emerald-500/10 border border-emerald-500/30 rounded-full text-emerald-400 text-sm font-medium mb-6" > - - Launching Soon — 100% Free + + Free Chrome Extension

@@ -96,56 +65,36 @@ export default function Home() { Stop memorizing solutions. Learn to identify the right approach before you start coding — just like in real interviews.

- {/* Email Signup Form */} -
- {submitStatus === 'success' ? ( - - - You're on the list! We'll email you when it's live. - - ) : ( -
-
- - setEmail(e.target.value)} - required - className="w-full pl-12 pr-4 py-4 bg-gray-900/80 border border-slate-700 rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-electric-purple transition-colors" - /> -
- - {submitStatus === 'loading' ? ( - - ) : ( - <> - - Get Early Access - - )} - -
- )} - {submitStatus === 'error' && ( -

Something went wrong. Please try again.

- )} + {/* Primary CTA */} +
+ + + Add to Chrome — Free +
-

- 100% free — Supports NeetCode 150 and LeetCode 75 with AI-powered hints -

+ {/* Trust signals */} +
+
+ + No account required +
+
+ + Works instantly +
+
+ 256+ + problems & growing +
+
@@ -288,9 +237,9 @@ export default function Home() {
-

Popular Problem Lists

+

256+ Problems & Growing

-

NeetCode 150, LeetCode 75, and the first 100 LeetCode problems. Continuously adding more.

+

NeetCode 150, LeetCode 75, and more. New problems added weekly — working toward full LeetCode coverage.

- {/* Email CTA Section */} + {/* Bottom CTA Section */}

- Get notified when PatternPulse launches. It's completely free. + Start training your pattern recognition today. It takes 10 seconds to install.

- {submitStatus === 'success' ? ( -
- - You're on the list! -
- ) : ( -
-
- - setEmail(e.target.value)} - required - className="w-full pl-12 pr-4 py-4 bg-gray-900/80 border border-slate-700 rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-electric-purple transition-colors" - /> -
- - {submitStatus === 'loading' ? ( - - ) : ( - 'Notify Me' - )} - -
- )} + + + Add to Chrome — Free +

- Works on any Chromium browser (Chrome, Edge, Brave, Arc) + Works on Chrome, Edge, Brave, Arc, and any Chromium browser