From a13b0a7b341f91551b474b4ad36f3d411cfb417c Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 13 Feb 2026 18:11:47 +0000 Subject: [PATCH 1/4] docs: add noridocs and fix misleading JSDoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs.md files to all source directories documenting architecture, system invariants, and implementation details. Fix installDebugHelpers JSDoc that incorrectly claimed it checks import.meta.env.DEV. 🤖 Generated with [Nori](https://nori.ai) Co-Authored-By: Nori --- __tests__/docs.md | 31 ++++++++++++++++++++++++ src/config/docs.md | 32 +++++++++++++++++++++++++ src/core/docs.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/debug/docs.md | 31 ++++++++++++++++++++++++ src/debug/index.ts | 4 +--- src/docs.md | 48 +++++++++++++++++++++++++++++++++++++ src/react/docs.md | 58 +++++++++++++++++++++++++++++++++++++++++++++ src/types/docs.md | 33 ++++++++++++++++++++++++++ 8 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 __tests__/docs.md create mode 100644 src/config/docs.md create mode 100644 src/core/docs.md create mode 100644 src/debug/docs.md create mode 100644 src/docs.md create mode 100644 src/react/docs.md create mode 100644 src/types/docs.md diff --git a/__tests__/docs.md b/__tests__/docs.md new file mode 100644 index 0000000..7d7258f --- /dev/null +++ b/__tests__/docs.md @@ -0,0 +1,31 @@ +# Noridoc: __tests__ + +Path: @/__tests__ + +### Overview + +- Test suite for the library, mirroring the `@/src` directory structure with subdirectories for `config/`, `core/`, and `react/`. +- Uses vitest with jsdom environment, `@testing-library/react` for React component/hook tests, and a global setup file for browser API mocks. +- Coverage thresholds are enforced at 80% for statements, branches, functions, and lines (configured in `@/vitest.config.ts`). + +### How it fits into the larger codebase + +- Tests exercise all public API surfaces from `@/src/core`, `@/src/config`, and `@/src/react`. +- Coverage excludes barrel `index.ts` files and the `@/src/types` directory, since these contain only re-exports and type definitions. +- The test setup (`setup.ts`) provides the mock environment that all tests rely on: a sessionStorage mock backed by a plain object with `vi.fn()` wrappers, and a `window.location` mock defaulting to `https://example.com`. +- CI runs tests across Node 18, 20, and 22. + +### Core Implementation + +- **`setup.ts`**: Creates a fresh sessionStorage mock and location mock in `beforeEach`, ensuring tests are isolated. The storage mock implements `getItem`, `setItem`, `removeItem`, `clear`, `length`, and `key`. Location is stubbed with `href`, `search`, `hash`, `pathname`, `protocol`, `host`, and `hostname`. +- **`core/` tests**: Cover capture (URL parsing, allowed parameters, key format conversion, SSR fallback), storage (write/read/clear, format conversion, validation of stored data, silent failure), appender (query/fragment placement, preserveExisting, remove, extract), keys (bidirectional conversion, standard and custom keys, detection, validation), and validator (protocol, domain, normalization, mutable default protocol). +- **`config/` tests**: Cover `createConfig` merging semantics (nullish coalescing, array replacement, object merge), `validateConfig` error messages, and `loadConfigFromJson` fallback behavior. +- **`react/` tests**: Use `@testing-library/react` `renderHook` and `render` to test `useUtmTracking` (auto-capture, manual capture, clear, appendToUrl with share context and exclusions) and `UtmProvider`/`useUtmContext` (context propagation, error on missing provider). + +### Things to Know + +- The sessionStorage mock uses `vi.fn()` wrappers, which means tests can assert on call counts and arguments (`sessionStorage.setItem` calls, etc.). +- `window.location` is stubbed globally rather than using JSDOM's location, so tests that need specific URLs must override `location.href` and `location.search` in their setup. +- The `beforeEach` in `setup.ts` resets both mocks, so each test starts with empty storage and a clean `https://example.com` location. + +Created and maintained by Nori. diff --git a/src/config/docs.md b/src/config/docs.md new file mode 100644 index 0000000..4ce82d9 --- /dev/null +++ b/src/config/docs.md @@ -0,0 +1,32 @@ +# Noridoc: config + +Path: @/src/config + +### Overview + +- Defines default configuration values and provides utilities for creating, merging, and validating configuration objects. +- Produces `ResolvedUtmConfig` objects (all fields required) from partial `UtmConfig` input, establishing the configuration contract for the rest of the library. +- Re-exported through `@/src/index.ts` and consumed directly by `@/src/react/useUtmTracking.ts` and `@/src/debug`. + +### How it fits into the larger codebase + +- `createConfig()` is the primary entry point, called by `useUtmTracking` in `@/src/react` to resolve user-provided partial config into a complete `ResolvedUtmConfig`. +- `@/src/debug` imports `getDefaultConfig()` from here as a fallback when no config is provided to diagnostic functions. +- `DEFAULT_CONFIG` and `STANDARD_UTM_PARAMETERS` are the canonical definitions of default behavior (enabled, snake_case, sessionStorage key `utm_parameters`, auto-capture on mount, append to shares, the 6 standard UTM params). +- The config system does not perform side effects -- it is pure data transformation. + +### Core Implementation + +- `createConfig()` merges a partial user config with defaults using nullish coalescing (`??`) for scalar fields. Array fields (`allowedParameters`, `excludeFromShares`) are replaced wholesale when provided by the user, not merged. Object fields (`defaultParams`, `shareContextParams`) are shallow-merged. +- `mergeConfig()` follows the same semantics but takes a `ResolvedUtmConfig` as the base instead of implicitly using defaults -- useful for layering configurations. +- `loadConfigFromJson()` accepts `unknown` input, validates it is a non-null non-array object, then delegates to `createConfig()`. Invalid input falls back to defaults with a `console.warn`. +- `validateConfig()` performs runtime type checking on each config field and returns an array of error message strings (empty array means valid). +- `getDefaultConfig()` returns a shallow copy of `DEFAULT_CONFIG` with cloned arrays and objects to prevent mutation of the shared constant. + +### Things to Know + +- Array replacement (not merge) for `allowedParameters` is intentional: if a consumer provides `allowedParameters: ['utm_source']`, they get only that parameter, not the union with defaults. This is a deliberate design choice. +- `STANDARD_UTM_PARAMETERS` is declared `as const` and used both as the default `allowedParameters` value and as the source of truth in tests. It defines the 6 standard UTM params: source, medium, campaign, term, content, id. +- `validateConfig()` and `createConfig()` are independent -- `createConfig()` does not call `validateConfig()`. Validation is opt-in for consumers who want to check config before using it. + +Created and maintained by Nori. diff --git a/src/core/docs.md b/src/core/docs.md new file mode 100644 index 0000000..41e31ba --- /dev/null +++ b/src/core/docs.md @@ -0,0 +1,59 @@ +# Noridoc: core + +Path: @/src/core + +### Overview + +- Framework-agnostic core logic for capturing UTM parameters from URLs, persisting them in sessionStorage, appending them to outbound URLs, converting between key formats, and validating URLs. +- This is the heart of the library. Every other module (`@/src/react`, `@/src/debug`) builds on top of these utilities. +- All functions are SSR-safe, returning empty/null/unchanged values when browser APIs are unavailable. + +### How it fits into the larger codebase + +- `@/src/react/useUtmTracking.ts` orchestrates the core modules: it calls `captureUtmParameters` on mount, `storeUtmParameters`/`getStoredUtmParameters` for persistence, `appendUtmParameters` for URL generation, and `convertParams`/`isSnakeCaseUtmKey` for format handling. +- `@/src/debug` imports from `capture` and `storage` to assemble diagnostic snapshots. +- `@/src/index.ts` re-exports everything from this module for direct consumer use without React. +- All modules import types from `@/src/types`. + +### Core Implementation + +The data flow through the core modules follows this path: + +``` +URL string + | + v +[capture.ts] -- parses URL, filters to utm_* keys, applies allowedParameters, converts key format + | + v +UtmParameters object + | + v +[storage.ts] -- serializes to JSON, writes/reads sessionStorage, validates on read + | + v +[appender.ts] -- converts params to snake_case, merges into target URL query/fragment + | + v +URL string with UTM params +``` + +- **keys.ts**: Bidirectional key conversion between `snake_case` and `camelCase`. Uses lookup tables (`SNAKE_TO_CAMEL`, `CAMEL_TO_SNAKE`) for the 6 standard keys and regex-based conversion for custom keys. `isSnakeCaseUtmKey` checks for `utm_` prefix; `isCamelCaseUtmKey` checks for `utm` followed by an uppercase letter. `detectKeyFormat` scans keys and returns the first format found, defaulting to `snake_case` for empty objects. + +- **capture.ts**: `captureUtmParameters()` takes a URL string (defaulting to `window.location.href`), parses it via `new URL()`, iterates `searchParams`, and filters to keys passing `isSnakeCaseUtmKey`. Optionally filters by an `allowedParameters` set and converts output via `convertParams`. + +- **storage.ts**: Uses sessionStorage with a configurable key (default: `utm_parameters`). Write operations skip empty param objects and fail silently with `console.warn`. Read operations validate parsed JSON with `isValidStoredData()`, which checks that all keys pass `isUtmKey` and all values are strings or undefined. + +- **appender.ts**: `appendUtmParameters()` always converts input params to snake_case before appending to URLs (URL parameters are conventionally snake_case). Supports query string or fragment placement via `AppendOptions.toFragment`. Uses a custom `buildQueryString()` that omits `=` for empty-string values. When adding to query, it also cleans conflicting UTM params from the fragment (and vice versa). `removeUtmParameters()` strips UTM params from both query and fragment. `extractUtmParameters()` pulls UTM params from both locations, with fragment params taking precedence. + +- **validator.ts**: `validateUrl()` checks protocol (http/https only), domain (must contain a `.` for TLD), and parsability. `normalizeUrl()` prepends a configurable default protocol (module-level `let defaultProtocol`). `setDefaultProtocol()` mutates this module-level state. + +### Things to Know + +- **Key invariant**: All URL-facing operations use snake_case keys. The `appender` always converts to snake_case before manipulating URLs, regardless of what format the consumer passes in. This means URLs always contain `utm_source`, never `utmSource`. +- **SSR safety pattern**: Each module that accesses browser APIs (`window`, `sessionStorage`, `URL`, `document`) checks for their existence before use and returns a safe fallback (empty object, null, or unchanged URL). This is consistent across all core modules. +- **Silent failure**: Storage and capture operations never throw. Errors produce `console.warn` messages and return fallback values. The appender returns the original URL unchanged on failure. +- **validator.ts mutable state**: `defaultProtocol` is module-level mutable state modified via `setDefaultProtocol()`. This is global -- all callers share the same default protocol. Tests that call `setDefaultProtocol()` should restore the original value. +- **Fragment parameter handling in appender**: When appending to query, conflicting UTM params are removed from the fragment. When appending to fragment, conflicting UTM params are removed from the query. Only fragments that contain `=` are treated as parameter-bearing; plain anchors like `#section` are left alone. + +Created and maintained by Nori. diff --git a/src/debug/docs.md b/src/debug/docs.md new file mode 100644 index 0000000..646a3b8 --- /dev/null +++ b/src/debug/docs.md @@ -0,0 +1,31 @@ +# Noridoc: debug + +Path: @/src/debug + +### Overview + +- Development-time diagnostic utilities for inspecting UTM tracking state (config, URL params, stored params, storage availability). +- Provides console-formatted output, status check messages, and an opt-in `window.utmDebug` helper object for browser console access. +- Not part of the production data flow; intended for troubleshooting. + +### How it fits into the larger codebase + +- Imports `captureUtmParameters` from `@/src/core/capture`, `getStoredUtmParameters`/`isSessionStorageAvailable`/`getRawStoredValue` from `@/src/core/storage`, and `getDefaultConfig` from `@/src/config/defaults`. +- Re-exported through `@/src/index.ts` so consumers can call these functions directly. +- Does not depend on or interact with `@/src/react` -- it operates on the core layer only. +- All functions accept an optional `ResolvedUtmConfig`; when omitted, they fall back to `getDefaultConfig()`. + +### Core Implementation + +- `getDiagnostics()` assembles a `DiagnosticInfo` snapshot: resolves config, captures URL params via `captureUtmParameters`, reads stored params via `getStoredUtmParameters`, and checks `isSessionStorageAvailable()`. SSR-safe (returns empty URL and empty params when `window` is unavailable). +- `debugUtmState()` calls `getDiagnostics()` and formats output using `console.group`/`console.table` for structured browser console display. +- `checkUtmTracking()` calls `getDiagnostics()` and returns an array of status strings with emoji prefixes indicating state (e.g., whether params are in the URL, in storage, or if there is a mismatch suggesting the hook has not initialized yet). +- `installDebugHelpers()` checks for `?debug_utm=true` in the URL query string. If present, it attaches a `window.utmDebug` object with `state()`, `check()`, `diagnostics()`, and `raw()` methods. Only activates in browser environments. + +### Things to Know + +- `installDebugHelpers()` is gated solely by the `debug_utm=true` URL parameter. It does not check `process.env` or `import.meta.env.DEV`. +- The `window.utmDebug` object is attached via a cast to `Record` to avoid TypeScript errors on the global augmentation. +- `checkUtmTracking()` detects a potential timing issue: when URL params exist but storage is empty and `captureOnMount` is enabled, it warns that the hook may not have initialized yet. + +Created and maintained by Nori. diff --git a/src/debug/index.ts b/src/debug/index.ts index d93ceea..6bca290 100644 --- a/src/debug/index.ts +++ b/src/debug/index.ts @@ -164,9 +164,7 @@ export function checkUtmTracking(config?: ResolvedUtmConfig): string[] { /** * Install debug helpers on the window object for browser console access * - * Only installs if running in a browser and either: - * - In development mode (import.meta.env.DEV) - * - URL contains ?debug_utm=true + * Only installs if running in a browser and URL contains ?debug_utm=true * * @example * ```typescript diff --git a/src/docs.md b/src/docs.md new file mode 100644 index 0000000..dd47efd --- /dev/null +++ b/src/docs.md @@ -0,0 +1,48 @@ +# Noridoc: src + +Path: @/src + +### Overview + +- Root source directory for `@jackmisner/utm-toolkit`, a TypeScript library for capturing, storing, and appending UTM tracking parameters. +- Contains framework-agnostic core utilities (`@/src/core`, `@/src/config`, `@/src/debug`, `@/src/types`) and an optional React integration (`@/src/react`). +- Exposes two package entry points: `@/src/index.ts` (main, imported as `@jackmisner/utm-toolkit`) and `@/src/react/index.ts` (imported as `@jackmisner/utm-toolkit/react`). + +### How it fits into the larger codebase + +- `@/src/index.ts` is the main barrel export that re-exports everything from `core`, `config`, `debug`, and `types`. This is what consumers get when they `import from '@jackmisner/utm-toolkit'`. +- `@/src/react/index.ts` is the second entry point for React-specific exports, built as a separate bundle with React externalized. +- `@/tsup.config.ts` defines these two entry points and produces dual ESM/CJS output with TypeScript declarations. +- `@/__tests__` mirrors this directory structure for testing. +- The library has zero runtime dependencies. React is an optional peer dependency used only by `@/src/react`. + +### Core Implementation + +The library follows a layered architecture: + +``` +Consumer API + | + +--> src/index.ts (barrel) -----> core/ config/ debug/ types/ + | + +--> src/react/index.ts --------> react/ (useUtmTracking, UtmProvider) + | + +--> core/ config/ types/ +``` + +- **types/** (`@/src/types`): Shared type definitions consumed by all other modules. Defines the dual key format system (snake_case/camelCase) and configuration interfaces. +- **config/** (`@/src/config`): Pure configuration creation and validation. Merges partial user config with defaults to produce `ResolvedUtmConfig`. +- **core/** (`@/src/core`): Framework-agnostic UTM operations -- capture from URLs, persist in sessionStorage, append to outbound URLs, convert key formats, validate URLs. All SSR-safe. +- **debug/** (`@/src/debug`): Development-time diagnostics. Assembles state snapshots and provides formatted console output and optional `window.utmDebug` helpers. +- **react/** (`@/src/react`): React hook and context provider that orchestrate the core modules into stateful React APIs with auto-capture-on-mount behavior. + +**Key data flow**: URL with UTM params --> `capture` --> `store` in sessionStorage --> `appendToUrl` for outbound link generation. + +### Things to Know + +- **Dual key format invariant**: The library supports both `snake_case` (URL convention) and `camelCase` (TypeScript convention) throughout, but all URL-facing operations always convert to snake_case internally. This is enforced in `@/src/core/appender.ts`. +- **SSR safety**: Every module that touches browser APIs (`window`, `sessionStorage`, `URL`, `document`) guards against their absence. The library can be imported and initialized on the server without errors. +- **Two entry points**: The package.json `exports` map defines separate conditional exports for `.` and `./react`, each with ESM/CJS/types variants. React is externalized in the build so it is not bundled into the output. +- **No runtime dependencies**: The library is self-contained. All functionality is implemented from scratch using standard Web APIs (`URL`, `URLSearchParams`, `sessionStorage`). + +Created and maintained by Nori. diff --git a/src/react/docs.md b/src/react/docs.md new file mode 100644 index 0000000..eca81a6 --- /dev/null +++ b/src/react/docs.md @@ -0,0 +1,58 @@ +# Noridoc: react + +Path: @/src/react + +### Overview + +- React integration layer providing a hook (`useUtmTracking`) and context provider (`UtmProvider`/`useUtmContext`) for UTM parameter management in React applications. +- This is the second package entry point, imported as `@jackmisner/utm-toolkit/react` and built as a separate bundle with React externalized. +- Orchestrates the core modules (`@/src/core`) and config system (`@/src/config`) into a stateful React API. + +### How it fits into the larger codebase + +- `useUtmTracking` is the primary orchestrator: it calls `createConfig()` from `@/src/config`, then uses `captureUtmParameters`, `storeUtmParameters`, `getStoredUtmParameters`, `clearStoredUtmParameters`, `appendUtmParameters`, `convertParams`, and `isSnakeCaseUtmKey` from `@/src/core`. +- `UtmProvider` wraps `useUtmTracking` in a React context, enabling tree-wide access via `useUtmContext()`. +- React is externalized in the build (`tsup.config.ts` declares `external: ['react']`) and declared as an optional peer dependency. The core library works without React. +- Types (`UseUtmTrackingReturn`, `UtmProviderProps`, etc.) come from `@/src/types`. + +### Core Implementation + +**Data flow through `useUtmTracking`:** + +``` +Mount + | + v +useState initializer --> getStoredUtmParameters() --> initial state from sessionStorage + | + v +useEffect (once, via ref guard) --> if captureOnMount && enabled: + | + v +capture() --> captureUtmParameters(window.location.href) --> if has params: + | storeUtmParameters() + | setUtmParameters() + | else if has defaultParams: + | store & set defaults + v +appendToUrl(url, platform?) --> merges: captured params < default share context < platform context + | --> filters out excludeFromShares + | --> appendUtmParameters(url, mergedParams) + v +URL with UTM params +``` + +- Config is resolved once via `useRef(createConfig(options.config))` -- config changes after mount are not picked up. +- The `hasInitialized` ref prevents double-capture in React strict mode or re-renders. +- `appendToUrl` implements a layered merge: captured params are the base, then `shareContextParams.default` is applied, then `shareContextParams[platform]`. After merging, `excludeFromShares` filters out unwanted keys (comparing in both snake_case and camelCase). +- `UtmProvider` memoizes the context value based on all return fields from `useUtmTracking` to prevent unnecessary re-renders of consumers. +- `useUtmContext()` throws a descriptive error if called outside a `UtmProvider`, guiding the developer to either wrap with `` or use `useUtmTracking()` directly. + +### Things to Know + +- **Config is frozen at mount**: The `useRef` pattern means the resolved config never changes. If a consumer passes new config props, they will be ignored after the first render. +- **Initialization guard**: `hasInitialized.current` is a ref (not state), so the guard works correctly across strict mode double-effects without triggering re-renders. +- **`appendToUrl` exclusion logic**: The `excludeFromShares` filter converts camelCase keys to snake_case using inline regex (not the `toSnakeCase` utility), so it duplicates some conversion logic from `@/src/core/keys.ts`. +- **SSR safety**: The `useState` initializer checks `typeof window === 'undefined'` and returns `null` for server rendering. The `capture` callback also checks before accessing `window.location`. + +Created and maintained by Nori. diff --git a/src/types/docs.md b/src/types/docs.md new file mode 100644 index 0000000..524317a --- /dev/null +++ b/src/types/docs.md @@ -0,0 +1,33 @@ +# Noridoc: types + +Path: @/src/types + +### Overview + +- Central type definitions for the entire library, consumed by `@/src/core`, `@/src/config`, `@/src/debug`, and `@/src/react`. +- Defines the dual key format system (`snake_case` for URLs, `camelCase` for TypeScript) that is a core invariant of the library. +- All types are re-exported through `@/src/index.ts` to package consumers. + +### How it fits into the larger codebase + +- Every other module in the library imports types from here. This is the single source of truth for all shared interfaces and type aliases. +- The `UtmParameters` union type (`UtmParametersSnake | UtmParametersCamel`) is the fundamental data shape that flows through capture, storage, appending, and React state. +- `UtmConfig` and `ResolvedUtmConfig` define the configuration contract: partial config goes in from consumers, fully-resolved config comes out from `@/src/config`. +- `UseUtmTrackingReturn` and `UtmProviderProps` define the React integration contract used by `@/src/react`. +- `SnakeCaseUtmKey` uses a template literal type (`utm_${string}`) that enables support for custom UTM parameters beyond the standard ones, which drives the extensibility design in `@/src/core/keys.ts` and `@/src/core/capture.ts`. + +### Core Implementation + +- `KeyFormat` is a string literal union (`'snake_case' | 'camelCase'`) that controls key conversion throughout the library. +- `UtmParametersSnake` uses an index signature `[key: \`utm_${string}\`]` to accept arbitrary `utm_*` keys while also declaring the standard ones explicitly. `UtmParametersCamel` uses a broader `[key: string]` index signature since TypeScript template literals cannot express the camelCase pattern. +- `ResolvedUtmConfig` mirrors `UtmConfig` but with all fields required -- it represents the result of merging user-provided partial config with defaults. +- `ShareContextParams` uses `Partial>` with a `default` key for base params and platform-specific overrides, enabling a layered merge strategy in `useUtmTracking`'s `appendToUrl` callback. +- `AppendOptions` controls whether UTM params go into query string or fragment, and whether existing UTM params on the target URL are preserved. + +### Things to Know + +- `UtmParameters` is a union, not an intersection. Code that receives it must handle either format, typically by detecting the format or converting via `@/src/core/keys.ts`. +- `SharePlatform` is `'linkedin' | 'twitter' | 'facebook' | 'copy' | string` -- the named platforms are documentation aids, but any string is accepted. +- `DiagnosticInfo` is only used by `@/src/debug` and is meant for development-time inspection, not production data flow. + +Created and maintained by Nori. From c2bffcda24d852d636ae250412441c013f497869 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 13 Feb 2026 18:14:48 +0000 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20add=20CLAUDE.md=20with=20project=20?= =?UTF-8?q?gotchas=20and=20quick=20reference=20=F0=9F=A4=96=20Generated=20?= =?UTF-8?q?with=20[Nori](https://nori.ai)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Nori --- CLAUDE.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a601f66 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,25 @@ +# CLAUDE.md + +## Quick Reference + +- **Build**: `npm run build` (tsup, dual ESM/CJS) +- **Test**: `npm test` (vitest + jsdom) +- **Lint**: `npm run lint` (oxlint) +- **Format**: `npm run format` (prettier) +- **Type check**: `npm run type-check` + +## Gotchas + +- The git remote is named `main`, not `origin`. Use `git push main `. +- `tsconfig.cjs.json` is a leftover from pre-tsup era — the build uses tsup, not tsc. +- `validator.ts` has module-level mutable state (`defaultProtocol`). Tests that call `setDefaultProtocol()` must restore the original value. +- The React hook freezes config at mount via `useRef` — config prop changes after mount are ignored. +- All URL-facing operations convert keys to `snake_case` internally, regardless of the consumer's `keyFormat` setting. + +## Releases + +Releases are done via `npm run release:patch|minor|major` which creates a `release/` branch from main. The tag push triggers the publish workflow. + +## Test Setup + +`__tests__/setup.ts` mocks `sessionStorage` and `window.location` globally before each test. Tests that need specific URLs must override `location.href` and `location.search`. From 31510317ac62ec3db47f3bb72625669b30f6f2b6 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 13 Feb 2026 18:17:20 +0000 Subject: [PATCH 3/4] chore: remove unused tsconfig.cjs.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leftover from the pre-tsup build. The build is entirely driven by tsup now, nothing references this file. 🤖 Generated with [Nori](https://nori.ai) Co-Authored-By: Nori --- .npmignore | 1 - CLAUDE.md | 1 - tsconfig.cjs.json | 10 ---------- 3 files changed, 12 deletions(-) delete mode 100644 tsconfig.cjs.json diff --git a/.npmignore b/.npmignore index 8c2bcdf..ca70a28 100644 --- a/.npmignore +++ b/.npmignore @@ -10,7 +10,6 @@ coverage/ # Config files tsconfig.json -tsconfig.cjs.json .gitignore .prettierrc .eslintrc* diff --git a/CLAUDE.md b/CLAUDE.md index a601f66..b68c288 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,6 @@ ## Gotchas - The git remote is named `main`, not `origin`. Use `git push main `. -- `tsconfig.cjs.json` is a leftover from pre-tsup era — the build uses tsup, not tsc. - `validator.ts` has module-level mutable state (`defaultProtocol`). Tests that call `setDefaultProtocol()` must restore the original value. - The React hook freezes config at mount via `useRef` — config prop changes after mount are ignored. - All URL-facing operations convert keys to `snake_case` internally, regardless of the consumer's `keyFormat` setting. diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json deleted file mode 100644 index ab83ea4..0000000 --- a/tsconfig.cjs.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "moduleResolution": "node", - "outDir": "./dist/cjs", - "declaration": false, - "declarationMap": false - } -} From 5ccc0eb9616a68317a30158a7b1d1c38c7a51d3a Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 13 Feb 2026 18:20:17 +0000 Subject: [PATCH 4/4] docs: add language identifiers to code fences and improve CLAUDE.md wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodeRabbit review feedback: add `text` language identifier to unlabeled code fences in docs.md files (MD040), and minor wording improvement in CLAUDE.md. 🤖 Generated with [Nori](https://nori.ai) Co-Authored-By: Nori --- CLAUDE.md | 2 +- src/core/docs.md | 2 +- src/docs.md | 2 +- src/react/docs.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b68c288..fac006d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ ## Releases -Releases are done via `npm run release:patch|minor|major` which creates a `release/` branch from main. The tag push triggers the publish workflow. +Releases are done via `npm run release:patch|minor|major` which creates a `release/` branch from main. Pushing the tag triggers the publish workflow. ## Test Setup diff --git a/src/core/docs.md b/src/core/docs.md index 41e31ba..91f9855 100644 --- a/src/core/docs.md +++ b/src/core/docs.md @@ -19,7 +19,7 @@ Path: @/src/core The data flow through the core modules follows this path: -``` +```text URL string | v diff --git a/src/docs.md b/src/docs.md index dd47efd..5a50332 100644 --- a/src/docs.md +++ b/src/docs.md @@ -20,7 +20,7 @@ Path: @/src The library follows a layered architecture: -``` +```text Consumer API | +--> src/index.ts (barrel) -----> core/ config/ debug/ types/ diff --git a/src/react/docs.md b/src/react/docs.md index eca81a6..4e82174 100644 --- a/src/react/docs.md +++ b/src/react/docs.md @@ -19,7 +19,7 @@ Path: @/src/react **Data flow through `useUtmTracking`:** -``` +```text Mount | v