diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..5cd280c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,8 @@
+## Codebase Overview
+
+Gecko is a cross-browser (Chrome/Firefox) Manifest V3 extension that automates discovery of Client-Side Path Traversals (CSPT) by intercepting HTTP requests and matching URL-derived values against request paths. Findings are surfaced in a DevTools panel with search, filtering, and export capabilities.
+
+**Stack**: TypeScript, React 18, Tailwind CSS 3, Headless UI, Webpack 5, webextension-polyfill
+**Structure**: Service worker (`src/sw/`) handles detection, React UI split between DevTools panel (`src/pages/panel.tsx`) and popup settings (`src/pages/popup.tsx`), shared types/constants in `src/shared/`, reusable UI components in `src/components/ui/`. `browser.storage.local` is the single shared state bus between all contexts.
+
+For detailed architecture, see [docs/CODEBASE_MAP.md](docs/CODEBASE_MAP.md).
diff --git a/docs/CODEBASE_MAP.md b/docs/CODEBASE_MAP.md
new file mode 100644
index 0000000..7cb2248
--- /dev/null
+++ b/docs/CODEBASE_MAP.md
@@ -0,0 +1,401 @@
+---
+last_mapped: 2026-04-13T20:09:39Z
+total_files: 35
+total_tokens: 13029
+---
+
+# Codebase Map
+
+> Auto-generated by Cartographer. Last mapped: 2026-04-13T20:09:39Z
+
+## System Overview
+
+Gecko is a cross-browser (Chrome/Firefox) Manifest V3 extension that automates discovery of **Client-Side Path Traversals (CSPT)**. It intercepts outbound HTTP requests via a service worker, extracts "sources" (query parameter values, path segments, null/undefined literals) from the active tab's URL, and checks whether any source value appears in the path of the intercepted request. Matches are called **findings** and are surfaced in a DevTools panel.
+
+```mermaid
+graph TB
+ subgraph Browser
+ Tab[Active Tab URL]
+ Requests[Outbound HTTP Requests]
+ end
+ subgraph Extension
+ SW[Service Worker
sw.ts]
+ Storage[(browser.storage.local)]
+ Panel[DevTools Panel
panel.tsx]
+ Popup[Popup Settings
popup.tsx]
+ DevTools[DevTools Page
devtools.js]
+ end
+ Tab -->|"urlToSources()"| SW
+ Requests -->|"onBeforeRequest"| SW
+ SW -->|"storeFinding()"| Storage
+ SW -->|"badge count"| Browser
+ Storage -->|"onChanged listener"| Panel
+ Popup -->|"write settings"| Storage
+ Storage -->|"onChanged listener"| SW
+ DevTools -->|"panels.create()"| Panel
+```
+
+## Directory Structure
+
+```
+gecko/
+├── public/ # Static assets copied to dist/
+│ ├── icons/ # Extension icons (48px, 96px)
+│ ├── js/
+│ │ └── devtools.js # Hand-authored DevTools panel registration (not bundled)
+│ ├── devtools.html # DevTools page entry point
+│ ├── manifest-chrome.json # Chrome MV3 manifest
+│ ├── manifest-ff.json # Firefox MV3 manifest
+│ ├── panel.html # DevTools panel React shell
+│ └── popup.html # Browser action popup React shell
+├── src/
+│ ├── components/
+│ │ ├── ui/ # Stateless presentation components
+│ │ │ ├── empty-findings.tsx # Empty state placeholder
+│ │ │ ├── highlithed-text.tsx# Text highlighting with URL awareness (filename typo)
+│ │ │ ├── info-alert.tsx # Blue informational alert box
+│ │ │ ├── search-input.tsx # Reusable search input with icon
+│ │ │ └── toggle.tsx # Accessible on/off switch (Headless UI)
+│ │ ├── finding-data-display.tsx # Detail view for a single finding
+│ │ ├── finding-drawer.tsx # Slide-over drawer (Headless UI Dialog)
+│ │ ├── findings-context.tsx # React Context: state, filtering, storage sync
+│ │ ├── findings-search.tsx # Simple + advanced search bar
+│ │ └── findings-table.tsx # Main findings list with export/clear
+│ ├── pages/
+│ │ ├── panel.tsx # DevTools panel entry point (Webpack: panel.js)
+│ │ └── popup.tsx # Popup settings entry point (Webpack: popup.js)
+│ ├── shared/
+│ │ ├── constants.ts # Default settings object
+│ │ └── types.ts # All TypeScript interfaces and enums
+│ ├── sw/
+│ │ └── sw.ts # Service worker: CSPT detection engine
+│ └── tailwind/
+│ └── styles.css # Tailwind CSS entry (@tailwind directives)
+├── tests/
+│ ├── index.html # Manual test: query-value-to-path CSPT
+│ └── test/
+│ └── path.html # Manual test: path-to-path CSPT
+├── Dockerfile # Node 20 Alpine build image
+├── build-docker.sh # Docker build helper (chrome|ff)
+├── package.json # Scripts, dependencies, metadata
+├── postcss.config.js # Tailwind + autoprefixer
+├── tailwind.config.js # Content paths, custom danger color
+├── tsconfig.json # TypeScript config (IDE only, not used by webpack)
+├── webpack.config.js # 3 entry points, babel-loader, copy plugin
+├── README.md # Project documentation
+├── PRIVACY.md # Privacy policy for Chrome Web Store
+└── todo.md # Development task tracker
+```
+
+## Module Guide
+
+### Service Worker (`src/sw/sw.ts`)
+
+**Purpose**: Core CSPT detection engine — intercepts all outbound requests and matches them against values extracted from the active tab URL.
+
+**Key files**:
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/sw/sw.ts` | Request interception, source extraction, finding generation, storage | ~1,530 |
+
+**Key functions**:
+- `urlToSources(url)` — Parses the active tab URL; emits `Source[]` for each query param value (raw, encoded, decoded), each path segment (raw, encoded, decoded), and optionally `"undefined"`/`"null"` literals.
+- `generateFindings(url, sources)` — Splits the target request URL path by `/`; checks each segment against each source value (exact or partial match, case-insensitive per settings). Hard-ignores `adservice.google.com` and `ad.doubleclick.net`.
+- `storeFinding(finding)` — Mutex-protected read-modify-write to `browser.storage.local`. Deduplicates via `findingsCache` keyed by `${source.url}-${source.value}-${target.url}`.
+- `updateCurrentTab()` — Queries active tab and caches it module-level.
+
+**Event listeners**: `webRequest.onBeforeRequest`, `tabs.onActivated`, `tabs.onUpdated`, `webNavigation.onCommitted`, `storage.local.onChanged`.
+
+**Dependencies**: `webextension-polyfill`, `async-mutex`, `shared/types`, `shared/constants`.
+
+**Gotchas**:
+- `storeFinding` is called per-finding in a loop with no batching — each acquires the mutex separately. Acknowledged by a `// TODO` comment.
+- Cache key does not include `SourceType`, so two sources with the same URL and value but different types are treated as duplicates.
+- `currentSettings` is mutated via `Object.assign` on storage changes — races with in-flight `onBeforeRequest` callbacks.
+- `partialMinLength` check is per-source-value, not global.
+
+---
+
+### Shared Types & Constants (`src/shared/`)
+
+**Purpose**: Central type definitions and default configuration shared by all layers.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/shared/types.ts` | Enums (`SourceType`), interfaces (`Source`, `Target`, `Finding`, `FindingUI`, `Settings`, `Search`) | ~283 |
+| `src/shared/constants.ts` | `defaultSettings` object with all scanner/matching/display/filter defaults | ~96 |
+
+**Default settings**: All scanners on, partial matching off, case-insensitive on, `partialMinLength: 3`, `clearOnRefresh: false`, empty ignore patterns.
+
+**Gotchas**:
+- `SourceType` enum values are human-readable strings (e.g., `"Query Parameter (URL encoded)"`) used directly as UI labels.
+- `sw.ts` deep-clones `defaultSettings` via `JSON.parse(JSON.stringify(...))` to avoid reference aliasing.
+
+---
+
+### Findings Context (`src/components/findings-context.tsx`)
+
+**Purpose**: React Context provider that owns all findings state, filtering logic, and storage synchronization for the DevTools panel.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/components/findings-context.tsx` | Context provider, storage listeners, filter pipeline | ~1,185 |
+
+**Exports**: `FindingsProvider`, `useFindings()` hook.
+
+**Context value**: `{ findings, clearFindings, refreshFindings, search, setSearch }`.
+
+**Filter pipeline** (runs on every change to `search`, `rawFindings`, or `settings`):
+1. **Ignore patterns**: Each pattern from `settings.filters.ignorePatterns` compiled as `RegExp`, tested against target URL pathname. Invalid patterns silently ignored.
+2. **Search terms**: Space-split tokens with `-prefix` negation (AND logic across all terms per field).
+
+**Dependencies**: `webextension-polyfill`, `shared/types`, `shared/constants`.
+
+**Gotchas**:
+- Two separate `storage.local.onChanged` listeners (settings + findings) in the same component.
+- `clearFindings` sets local state to `[]` immediately AND writes to storage, causing a redundant re-read when the storage listener fires.
+- Negative search syntax (`-term`) is not documented in the UI.
+
+---
+
+### Findings Table & Search (`src/components/findings-table.tsx`, `findings-search.tsx`)
+
+**Purpose**: Main data table listing findings with search, export, and clear controls.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/components/findings-table.tsx` | Table rendering, export JSON, clear, row click handling | ~1,391 |
+| `src/components/findings-search.tsx` | Simple/advanced search mode toggle with SearchInput fields | ~379 |
+
+**Export behavior**: Creates a `Blob` of `JSON.stringify(findings)`, triggers a synthetic anchor click. Exports the **unfiltered** findings from context, not the currently visible filtered set.
+
+**Dependencies**: `findings-context`, `findings-search`, `ui/highlithed-text`, `ui/empty-findings`, `@heroicons/react`.
+
+**Gotchas**:
+- `.reverse()` mutates the findings array in place — a code smell though not currently causing bugs.
+- Export downloads unfiltered findings, which may surprise users who applied filters.
+- `FindingsTable` imports `tailwind/styles.css` — `panel.tsx` relies on this transitive import.
+
+---
+
+### Finding Detail View (`src/components/finding-drawer.tsx`, `finding-data-display.tsx`)
+
+**Purpose**: Slide-over drawer showing full detail for a selected finding.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/components/finding-drawer.tsx` | Headless UI Dialog slide-over panel | ~640 |
+| `src/components/finding-data-display.tsx` | Structured detail display (value, source, target, type) | ~614 |
+
+**Dependencies**: `@headlessui/react`, `@heroicons/react`, `ui/highlithed-text`, `shared/types`.
+
+**Gotchas**:
+- `new URL(...)` calls in `finding-data-display.tsx` are unguarded — malformed URLs would throw.
+- `"use client"` directive in `finding-drawer.tsx` is a Next.js artifact with no effect.
+- Target URL in detail view shows full URL (with protocol), while the table shows protocol-stripped version.
+
+---
+
+### UI Primitives (`src/components/ui/`)
+
+**Purpose**: Stateless, reusable presentation components.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/components/ui/toggle.tsx` | Accessible toggle switch (Headless UI Switch) | ~394 |
+| `src/components/ui/search-input.tsx` | Search input with optional label and icon | ~396 |
+| `src/components/ui/highlithed-text.tsx` | Text highlighting with URL-aware splitting | ~362 |
+| `src/components/ui/info-alert.tsx` | Blue informational alert box | ~161 |
+| `src/components/ui/empty-findings.tsx` | Empty state placeholder | ~102 |
+
+**Gotchas**:
+- `highlithed-text.tsx` filename is misspelled (should be "highlighted") — consistent across all imports.
+- `empty-findings.tsx` imports `PlusIcon` but never uses it (dead import).
+- `search-input.tsx` passes optional `value` prop directly to `` — `undefined` causes controlled/uncontrolled switch warning.
+
+---
+
+### Pages / Entry Points (`src/pages/`)
+
+**Purpose**: Webpack entry points that bootstrap independent React trees.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `src/pages/panel.tsx` | DevTools panel — FindingsTable + FindingDrawer inside FindingsProvider | ~291 |
+| `src/pages/popup.tsx` | Browser action popup — settings toggles, ignore patterns textarea | ~1,317 |
+
+**Gotchas**:
+- `popup.tsx` has a subtle race: `useEffect([settings])` writes `defaultSettings` to storage on initial render before the async storage read completes.
+- `panel.tsx` does not clear `selectedFinding` on drawer close — intentional for smooth exit animation.
+- Both files bootstrap via `createRoot(document.getElementById("root"))` at module evaluation time.
+
+---
+
+### Browser Integration (`public/`)
+
+**Purpose**: Static assets, HTML shells, and manifest definitions copied to `dist/` by CopyWebpackPlugin.
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `public/manifest-chrome.json` | Chrome MV3 manifest (ES module service worker) | ~148 |
+| `public/manifest-ff.json` | Firefox MV3 manifest (`scripts` array fallback) | ~153 |
+| `public/devtools.html` | Invisible DevTools page that loads devtools.js | ~27 |
+| `public/js/devtools.js` | Registers the Gecko panel via `panels.create()` | ~44 |
+| `public/panel.html` | React shell for DevTools panel | ~69 |
+| `public/popup.html` | React shell for browser action popup | ~69 |
+
+**Chrome vs Firefox differences**:
+| Aspect | Chrome | Firefox |
+|--------|--------|---------|
+| Background type | `"type": "module"` | Absent (classic script) |
+| Background field | `service_worker` only | `service_worker` + `scripts` array |
+
+**Gotchas**:
+- `devtools.js` uses manual `window.browser || window.chrome` polyfill — NOT `webextension-polyfill`.
+- `public/manifest.json` is `.gitignore`d — generated at build time by copying the platform-specific manifest.
+- Both manifests include `browser_specific_settings.gecko.id` — harmless on Chrome, required by Firefox.
+
+---
+
+### Build System
+
+| File | Purpose | Tokens |
+|------|---------|--------|
+| `webpack.config.js` | 3 entry points, babel-loader, CSS pipeline, CopyWebpackPlugin | ~319 |
+| `package.json` | Scripts, dependencies (v0.1.0 in package.json vs v1.6.1 in manifests) | ~417 |
+| `tsconfig.json` | TypeScript config for IDE type-checking (not used by webpack) | ~73 |
+| `tailwind.config.js` | Content paths, custom `danger`/`danger-dark` colors | ~89 |
+| `postcss.config.js` | Tailwind + autoprefixer plugins | ~20 |
+| `Dockerfile` | Node 20 Alpine build image | ~83 |
+| `build-docker.sh` | Docker build helper (`chrome` or `ff` argument) | ~191 |
+
+**Webpack entry points**:
+| Entry | Source | Output |
+|-------|--------|--------|
+| `popup` | `src/pages/popup.tsx` | `dist/js/popup.js` |
+| `panel` | `src/pages/panel.tsx` | `dist/js/panel.js` |
+| `sw` | `src/sw/sw.ts` | `dist/js/sw.js` |
+
+**Gotchas**:
+- `devtool: "inline-source-map"` is set at top level and NOT removed for production builds — production bundles include source maps.
+- `ts-loader` and `webpack-merge` are installed but unused.
+- No `splitChunks` — React and shared deps are duplicated across `popup.js` and `panel.js`.
+- CSS is injected via `style-loader` (runtime `