Changelog
2026-03-01 (Form builder: default TEAM umikoindia, minimal integration)
Changed
- Default TEAM — Default hidden field
TEAMis now'umikoindia'(was'WebSite') when not set inconfigure()orrender(). - 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.defaultValueis 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). ExplicitdefaultValuein config still overrides.
2026-03-01 (Form builder: input classes, CTA class, and showLabels for embedding)
Added
inputClassName— Optional string (inconfigure()orrender()) 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 thecountryCodeconfig for extra class(es) on the country code select only (e.g. different width or style from other inputs).showLabels— Option (defaulttrue) to show or hide field labels; whenfalse, labels are omitted and the form wrapper gets classpg-form-no-labelsfor host CSS (e.g. placeholder-only layout).- Per-field
className— Field definitions accept an optionalclassNamestring, merged with globalinputClassName, for field-specific styling.
Changed
- docs/features/form-builder.md — Documented
inputClassName,submitClassName,showLabels, and fieldclassName; 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.captureinstead of the deprecated/lead. AllPulseGateForm.configure({ endpoint: '...' })examples now use full URLhttps://your-worker.dev/lead.captureor omit endpoint for default. API reference table clarifies thatendpointcan be a path (e.g.'/lead.capture') or full URL; legacy/leadnoted as deprecated (sunset 2026-06-01). - test-app/contact.html — Form config updated from
endpoint: '/lead'toendpoint: '/lead.capture'.
2026-03-01 (PulseGate naming: session API and localStorage)
Changed
- Session API —
window.PulseGateTrackerrenamed towindow.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_vidfor visitor_id injection.
2026-03-01 (Single-script integration: pulse.js)
Added
/pulse.js— Combined script (tracker + form) in one request. CallPulseGate.init()with no args; endpoint defaults to the script’s origin (worker URL). Form script’sresolveEndpoint()detectsscript[src*="pulse.js"]so the form knows the worker origin when loaded via the combined script.
Changed
- Script name — Combined script renamed from
pulsegate.jstopulse.js. - Default endpoint —
PulseGate.init()no longer requires{ endpoint }; it infers the worker domain from thepulse.jsscript tag. PassPulseGate.init({ endpoint: '...' })orformEndpointonly to override. Existing integrations using/tracker.jsand/form.jscontinue to work unchanged.
2026-03-01 (Form builder minification and GitHub Actions)
Added
- CI workflow —
.github/workflows/ci.ymlruns on pull requests tomainand on push to non-main branches. Runsnpm ci→npm run build:form→npm testso feature/fix/refactor/chore branches are validated before merge (aligns with branch strategy). /tracker.jsminification — Same minification as/form.js:scripts/minify-tracker.mjsproducestrackerScript.generated.ts.npm run build:formnow runs both minifiers; optionalnpm run build:trackerruns only the tracker.
Changed
/form.jsis now minified — The form builder script is built fromsrc/formBuilder/formScript.tsand minified (comments and whitespace stripped) intoformScript.generated.ts. Runnpm run build:formbefore deploy to regenerate;predeployruns 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:formin 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 tomain.
2026-03-01 (Lead observability and structured logging)
Added
- Structured lead events for alerting — Lead flow now emits JSON log lines with a stable
eventfield 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 eventscrm_callback.success,crm_callback.lead_not_found,crm_callback.invalid_key,crm_callback.db_error. Each event includeslevel,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
LeadStatuscolumn on Leads (migration 012) with stages:new,contacted,qualified,proposal,won,lost. Default isnew. Enables kanban-style pipeline management. - Leads list — List response and filters now include
status; query paramstatusfilters by pipeline stage. - PATCH /analytics/leads/:id — Update a lead’s pipeline status: body
{ "status": "contacted" }(API key required). Allowed methods for analytics now includePATCHfor 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 paramstatusfilters 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) andlast_follow_up_at(timestamp of most recent follow-up, or null). New sort optionsort=last_follow_uporders 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 secretsANALYTICS_API_KEYandCRM_CALLBACK_SECRET(and optionallyCRM_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 runswrangler secret putwhen the value is non-empty. Seedocs/deployment.mdfor 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 API —
GET /analytics/leads/:idsubmissions now includepayload_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, soANALYTICS_API_KEYandCRM_CALLBACK_SECRETwere undefined. Default[vars]now set both to dev values so Vitest and localwrangler devhave keys; production deploy uses--env productionand secrets and is unchanged.
2026-03-01 (GitHub Actions production deployment)
Added
- GitHub Actions workflow —
.github/workflows/deploy-production.yml: on push tomainor 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_TOKENandCLOUDFLARE_ACCOUNT_ID, optionalVITE_PULSEGATE_URLrepo 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
sessionsintest/tracking.spec.jsnow includesviewport_widthandviewport_height(migration 008) so session start INSERT succeeds; all 265 tests pass.
Changed
- Wrangler — Added placeholder
ANALYTICS_API_KEYandCRM_CALLBACK_SECRETto[env.production.vars]so wrangler no longer warns about missing vars (secrets override when set viawrangler 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 indashboard/distready for Pages deploy (custom domainpulsegate-app.pixleo.com).
2026-03-01 (Production deployment runbook — Phase 9.5)
Added
- Deployment runbook —
docs/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 script —
scripts/apply-production-migrations.shapplies 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
CrmLeadIdonLeads(migration 011) to store the CRM’s lead ID after Make.com creates the lead in the CRM. - CRM callback endpoint —
POST /lead.capture/crm-callbackfor Make.com to send{ lead_id, crm_lead_id }after successful CRM create. Secured withCRM_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 fields —
GET /analytics/leads/:idnow returnscrm_lead_idandcrm_lead_url(whenCRM_LEAD_URL_TEMPLATEis 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_SECRETandCRM_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]includesCRM_CALLBACK_SECRETfor development; production should usewrangler secret put CRM_CALLBACK_SECRETand optionallyCRM_LEAD_URL_TEMPLATE.
2026-03-01 (Interactive Dashboard — Phase 7.2)
Added
- Lead listing filters API —
GET /analytics/leadsnow acceptsproject,source,min_score,max_score,min_risk,max_risk,date_from,date_to,label, andsortquery params for server-side filtering. - Filter metadata endpoints —
GET /analytics/leads/projectsandGET /analytics/leads/sourcesreturn distinct values for filter dropdowns. - Anomaly detection endpoint —
GET /analytics/anomaliescomputes 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 enforcement —
POST /lead.captureis 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 deprecated —
POST /leadandPOST /remain functional for backward compatibility but now returnX-DeprecatedandSunset: 2026-06-01headers. - Unknown paths return 404 — All paths not matching a known route now return
404 Not Foundinstead of falling through to the lead handler. - form.js default endpoint —
resolveEndpoint()in the dynamic form builder now auto-appends/lead.captureto the detected origin, ensuring new integrations use the canonical route without explicit configuration.
Added
- 14 new route enforcement tests — Covers
/lead.captureacceptance, deprecation headers on legacy routes, 404 on unknown paths, CORS on 404 responses. - 1 new form builder test — Verifies
/lead.capturepath appended to auto-detected endpoint.
2026-02-24 (Lead Submission History)
Added
- Lead submission history model — Added
LeadSubmissionstable to store every form submit attempt (including repeated submits from different CTAs/pages). - D1 migration 009 — Creates
LeadSubmissionswith indexes onLeadId,VisitorId, andSubmittedAt. - Lead detail timeline API —
GET /analytics/leads/:idnow returnssubmissions[]with CTA/referrer/duplicate-window markers. - Dashboard submission history panel — Leads detail view now renders submission timeline alongside replay sessions.
- Form builder country code mode —
PulseGateForm.render({ countryCode: { enabled: true } })can auto-add a validatedCOUNTRYCODEdial-code field and pass it to lead capture payloads.
Changed
- Lead resolution logic —
src/lead.tsnow treatsLeadsas 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
Leadsrows; they are stored inLeadSubmissions. - Form velocity counting —
countRecentSubmissions()now prefersLeadSubmissions(with fallback toLeadsfor backward compatibility). - Country code dropdown defaults — Expanded
form.jsbuilt-in dial-code options to a broad alphabetical country list for easier selection without custom config. - Lead webhook email field — Payload now includes
emailwhile retainingemail1for 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 reliability —
LeadSubmissionswrite 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 endpoints —
GET /analytics/leads(paginated + searchable) andGET /analytics/leads/:id(full lead detail + linked session activity for replay). - Dashboard Leads page — New
/leadsroute 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:leadsfor 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
%27URL encoding. - Dashboard API field mismatches — 6 interface/field name mismatches between dashboard frontend and Worker analytics API now corrected.
Added
- E2E test page —
e2e-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.devwith SPA redirect support.
Deployed
- Worker —
https://pulsegate-development.firstdoorrealty.workers.dev(development environment) - Dashboard —
https://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
/replaywith cursor trail, click pulse animations, scroll position indicator, timeline scrubber, and playback controls (1×/2×/4×/8× speed). - Replay API —
GET /replay/sessions(paginated list with event counts, visitor filter) andGET /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.
2026-02-22 (Related Projects Tracking)
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_projectsfield listing all previous projects they inquired about. Lookup uses visitor_id (primary), email (secondary), and IP (fallback). - Visitor ID preservation —
visitor_idfrom form body (__visitor_id) is now persisted even when no tracking session exists, ensuring cross-form identity linking. - D1 migration 007 — Adds
RelatedProjectsTEXT 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_websitefield injected by form.js. Positioned off-screen witharia-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 tracking —
countRecentSubmissions()queries Leads by visitor_id or IP in last 24h.FormSubmitCountstored per lead. Multiple forms from same visitor = higher engagement signal for sales. - Suspicion scoring —
calculateSuspicionScore()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 handler —
lookupVisitorScore()insrc/lead.tsqueries sessions byvisitor_id(primary) or IP (fallback) to enrich leads with engagement data. - form.js visitor_id auto-injection — Reads
_fdr_vidfrom localStorage (set by tracker.js) and includes it as hidden field__visitor_idin 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_id —
getFingerprintByVisitorId()infingerprintService.tsfor 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_countfactor withreturn_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 script —
src/formBuilder/formScript.tsserved 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 backoff —
src/lib/webhook.tswithsendWebhookWithRetry(). 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 fromsrc/lead.ts.
2026-02-22 (Bot Detection & Service Coverage Tests)
Added
- 37 unit tests in
test/services.spec.jscovering 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 viaAuthorization: Bearer <key>orX-API-Keyheader. Returns401withWWW-Authenticate: Bearerwhen missing or invalid. - Auth middleware — Reusable
authenticateApiKey()insrc/lib/auth.tswith constant-time string comparison to prevent timing attacks. - CORS preflight for analytics —
OPTIONS /analytics/*returns proper CORS headers (includingAuthorizationandX-API-KeyinAccess-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_KEYadded toEnvinterface andwrangler.tomlvars. Production keys should be set viawrangler secret put ANALYTICS_API_KEY.
2026-02-22 (Phase 3: Observability & Hardening)
Added
- Structured logging — JSON-formatted
Loggerclass with request-scoped context (request ID, route, IP, country, timing). Replaces allconsole.log/error/warnin 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. Returns429withRetry-AfterandX-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/storageendpoint returns total R2 object count and estimated session count.
Changed
- Tracking router now passes
Loggerinstance 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-workertopulsegateacross 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 —
parseRequestBodynow 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
/leadlegacy 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) andfdr-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.tsis now the single source of truth for the frontend tracker (removedpublic/tracker.jsduplicate)wrangler.tomlupdated 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