|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What CodeLedger Is |
| 8 | + |
| 9 | +A **Manifest V3 browser extension** (Chrome + Firefox) that automatically commits solved DSA problems from LeetCode, GeeksForGeeks, and Codeforces to a user-owned GitHub repository. Backed by a **Cloudflare Worker** (Hono) that handles GitHub OAuth and serves the landing page. |
| 10 | + |
| 11 | +- **Domain:** `codeledger.vkrishna04.me` |
| 12 | +- **Auth worker:** `https://codeledger.vkrishna04.me/api` |
| 13 | +- **Extension root:** `src/` — this is the directory loaded unpacked in Chrome |
| 14 | +- **Stack:** Pure ES6 modules, no bundler, no transpiler. Preact + htm from CDN. Tailwind CSS for the compiled stylesheet only. |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Commands |
| 19 | + |
| 20 | +### Extension development |
| 21 | +```bash |
| 22 | +npm install |
| 23 | +npm run build:css # Tailwind → src/ui/styles/compiled.css (run after CSS changes) |
| 24 | +npm run build # CSS + dist packaging |
| 25 | +npm run watch # rebuild on file changes (dev mode) |
| 26 | +npm run lint # tsc --noEmit (type-check only, no transpile) |
| 27 | +``` |
| 28 | + |
| 29 | +Load the extension unpacked from `src/` in `chrome://extensions`. |
| 30 | + |
| 31 | +### Worker (Cloudflare) |
| 32 | +```bash |
| 33 | +cd worker && npm install |
| 34 | +npx wrangler dev # local dev (requires wrangler.toml with secrets) |
| 35 | +npx wrangler deploy # deploy to production |
| 36 | +cd .. && npm run deploy:worker # shorthand from root |
| 37 | +``` |
| 38 | + |
| 39 | +`worker/wrangler.toml` is git-ignored — create it from the template in `CODELEDGER_EXECUTION_GUIDE.md`. |
| 40 | + |
| 41 | +### Dev utilities |
| 42 | +```bash |
| 43 | +node dev/generate-manifest-domains.js # regenerates host_permissions from dom-selectors DOMAINS exports |
| 44 | +node dev/build-canonical-map.js # validate data/canonical-map.json against schema |
| 45 | +node dev/package-chrome.js # produce codeledger-chrome-vX.zip |
| 46 | +node dev/package-firefox.js # produce codeledger-firefox-vX.zip |
| 47 | +node dev/import-profile/leetcode-importer.js --github-token=TOKEN --repo=owner/repo |
| 48 | +node dev/import-profile/gfg-importer.js --github-token=TOKEN --repo=owner/repo |
| 49 | +``` |
| 50 | + |
| 51 | +### Smoke test (post-deploy) |
| 52 | +```bash |
| 53 | +curl -sf https://codeledger.vkrishna04.me/api/health |
| 54 | +``` |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## Architecture |
| 59 | + |
| 60 | +### Extension layers (all in `src/`) |
| 61 | + |
| 62 | +``` |
| 63 | +manifest.json |
| 64 | +├── background/service-worker.js — SW: init, event bus, handles problem:solved |
| 65 | +│ ├── git-engine.js — atomic GitHub Tree API commits |
| 66 | +│ ├── sync-engine.js — cross-device sync via repo index.json |
| 67 | +│ └── alarm-manager.js — chrome.alarms for reminders/sync |
| 68 | +├── content/handler-loader.js — matches hostname → dynamically imports platform handler |
| 69 | +│ ├── heartbeat.js — SW keepalive port |
| 70 | +│ └── presence-marker.js — injects #codeledger-present on landing page |
| 71 | +├── handlers/ |
| 72 | +│ ├── _base/BasePlatformHandler.js — safeQuery(), MutationObserver lifecycle |
| 73 | +│ ├── platforms/{leetcode,geeksforgeeks,codeforces}/index.js |
| 74 | +│ ├── ai/{gemini,openai,claude,deepseek,ollama}/index.js |
| 75 | +│ └── git/{github,gitlab,bitbucket}/index.js |
| 76 | +├── core/ |
| 77 | +│ ├── constants.js — SINGLE SOURCE OF TRUTH for all URLs, keys, storage key names |
| 78 | +│ ├── storage.js — unified storage abstraction (wraps browser-compat) |
| 79 | +│ ├── event-bus.js — typed pub/sub (problem:solved → service-worker) |
| 80 | +│ ├── canonical-mapper.js — resolves platform problem → canonical ID |
| 81 | +│ └── ai-prompts.js — prompt templates + normalizeAIPrompts() |
| 82 | +├── lib/ |
| 83 | +│ ├── browser-compat.js — THE ONLY FILE that uses chrome.* or browser.* |
| 84 | +│ └── debug.js — createDebugger() with console.bind() trick |
| 85 | +└── ui/components/SettingsSchema.js — schema-driven settings renderer (Preact + htm) |
| 86 | +``` |
| 87 | + |
| 88 | +### Data flow for a solve event |
| 89 | +1. Content script (`handler-loader.js`) → imports platform handler → calls `handler.init()` |
| 90 | +2. Platform handler detects accepted submission (DOM / GraphQL / REST) |
| 91 | +3. Fires `eventBus.emit("problem:solved", data)` → caught by service-worker |
| 92 | +4. SW saves to IndexedDB, optionally calls AI review, then calls `git-engine.js` |
| 93 | +5. `git-engine.js` calls GitHub Tree API for a single atomic commit |
| 94 | + |
| 95 | +### Cloudflare Worker (`worker/src/index.js`) |
| 96 | +- Built with **Hono** framework |
| 97 | +- Routes: `/api/health`, `/api/auth/github`, `/api/auth/github/callback`, `/api/webhook/github`, `/api/admin/canonical`, `/api/data/canonical-map.json` |
| 98 | +- Serves static landing page from `worker/public/` |
| 99 | +- OAuth callback posts `{ type: 'CODELEDGER_AUTH', provider, token }` — the extension listens for exactly this message type |
| 100 | + |
| 101 | +### Library / Web App (`src/library/`) |
| 102 | +- Shared HTML + Preact components used both inside the extension sidebar and at `codeledger.vkrishna04.me/library` |
| 103 | +- Auto-detects context: `IS_EXTENSION = !!chrome.runtime?.id` |
| 104 | +- Extension mode: reads IndexedDB; Web app mode: reads GitHub API via OAuth token |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +## Critical Rules |
| 109 | + |
| 110 | +### Never use `chrome.*` or `browser.*` directly |
| 111 | +All extension API calls must go through `src/lib/browser-compat.js`. This is the only file that touches those namespaces. |
| 112 | + |
| 113 | +### Never use `console.log` directly |
| 114 | +Use `createDebugger('HandlerName')` from `src/lib/debug.js`. The `.bind()` trick preserves caller file+line in DevTools. |
| 115 | + |
| 116 | +```js |
| 117 | +import { createDebugger } from '../../lib/debug.js'; |
| 118 | +const dbg = createDebugger('MyHandler'); |
| 119 | +dbg.log('message'); // shows at the correct source location in DevTools |
| 120 | +``` |
| 121 | + |
| 122 | +### Import paths from extension pages |
| 123 | +The extension root is `src/`. `chrome.runtime.getURL('handlers/...')` — no `src/` prefix in the path. This is a common bug source. |
| 124 | + |
| 125 | +### UI: Preact + htm, no build step |
| 126 | +All UI files import Preact and htm from `https://esm.sh`. No JSX. No webpack. No transpilation. Every UI file starts with: |
| 127 | +```js |
| 128 | +import { h, render } from '../../vendor/preact-bundle.js'; |
| 129 | +import { useState, useEffect } from '../../vendor/preact-bundle.js'; |
| 130 | +import { htm } from '../../vendor/preact-bundle.js'; |
| 131 | +const html = htm.bind(h); |
| 132 | +``` |
| 133 | +`src/vendor/preact-bundle.js` is a CDN re-export shim — all UI files import from this single path. |
| 134 | + |
| 135 | +### OAuth message contract |
| 136 | +Worker posts: `{ type: 'CODELEDGER_AUTH', provider: 'github', token: '...' }` |
| 137 | +Extension listens for exactly `data.type === 'CODELEDGER_AUTH'`. Any mismatch silently drops the token. |
| 138 | + |
| 139 | +### Token storage paths |
| 140 | +- OAuth tokens: `Storage.setAuthToken(provider, token)` → stored at `auth.tokens` |
| 141 | +- AI API keys: `Storage.setAIKeys(map)` → stored at `ai.keys` |
| 142 | +- Manual PAT: `settings['github_token']` |
| 143 | +- `GitHubHandler.getToken()` checks OAuth path first, then settings PAT — order matters. |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## Current State (as of CODELEDGER_EXECUTION_GUIDE.md) |
| 148 | + |
| 149 | +The execution guide defines 5 modules of fixes needed. Status of each: |
| 150 | + |
| 151 | +| Module | Problem | Files | |
| 152 | +| ------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | |
| 153 | +| M0 | vendor bundles need CDN re-export shims | `src/vendor/preact-bundle.js`, `src/vendor/chart-bundle.js` | |
| 154 | +| M1 | Worker: PKCS#1 key bug, wrong postMessage type, missing /api/health | `worker/src/index.js` | |
| 155 | +| M2 | `src/core/ai-prompts.js` does not exist → settings page crashes | CREATE `src/core/ai-prompts.js` | |
| 156 | +| M3 | handleOAuth saves to wrong storage; getToken() checks wrong order | `src/ui/components/SettingsSchema.js`, `src/handlers/git/github/index.js`, `src/background/service-worker.js` | |
| 157 | +| M4 | LeetCode: no debounce, double commits; manifest run_at wrong, WAR too narrow, handler-loader has extra `src/` prefix | `src/handlers/platforms/leetcode/index.js`, `src/manifest.json`, `src/content/handler-loader.js` | |
| 158 | +| M5 | `worker/public/config.json` has placeholder GitHub App slug | `worker/public/config.json` | |
| 159 | + |
| 160 | +Apply modules in order M0 → M5. Each has a VERIFY section that must pass before proceeding. |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Worker Secrets (Wrangler) |
| 165 | + |
| 166 | +| Secret name | Source | |
| 167 | +| ---------------------------------- | ------------------------------------------------------------ | |
| 168 | +| `CODELEDGER_GH_APP_PRIVATE_KEY` | PKCS#8 PEM file (convert PKCS#1 with `openssl pkcs8 -topk8`) | |
| 169 | +| `CODELEDGER_GH_APP_ID` | GitHub App numeric ID | |
| 170 | +| `CODELEDGER_GH_APP_CLIENT_ID` | GitHub App Client ID | |
| 171 | +| `CODELEDGER_GH_APP_CLIENT_SECRET` | GitHub App client secret | |
| 172 | +| `CODELEDGER_GH_APP_WEBHOOK_SECRET` | `openssl rand -hex 32` | |
| 173 | +| `CANONICAL_UPLOAD_TOKEN` | `openssl rand -hex 32` | |
| 174 | +| `SESSION_SECRET` | `openssl rand -hex 32` | |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## Adding a New Platform Handler |
| 179 | + |
| 180 | +1. Create `src/handlers/platforms/{name}/index.js` extending `BasePlatformHandler` |
| 181 | +2. Create `dom-selectors.js` with versioned `SELECTORS`, `LEGACY_SELECTORS`, and `DOMAINS` export |
| 182 | +3. Create `page-detector.js` with `detectPage()` and `isSolveCapablePage()` |
| 183 | +4. Add hostname match in `src/content/handler-loader.js` |
| 184 | +5. Run `node dev/generate-manifest-domains.js` to update `manifest.json` host_permissions |
| 185 | +6. See `docs/ADDING_PLATFORM_HANDLER.md` for full contract |
| 186 | + |
| 187 | +## Adding a New AI Provider |
| 188 | + |
| 189 | +1. Create `src/handlers/ai/{name}/index.js` extending `BaseAIHandler` |
| 190 | +2. Create `model-fetcher.js` that fetches live models (or static list for providers without a models endpoint) |
| 191 | +3. Add provider config to `CONSTANTS.AI_PROVIDERS` in `src/core/constants.js` |
| 192 | +4. Register settings schema in `src/handlers/init.js` |
| 193 | +5. Wire into `ModelSelector.js` `loadModels()` switch |
| 194 | + |
| 195 | +--- |
| 196 | + |
| 197 | +## Branch Strategy |
| 198 | + |
| 199 | +Use feature branches off `main`: |
| 200 | +- `fix/m0-vendor-bundles` — Module 0 vendor shims |
| 201 | +- `fix/m1-worker` — Worker OAuth + health endpoint |
| 202 | +- `fix/m2-ai-prompts` — Create ai-prompts.js |
| 203 | +- `fix/m3-oauth-wiring` — Token storage + getToken priority |
| 204 | +- `fix/m4-leetcode-dedup` — Debounce + manifest + handler-loader |
| 205 | +- `fix/m5-config` — Worker config.json slug |
| 206 | + |
| 207 | +Merge each back to `main` after its VERIFY section passes. |
0 commit comments