Skip to content

Changelog

2026-03-01 (Form builder: default TEAM umikoindia, minimal integration)

Changed

  • Default TEAM — Default hidden field TEAM is now 'umikoindia' (was 'WebSite') when not set in configure() or render().
  • Docs — Form builder docs state that FIRSTNAME, PHONE, EMAIL, COUNTRYCODE are always on the form; integrators typically only set PROJECT, LOCATION, DOMAIN. Added "Minimal integration" example.

2026-03-01 (Form builder: default country code from user locale)

Changed

  • Country code default — When countryCode.defaultValue is not set, the form now infers a default from the user’s browser locale (navigator.language / navigator.languages) and pre-selects the matching dial code (e.g. en-IN → +91). Explicit defaultValue in config still overrides.

2026-03-01 (Form builder: input classes, CTA class, and showLabels for embedding)

Added

  • inputClassName — Optional string (in configure() or render()) applied as extra CSS class(es) on all inputs, selects, and textareas so forms can match host-site styling when embedded on different websites.
  • submitClassName — Optional string applied as extra CSS class(es) on the submit/CTA button for host-site styling.
  • countryCode.className — Optional string in the countryCode config for extra class(es) on the country code select only (e.g. different width or style from other inputs).
  • showLabels — Option (default true) to show or hide field labels; when false, labels are omitted and the form wrapper gets class pg-form-no-labels for host CSS (e.g. placeholder-only layout).
  • Per-field className — Field definitions accept an optional className string, merged with global inputClassName, for field-specific styling.

Changed

  • docs/features/form-builder.md — Documented inputClassName, submitClassName, showLabels, and field className; added "Embedding on external sites" section with example.

2026-03-01 (Form builder docs: canonical endpoint)

Changed

  • docs/features/form-builder.md — Updated JS integration examples to use the canonical lead endpoint /lead.capture instead of the deprecated /lead. All PulseGateForm.configure({ endpoint: '...' }) examples now use full URL https://your-worker.dev/lead.capture or omit endpoint for default. API reference table clarifies that endpoint can be a path (e.g. '/lead.capture') or full URL; legacy /lead noted as deprecated (sunset 2026-06-01).
  • test-app/contact.html — Form config updated from endpoint: '/lead' to endpoint: '/lead.capture'.

2026-03-01 (PulseGate naming: session API and localStorage)

Changed

  • Session APIwindow.PulseGateTracker renamed to window.PulseGateSession (init, trackEvent, flush, _debug). Avoids "tracker" in the global script name so adblockers are less likely to block or interfere. Log prefix [FDR][PulseGate].
  • localStorage keys_fdr_fp_pg_fp, _fdr_vid_pg_vid (PulseGate fingerprint and visitor id). Form script and docs updated to use _pg_vid for visitor_id injection.

2026-03-01 (Single-script integration: pulse.js)

Added

  • /pulse.js — Combined script (tracker + form) in one request. Call PulseGate.init() with no args; endpoint defaults to the script’s origin (worker URL). Form script’s resolveEndpoint() detects script[src*="pulse.js"] so the form knows the worker origin when loaded via the combined script.

Changed

  • Script name — Combined script renamed from pulsegate.js to pulse.js.
  • Default endpointPulseGate.init() no longer requires { endpoint }; it infers the worker domain from the pulse.js script tag. Pass PulseGate.init({ endpoint: '...' }) or formEndpoint only to override. Existing integrations using /tracker.js and /form.js continue to work unchanged.

2026-03-01 (Form builder minification and GitHub Actions)

Added

  • CI workflow.github/workflows/ci.yml runs on pull requests to main and on push to non-main branches. Runs npm cinpm run build:formnpm test so feature/fix/refactor/chore branches are validated before merge (aligns with branch strategy).
  • /tracker.js minification — Same minification as /form.js: scripts/minify-tracker.mjs produces trackerScript.generated.ts. npm run build:form now runs both minifiers; optional npm run build:tracker runs only the tracker.

