# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Leckerli is a lightweight (~23kB gzip) GDPR cookie consent manager built with **Preact**, **TypeScript**, **Zustand**, and **Tailwind CSS**. It produces two IIFE bundles for direct script inclusion on websites.

## Commands

```bash
# Install dependencies
yarn

# Development (BrowserSync on :4321 + Rollup watch + Tailwind watch)
yarn dev

# Full build (Tailwind CSS + Rollup)
yarn build

# Lint (zero warnings allowed)
yarn lint:js
yarn lint:gtm        # validate GTM sandbox JS restrictions
yarn fix:js          # with auto-fix
```

Manual testing is done via the dev server using `index.html` or `gtm-basic.html`. The GTM tag template (`doc/gtm-tag-template.tpl`) includes test scenarios runnable from the GTM template editor.

## Architecture

### Two Build Outputs (Rollup, IIFE format)

1. **`src/index.tsx` → `dist/assets/leckerli.min.js`** — Main bundle. Creates a `<div id="lkrl-wrapper">`, mounts the Preact app.
2. **`src/gtm.ts` → `dist/assets/leckerli-gtm.min.js`** — Lightweight GTM integration. Can auto-load the main bundle and bridges permission updates to GTM callbacks.

### State Management

All state lives in a single Zustand store (`src/hooks/useSettings.ts`). It:
- Merges `window.leckerliSettings` with defaults from `src/defaultSettings.ts`
- Persists consent as a JSON cookie via `js-cookie` (SameSite: strict)
- Dispatches/listens to custom DOM events (`leckerli:*`) for external integration

### Components

- **App.tsx** — Root component, conditionally renders Banner or Settings
- **Banner.tsx** — Fixed-position consent banner (HTML sanitized with DOMPurify)
- **Settings.tsx** — Modal with per-permission toggles

### CSS Scoping

All Tailwind styles are scoped to `#lkrl-wrapper` via `postcss-prefixwrap` (see `postcss.config.cjs`), preventing conflicts with host websites. Theming uses CSS custom properties (`--leckerli-foreground`, `--leckerli-background`, `--leckerli-primary`, etc.).

## Code Conventions

- Preact with `jsxImportSource: "preact"` (not React)
- `react` dependency is aliased to `@preact/compat` in package.json
- Arrow functions for all components (`react/function-component-definition: arrow-function`)
- Single quotes, trailing commas (es5), no parens on single arrow params
- Import sorting enforced by `eslint-plugin-simple-import-sort` (node builtins → external → internal aliases → relative → styles)
- Strict TypeScript: `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`
- `tailwind.config.js` has `important: true` to ensure styles override host site

## Git / Commits

- `dist/` files (leckerli.min.js, leckerli.min.css) should only be committed when releasing. Do not include them in feature/fix commits.
- The pre-commit hook (`.husky/pre-commit`) runs `yarn lint-staged && yarn build && git add dist`. Use `--no-verify` when amending to remove dist files from a commit.

### Pre-commit Hooks

Husky + lint-staged runs on every commit:
1. ESLint on changed `src/**/*.{js,jsx,ts,tsx}` files
2. Full `yarn build`
3. Auto-stages `dist/` changes

### Release Process

1. Create a `release/x.y.z` branch from `main`
2. Bump version in `package.json`
3. Update `CHANGELOG.md` via `chan release x.y.z` (moves `[Unreleased]` entries under `[x.y.z] - YYYY-MM-DD`)
4. **If bumping the minor or major version** (e.g. 1.2 → 1.3), update `LECKERLI_JS_URL` in `doc/gtm-tag-template.js` to match the new version (e.g. `@antistatique/leckerli@1.3/dist/assets/leckerli-gtm.min.js`)
5. Run `yarn build` to rebuild dist files
6. Commit with message `Bump vx.y.z` (include `dist/` files)
7. Open a PR from `release/x.y.z` → `main` and merge
8. Tag the release: both `x.y.z` and `vx.y.z` tags are created (lightweight)
9. Create a GitHub release via `chan gh-release x.y.z`
10. Publish to npm (`yarn publish` — `prepublishOnly` runs build + `pinst --disable`)

## GTM Tag Template

`doc/gtm-tag-template.js` runs in the GTM sandboxed JS environment (ECMAScript 5.1 + arrow functions + const/let only). Key restrictions: no `this`, no `new`, no template literals, no destructuring, no spread, no browser globals. All standard objects (`Object`, `JSON`, `Math`) must be obtained via `require()`. The `yarn lint:gtm` command enforces these constraints via a dedicated ESLint config (`.eslintrc.gtm.json`).