Changed

  • /form.js is now minified — The form builder script is built from src/formBuilder/formScript.ts and minified (comments and whitespace stripped) into formScript.generated.ts. Run npm run build:form before deploy to regenerate; predeploy runs it automatically. Served script is smaller for production (e.g. https://pulsegate.pixleo.com/form.js).
  • Deploy workflow — The production workflow now runs npm run build:form in both the Test job and the Deploy job, so the minified form and tracker scripts are always regenerated in CI and the Worker bundle is up to date on every deploy to main.

2026-03-01 (Lead observability and structured logging)

Added

  • Structured lead events for alerting — Lead flow now emits JSON log lines with a stable event field so log aggregators (e.g. Datadog, CloudWatch) can filter and trigger alerts. Events: lead.form_submitted, lead.rejected_unsupported_content_type, lead.bot_detected, lead.test_lead_detected, lead.bot_detection_disabled, lead.duplicate_detected, lead.new_lead_created, lead.high_suspicion, lead.webhook_make_sent / lead.webhook_make_failed, lead.webhook_bot_sent / lead.webhook_bot_failed, lead.redirect, lead.db_error, lead.submission_history_error, lead.fatal_error, and CRM callback events crm_callback.success, crm_callback.lead_not_found, crm_callback.invalid_key, crm_callback.db_error. Each event includes level, message, ts, and context (e.g. lead_id, project, suspicion_score, error).

2026-03-01 (Lead kanban view and pipeline status)

Added

  • Lead pipeline status — New LeadStatus column on Leads (migration 012) with stages: new, contacted, qualified, proposal, won, lost. Default is new. Enables kanban-style pipeline management.
  • Leads list — List response and filters now include status; query param status filters by pipeline stage.
  • PATCH /analytics/leads/:id — Update a lead’s pipeline status: body { "status": "contacted" } (API key required). Allowed methods for analytics now include PATCH for this endpoint.
  • Dashboard: Table and Kanban views — Leads page has a Table / Kanban toggle. Table view is the existing sortable list with pagination. Kanban view shows columns per status; cards are draggable between columns to update status. Detail panel includes a Pipeline status dropdown to change status. Kanban loads up to 100 leads (filters apply).

Changed

  • GET /analytics/leads — Response items now include status. Optional query param status filters by stage.
  • GET /analytics/leads/:id — Detail response lead object now includes status.

2026-03-01 (Follow-up count and last follow-up in leads table)

Added

  • Leads list API — Each lead now includes follow_ups_count (number of follow-up form submissions) and last_follow_up_at (timestamp of most recent follow-up, or null). New sort option sort=last_follow_up orders by last follow-up first so you can focus on recently updated leads.
  • Dashboard leads table — New columns Follow-ups (count badge) and Last follow-up (date/time). Sort dropdown option Last follow-up first and saved view Recent follow-ups to quickly see leads with recent follow-up activity.

2026-03-01 (Worker secrets re-applied on deploy)

Fixed

  • Worker secrets lost after deployment — The production deploy workflow now re-applies Worker secrets from GitHub Actions after each wrangler deploy. If the repository secrets ANALYTICS_API_KEY and CRM_CALLBACK_SECRET (and optionally CRM_LEAD_URL_TEMPLATE) are set in Settings → Secrets and variables → Actions, they are written to the production Worker after every deploy so they are not cleared by Cloudflare. Add these secrets once in GitHub; the workflow only runs wrangler secret put when the value is non-empty. See docs/deployment.md for setup.

2026-03-01 (Follow-up history in dashboard)

Added

  • Follow-up history in lead detail — Dashboard lead detail panel now has a "Follow-up history" section when viewing a primary lead. Each form submission (primary and follow-ups) is listed with submitted-at time and Primary/Follow-up badge; expanding a row shows the data as submitted at that time (name, email, phone, project, source, CTA, optional message). Enables admins to see how lead data changed across multiple submits.
  • Lead detail APIGET /analytics/leads/:id submissions now include payload_json (raw form payload per submit) so the dashboard can display per-submission details.

2026-03-01 (GitHub Actions test fixes)

Fixed

  • CI test failures — Tests were failing in GitHub Actions: CRM callback "POST without auth returns 401" got 503; replay "GET /replay/sessions without auth returns 401" and tracking analytics got 500. Cause: Vitest uses the default Wrangler config with no env, and default [vars] was empty, so ANALYTICS_API_KEY and CRM_CALLBACK_SECRET were undefined. Default [vars] now set both to dev values so Vitest and local wrangler dev have keys; production deploy uses --env production and secrets and is unchanged.

2026-03-01 (GitHub Actions production deployment)

Added

  • GitHub Actions workflow.github/workflows/deploy-production.yml: on push to main or manual run, runs tests → D1 migrations (best-effort) → deploys Worker to production → builds and deploys Dashboard to Cloudflare Pages. No command-line deploy needed after one-time setup.
  • Deployment runbook — New section "GitHub Actions (fully automated)" with one-time setup: Cloudflare API token (Workers + Pages), GitHub secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, optional VITE_PULSEGATE_URL repo variable for Connect page pre-fill.

Changed

  • README — Deployment section now recommends automated deploy via Actions; manual steps linked in runbook.

2026-03-01 (Production deployment execution — Phase 9.2, 9.3)

Fixed

  • Tracking tests — Test schema for sessions in test/tracking.spec.js now includes viewport_width and viewport_height (migration 008) so session start INSERT succeeds; all 265 tests pass.

Changed

  • Wrangler — Added placeholder ANALYTICS_API_KEY and CRM_CALLBACK_SECRET to [env.production.vars] so wrangler no longer warns about missing vars (secrets override when set via wrangler secret put).
  • Deployment runbook — Documented production Worker URL (pulsegate.firstdoorrealty.workers.dev), and that re-running migrations may hit "duplicate column" if production D1 was already partially migrated.

Deployed

  • Worker (production) — Live at https://pulsegate.firstdoorrealty.workers.dev. Dashboard build output in dashboard/dist ready for Pages deploy (custom domain pulsegate-app.pixleo.com).

2026-03-01 (Production deployment runbook — Phase 9.5)

Added

  • Deployment runbookdocs/deployment.md: pre-deploy checklist, order of operations (secrets → D1 migrations → Worker → Dashboard), secret list and rotation, smoke tests, rollback steps, post-launch verification.
  • Production D1 migration scriptscripts/apply-production-migrations.sh applies all 11 migrations to production D1 in order; run from repo root.

Changed

  • README — Deployment section now points to runbook and summarizes the four-step flow (secrets, migrations, Worker deploy, Dashboard deploy).

2026-03-01 (Production deployment plan — Phase 9)

Changed

  • ROADMAP — Phase 9 (Production Deployment) added as next focus: secrets, D1 migrations, Worker deploy, Dashboard deploy, runbook, post-launch verification.
  • TASK.md — Production deployment promoted to HIGH priority with six tasks (9.1–9.6); Plan: Production Deployment added with order of operations and rollback notes.

2026-03-01 (CRM Lead ID & View in CRM — Phase 8.1 & 8.2)

Added

  • CRM lead ID storage — New D1 column CrmLeadId on Leads (migration 011) to store the CRM’s lead ID after Make.com creates the lead in the CRM.
  • CRM callback endpointPOST /lead.capture/crm-callback for Make.com to send { lead_id, crm_lead_id } after successful CRM create. Secured with CRM_CALLBACK_SECRET (Bearer or X-API-Key). Returns 200 with { ok, lead_id, crm_lead_id }, or 400/401/404/503 as appropriate.
  • Lead detail CRM fieldsGET /analytics/leads/:id now returns crm_lead_id and crm_lead_url (when CRM_LEAD_URL_TEMPLATE is set and the lead has a CRM ID). Enables one-click “View in CRM” from the dashboard.
  • Dashboard “View in CRM” — Lead detail panel shows a “View in CRM” link (opens in new tab) when the lead has a stored CRM ID and the URL template is configured.
  • Env / config — Optional CRM_CALLBACK_SECRET and CRM_LEAD_URL_TEMPLATE (e.g. https://crm.example.com/lead/) in wrangler vars or secrets.
  • Webhook payload — Make.com webhook payload now includes lead_id (our D1 lead row ID) so Make.com can pass it to the CRM callback.

Changed

  • Wrangler — Default [vars] includes CRM_CALLBACK_SECRET for development; production should use wrangler secret put CRM_CALLBACK_SECRET and optionally CRM_LEAD_URL_TEMPLATE.

2026-03-01 (Interactive Dashboard — Phase 7.2)

Added

  • Lead listing filters APIGET /analytics/leads now accepts project, source, min_score, max_score, min_risk, max_risk, date_from, date_to, label, and sort query params for server-side filtering.
  • Filter metadata endpointsGET /analytics/leads/projects and GET /analytics/leads/sources return distinct values for filter dropdowns.
  • Anomaly detection endpointGET /analytics/anomalies computes submission drops (day-over-day), traffic spikes (vs 7-day avg), high VPN/bot ratio, and high-risk lead ratio.
  • Dashboard anomaly cards — Dashboard page renders severity-colored anomaly alerts above the charts when detected.
  • Leads filter bar — Expandable advanced filter panel with project, source, min score, max risk, date range, engagement label, and sort controls.
  • Saved views — One-click preset filters: All Leads, Hot Leads, Very Hot, High Risk, Last 24h, Cold/No Engagement.
  • Activity timeline — Lead detail panel now shows a unified chronological timeline merging form submissions and browsing sessions with icons, timestamps, and replay links.
  • Quick contact actions — Email and phone links (mailto/tel) in the lead detail panel for one-tap outreach.

Changed

  • Lead listing sort — Sort is now server-side (newest, oldest, score_desc, score_asc, risk_desc, name_asc) instead of client-side re-sort.
  • Dashboard imports — Added Badge component and anomaly-related icons to dashboard page.

2026-03-01 (Enforce Lead Capture Route)

Changed

  • Lead capture route enforcementPOST /lead.capture is now the primary endpoint for lead form submissions. The catch-all handler that routed all unmatched paths to the lead handler has been removed.
  • Legacy routes deprecatedPOST /lead and POST / remain functional for backward compatibility but now return X-Deprecated and Sunset: 2026-06-01 headers.
  • Unknown paths return 404 — All paths not matching a known route now return 404 Not Found instead of falling through to the lead handler.
  • form.js default endpointresolveEndpoint() in the dynamic form builder now auto-appends /lead.capture to the detected origin, ensuring new integrations use the canonical route without explicit configuration.

Added

  • 14 new route enforcement tests — Covers /lead.capture acceptance, deprecation headers on legacy routes, 404 on unknown paths, CORS on 404 responses.
  • 1 new form builder test — Verifies /lead.capture path appended to auto-detected endpoint.

2026-02-24 (Lead Submission History)

Added

  • Lead submission history model — Added LeadSubmissions table to store every form submit attempt (including repeated submits from different CTAs/pages).
  • D1 migration 009 — Creates LeadSubmissions with indexes on LeadId, VisitorId, and SubmittedAt.
  • Lead detail timeline APIGET /analytics/leads/:id now returns submissions[] with CTA/referrer/duplicate-window markers.
  • Dashboard submission history panel — Leads detail view now renders submission timeline alongside replay sessions.
  • Form builder country code modePulseGateForm.render({ countryCode: { enabled: true } }) can auto-add a validated COUNTRYCODE dial-code field and pass it to lead capture payloads.

Changed

  • Lead resolution logicsrc/lead.ts now treats Leads as canonical primary records and resolves duplicates by project + identity priority (visitor_id > email > phone > IP).
  • Submission persistence — Repeated submissions for the same user+project no longer create new Leads rows; they are stored in LeadSubmissions.
  • Form velocity countingcountRecentSubmissions() now prefers LeadSubmissions (with fallback to Leads for backward compatibility).
  • Country code dropdown defaults — Expanded form.js built-in dial-code options to a broad alphabetical country list for easier selection without custom config.
  • Lead webhook email field — Payload now includes email while retaining email1 for backward compatibility with existing CRM/Make mappings.

Fixed

  • Hard uniqueness data loss risk — Added migration 010 to drop legacy unique index on Leads(ProjectName, ContactIP) so repeat/shared-IP activity can be safely preserved in history.
  • False bot positives blocking persistence — Removed UA/referer-based bot heuristic; bot detection now relies on honeypot (__pg_hp) signal.
  • Bot-path data loss — Bot-classified submissions now persist to Leads/LeadSubmissions; only CRM webhook forwarding is suppressed.
  • Submission history write reliabilityLeadSubmissions write now performs insert-first and retries with table-create only on missing-table errors.

2026-02-23 (Dashboard Leads Listing + Details)

Added

  • Lead analytics API endpointsGET /analytics/leads (paginated + searchable) and GET /analytics/leads/:id (full lead detail + linked session activity for replay).
  • Dashboard Leads page — New /leads route in React dashboard with searchable lead listing, per-lead detail panel, and replay shortcuts for associated sessions.
  • Sidebar navigation — Added Leads navigation item for quick access from dashboard.
  • Test coverage — Added analytics route tests for lead list and lead detail endpoints.
  • Puppeteer dashboard E2E — Added test/leadsDashboard.e2e.mjs + npm run test:e2e:leads for connect → leads list/detail → replay link flow.

2026-02-22 (E2E Deployment & Bug Fixes)

Fixed

  • form.js syntax error — SVG data URL in form script had unescaped single quotes inside template literal, breaking JavaScript parsing in browsers. Replaced with %27 URL encoding.
  • Dashboard API field mismatches — 6 interface/field name mismatches between dashboard frontend and Worker analytics API now corrected.

Added

  • E2E test pagee2e-test.html — Real estate landing page (Sky Towers) with tracker.js + form.js integration for end-to-end testing.
  • Cloudflare Pages deployment — Dashboard deployed to https://pulsegate-dashboard-4zr.pages.dev with SPA redirect support.

Deployed

  • Workerhttps://pulsegate-development.firstdoorrealty.workers.dev (development environment)
  • Dashboardhttps://pulsegate-dashboard-4zr.pages.dev (Cloudflare Pages)

2026-02-22 (React Dashboard)

Added

  • React dashboard application — Vite + React 18 + TypeScript + Tailwind CSS v4 + Recharts. Connect page, Analytics dashboard with charts, Sessions table with pagination, Session replay player with canvas-based visualization.

2026-02-22 (Session Replay Viewer)

Added

  • Session replay viewer — Self-contained HTML player served at /replay with cursor trail, click pulse animations, scroll position indicator, timeline scrubber, and playback controls (1×/2×/4×/8× speed).
  • Replay APIGET /replay/sessions (paginated list with event counts, visitor filter) and GET /replay/sessions/:id (full session + events from R2/D1). Protected by API key auth, same as /analytics/*.
  • R2 → D1 fallback — Event loading tries R2 append-only objects first, falls back to D1 events table for environments without R2 data.
  • Keyboard shortcuts — Space (play/pause), arrow keys (skip ±5s), 1-4 (speed select).
  • 21 new tests — Viewer HTML, CORS, auth (4 tests), session list API (5 tests), session detail API (5 tests), unknown routes.

Added

  • Related projects per lead — When the same visitor submits forms for multiple projects (e.g. "Sky Towers" then "Garden View"), subsequent leads include a related_projects field listing all previous projects they inquired about. Lookup uses visitor_id (primary), email (secondary), and IP (fallback).
  • Visitor ID preservationvisitor_id from form body (__visitor_id) is now persisted even when no tracking session exists, ensuring cross-form identity linking.
  • D1 migration 007 — Adds RelatedProjects TEXT column to Leads table.
  • 3 new tests — Sequential multi-project same-visitor (all 3 projects in sequence), email-only matching across IPs, and current-project exclusion.

2026-02-22 (Multi-Form & Fraud Detection)

Added

  • Honeypot field — Invisible __pg_website field injected by form.js. Positioned off-screen with aria-hidden, tabindex=-1. Bots fill it, humans don't. Triggers +40 suspicion score.
  • Time-to-submit tracking — form.js records Date.now() at render, calculates seconds elapsed at submit. Sent as __pg_tts. Under 5s → +30 suspicion (bot speed). 5-10s → +10 suspicion.
  • Multi-form submission trackingcountRecentSubmissions() queries Leads by visitor_id or IP in last 24h. FormSubmitCount stored per lead. Multiple forms from same visitor = higher engagement signal for sales.
  • Suspicion scoringcalculateSuspicionScore() builds a 0-100 composite score from 6 signals: honeypot filled (40), fast submit (10-30), no tracking session (15), zero engagement (10), VPN suspected (5-15), high form velocity (10-20), competitor email domain (25).
  • D1 migration 006 — Adds FormSubmitCount, SuspicionScore, SuspicionReasons, TimeToSubmit columns to Leads table.
  • 9 new tests — Honeypot detection, fast submit, time-to-submit, multi-form count, suspicion scoring (no tracking, VPN+zero engagement stacking, genuine buyer with zero suspicion).

2026-02-22 (Lead Scoring Integration)

Added

  • Real estate-optimized engagement scoring — Revised scoring formula: Duration 0-35 pts (1pt/10s), Scroll Depth 0-30 pts (1pt/3.33%), Clicks 0-20 pts (1pt/click), Return Visits 0-15 pts (5pt/return). Total max 100.
  • Engagement labels — Cold (0-20), Warm (21-45), Hot (46-70), Very Hot (71-100) for CRM prioritization.
  • Visitor score lookup in lead handlerlookupVisitorScore() in src/lead.ts queries sessions by visitor_id (primary) or IP (fallback) to enrich leads with engagement data.
  • form.js visitor_id auto-injection — Reads _fdr_vid from localStorage (set by tracker.js) and includes it as hidden field __visitor_id in form submissions.
  • D1 migration 005 — Adds 8 scoring columns to Leads table: VisitorId, SessionId, EngagementScore, EngagementLabel, PagesVisited, SessionDuration, ReturnVisits, VpnSuspected.
  • Webhook enrichment — Make.com webhook payload now includes engagement_score, engagement_label, pages_visited, session_duration_seconds, return_visits, vpn_suspected, visitor_id, session_id.
  • Fingerprint lookup by visitor_idgetFingerprintByVisitorId() in fingerprintService.ts for return visit data.
  • 13 new tests — 5 updated engagement scoring tests + 8 new lead scoring integration tests covering visitor_id lookup, IP fallback, no-tracking defaults, VPN flag, page count, and form.js injection.

Changed

  • Scoring formula — Replaced page_count factor with return_visits. Adjusted caps: duration 30→35, scroll 25→30, clicks 25→20, pages 20→return_visits 15.

2026-02-22 (Dynamic Form Builder)

Added

  • Dynamic form builder scriptsrc/formBuilder/formScript.ts served at /form.js. Generates configurable lead capture forms via script injection on any website.
  • Configurable hidden fields — Pass project, source, team, domain, and custom hidden values per form instance.
  • Configurable visible inputs — Define fields (text, email, tel, select, textarea) with labels, placeholders, validation rules, and required flags.
  • Theme customization — Primary color, border radius, font family, background, text color, and more.
  • Client-side validation — Email/phone format validation, required field checks, min/max length, and custom regex patterns with real-time blur validation.
  • Auto-enrichment — Automatically captures page domain and search keywords (utm_term, keyword, gclid) from URL params.
  • Success/error states — Configurable success message, optional redirect after submit, and error banners with callbacks.

2026-02-22 (Webhook Retry Strategy)

Added

  • Webhook retry with exponential backoffsrc/lib/webhook.ts with sendWebhookWithRetry(). Retries up to 3 times with 1s/2s/4s delays on network errors and 5xx responses. Short-circuits on 4xx. 10s timeout per attempt.
  • KV dead letter queue — Failed webhooks stored in KV (dead_letter:* keys, 7-day TTL) with URL, payload, error, and failure timestamp for manual recovery.
  • 5 new tests — Webhook retry success, 4xx short-circuit, 5xx dead letter storage, unreachable host, custom options.

Changed

  • Lead handler webhook calls (Make.com and Bot webhook) now use retry-capable sender instead of fire-and-forget.
  • Removed old sendWebhook() function from src/lead.ts.

2026-02-22 (Bot Detection & Service Coverage Tests)

Added

  • 37 unit tests in test/services.spec.js covering 4 service modules:
    • VPN Detection (19 tests) — hosting ASN flagging, IP/ASN volatility thresholds, session velocity, ASN change detection, Cloudflare bot score boundaries, multi-signal accumulation
    • KV Counters (7 tests) — increment, unique IP/ASN tracking with deduplication
    • Engagement Scoring (7 tests) — duration/click/scroll/page factor scoring with caps, total score max 100
    • Identity Service (5 tests) — visitor_id and device_id hash determinism and differentiation

Changed

  • Deployment task deferred to post all-phase development

2026-02-22 (Analytics API Authentication)

Added

  • API key authentication — All /analytics/* endpoints now require a valid API key via Authorization: Bearer <key> or X-API-Key header. Returns 401 with WWW-Authenticate: Bearer when missing or invalid.
  • Auth middleware — Reusable authenticateApiKey() in src/lib/auth.ts with constant-time string comparison to prevent timing attacks.
  • CORS preflight for analyticsOPTIONS /analytics/* returns proper CORS headers (including Authorization and X-API-Key in Access-Control-Allow-Headers) without requiring authentication.
  • 7 new tests — Covers: missing key (401), wrong key (401), valid Bearer token (200), valid X-API-Key (200), CORS preflight passthrough, method enforcement (405), unknown endpoint with auth (404).

Changed

  • ANALYTICS_API_KEY added to Env interface and wrangler.toml vars. Production keys should be set via wrangler secret put ANALYTICS_API_KEY.

2026-02-22 (Phase 3: Observability & Hardening)

Added

  • Structured logging — JSON-formatted Logger class with request-scoped context (request ID, route, IP, country, timing). Replaces all console.log/error/warn in tracking handlers.
  • Rate limiting — Fixed-window IP-based rate limiter on all /track/* endpoints. Backed by KV. Per-route limits: session start/end 10/min, events 120/min, batch 30/min. Returns 429 with Retry-After and X-RateLimit-* headers.
  • Request validation & sanitization — Centralized validator module enforces string length limits, type checks, event type whitelisting, coordinate bounds, and XSS prevention (strips <>). Applied to all four tracking routes.
  • Analytics dashboard API — Seven new GET endpoints under /analytics/*: overview, sessions/daily, pages/top, geo, engagement, sources, realtime, storage.
  • R2 retention scheduled job — Daily cron (3:00 AM UTC) prunes R2 objects older than 90 days using date-prefix scanning.
  • R2 storage monitoring/analytics/storage endpoint returns total R2 object count and estimated session count.

Changed

  • Tracking router now passes Logger instance through to all route handlers for consistent structured logging.
  • All tracking route validation errors return specific field-level messages instead of generic "Missing required fields".

2026-02-22 (Rename to Pulsegate)

Changed

  • Project renamed from fdr-lead-worker to pulsegate across package.json, wrangler.toml, README, docs, and context files
  • Removed client-specific references (First Door Realty) for reusability

2026-02-22 (Lead API Hardening)

Fixed

  • text/plain body parsing crash — parseRequestBody now falls back to key:value line parsing when JSON.parse fails
  • Double-slash in WebSite redirect URL when referer ends with /
  • Response.redirect() crash when referer/domain is empty or not an absolute URL

Changed

  • Comprehensive test suite for /lead legacy API: 26 tests covering routing, content-type handling, D1 persistence, duplicate detection, source mapping, fraud detection, redirect behavior, location defaults, error handling, and CORS

2026-02-22

Added

  • VitePress documentation system with full API reference, feature docs, and architecture guide
  • KV namespaces created on PixLeo Cloudflare account (production + preview)
  • R2 buckets created: fdr-tracking-data (prod) and fdr-tracking-data-dev (dev)
  • D1 migration 004 applied: sessions, events, fingerprints, identity_network tables with indexes

Changed

  • R2 storage refactored from read-modify-write to append-only key pattern (eliminates race conditions)
  • trackerScript.ts is now the single source of truth for the frontend tracker (removed public/tracker.js duplicate)
  • wrangler.toml updated with real KV namespace IDs (previously had placeholder values)

Fixed

  • R2 concurrent write race condition that could lose events under load

2026-02-22 (Initial)

Added

  • Visitor tracking system: sessions, events, fingerprinting, VPN detection, engagement scoring
  • Four tracking API endpoints: session start, event, events batch, session end
  • Frontend tracker script with canvas/WebGL/audio fingerprinting
  • TypeScript migration of entry point (index.ts)
  • Project context files: PROJECT_CONTEXT.md, ARCHITECTURE.md, TASK.md, SESSION_SUMMARY.md, ROADMAP.md