Queue-North-Website/docs/history/BATCH_LEDGER.md

10 KiB
Raw Blame History

Batch ledger — Queue North Website

Status: Archived
Owner: _null
Last reviewed: 2026-08-18
Governs: what the batches were, before the tracker held them
Review trigger: Nothing. Superseded by the tracker; kept for the record.

Why this file is archived rather than deleted

Planning used to live in markdown as a numbered batch list. It now lives in the tracker: milestones are batches, issues are deliverables, and severity is P0 / P1 / P2 / release-blocker.

This file is what that list was. It is kept because the reasoning in it is still worth reading, and deleted files are not readable.

It must not be updated. A batch list beside the tracker is a second answer to "what is open", and two records of the same thing will disagree without saying which is right. That is precisely why the work moved. If you are tempted to add a batch here, add a milestone instead.

Its Status: Archived is therefore not a nicety — it is the machine-readable form of "do not treat this as current".

"Ledger: missing" on the project screen is the correct answer

The Command Center's docs report still looks for a batch ledger — at docs/planning/FUTURE.md, named here without backticks deliberately, because doc-claims.sh treats a backticked path as a claim that the file exists and this one must not — and reports it as missing for every repository that has moved planning into the tracker. That is the expected state and not a gap to close: the report's own contract is that a tracker-first project shows a missing ledger and no batch percentages.

Creating that file to turn the line green would rebuild, in a new name, the exact second record this one was archived for.

This repository's record already reads ledger: retired, which is that contract working as intended.

The batches, as they stood

Ten phases, roughly 0.0.1 through 0.9.3, May to August 2026. The tracker now holds all 205 issues; Batch 00 — Pre-convention history is the closed milestone they were assigned to on adoption, because per-batch attribution was not recoverable — closure timestamps collapse into five bulk-close days, 119 of them on 2026-05-17 alone.

Phase What it covered Outcome
1 — Stack scaffold (0.1.x) Vite + React + Tailwind, shadcn-style primitives, React Router, Express, better-sqlite3, first API paths Complete
2 — Layout rebuild (0.2.x) app shell, all route pages, business content ported into React, service/industry data files, hash routing removed Complete
3 — Visual overhaul (0.3.x) light-first business design, Tailwind theme, typography and spacing rhythm, mobile-first polish Complete
4 — Forms and backend hardening (0.4.x) forms wired to Express, SQLite persistence, client and server validation, sanitisation, Zoho forwarding scaffold, rate limiting, Helmet, CORS Complete
5 — Verification and redesign (0.5.x) SPA router fix, hero rewrite, trust signals, services rewrite, Why Queue North, footer and CTA pass Complete
6 — Owner feedback (0.6.x) issues #30#41: nav active state, service icons, Cisco signals, hero alignment, industry icons, 8x8 page merge Complete except the About content, which needed owner input
7 — Zoho CRM integration (0.6.6) OAuth token endpoint fix, lead payload mapping, support→Cases, upsert, setup guide Complete. Batch 7.5 (webhook verification) deferred and never taken up
8 — Deferred enhancements testimonials, blog, SEO, performance audit SEO and performance done; testimonials and blog still open on owner content
9 — Audit bug fixes (0.7.0) 44 issues across batches 9.09.9 Complete
10 — Low-priority fixes (0.7.1) 6 issues across batches 10.010.2 Complete
— (0.8.x0.9.3) badges, mobile nav, breadcrumbs, injection hardening, 404, Zoho WebToLead mode, reCAPTCHA, privacy policy, full-route prerender Complete. Deployed as queue-north-website:dev

What was still open when the ledger was retired

All of these were already filed as issues, which is why nothing needed migrating out of this file when it was archived:

  • #68 — About section content corrections. Blocked on owner input.
  • #69 — Testimonials / case studies. Blocked on real client content.
  • #70 — Blog / writing section. Blocked on an owner decision.
  • #108 — Certification number #25432 appears fabricated. Needs owner verification.
  • #110 — "25+ years" claim unverified against the original site.
  • #162 — About page: founding year, partnerships, cybersecurity, compass image.

The Zoho audit, as it was written

Kept because it is the reasoning behind how the integration is shaped now, and because points 17 and 11 were fixed by batches 7.07.3 — a reader who finds the current code puzzling is usually looking at one of these decisions.

Current Implementation Status

Already working:

  • OAuth2 refresh token flow with in-memory token caching
  • 10s AbortController timeout on all Zoho fetches
  • Null-check short-circuit if credentials missing
  • Response.ok check before JSON parsing
  • Double-slash URL path fix (ZOHO_API_DOMAIN.replace(/\/$/, ""))
  • Fire-and-forget with .catch() (non-blocking, won't break lead submission)
  • SQLite always writes first (Zoho is best-effort overlay)
  • UNIQUE constraint on leads.email with 409 Conflict response
  • Sanitization + Zod validation before insert
  • Zoho forwarding also attempted on duplicate emails (in case Zoho record doesn't exist yet)

⚠️ Issues found in audit:

  1. Service_Interest is not a standard Zoho field — Zoho uses Lead_Source (picklist) for tracking where leads come from, and custom fields for service interest. Service_Interest will be silently ignored unless a custom field with that exact API name exists in the Zoho org. Should map to Lead_Source: "Website" plus a custom field or Description for the specific service.

  2. Name field mapping is wrong — Code maps leadData.name to Last_Name (correct that it's required), but the contact form has a single "Name" field. Zoho requires Last_Name (mandatory) and has First_Name (optional). Current mapping Last_Name: leadData.name || "Unknown" puts the full name in Last_Name which works but is ugly in Zoho UI. Should split on last space.

  3. Missing Lead_Source field — Every web-submitted lead should have Lead_Source: "Website" so it's trackable in Zoho. Currently absent.

  4. Token endpoint uses wrong URL — The refresh token POST goes to {ZOHO_API_DOMAIN}/oauth/v2/token but Zoho requires the token endpoint to be on the accounts server (accounts.zoho.com for US, accounts.zoho.eu for EU, etc.), NOT the API domain (www.zohoapis.com). This is a bug — it will fail in production.

  5. Missing redirect_uri in refresh token request — The Zoho docs don't require redirect_uri for the refresh token grant, but our code sends it. It won't cause an error (Zoho ignores it), but it's unnecessary.

  6. No $approved flag — For web-to-lead submissions, Zoho recommends sending "$approved": false to route leads through approval workflows, preventing unverified web submissions from immediately entering the active pipeline.

  7. No trigger parameter — By default Zoho will fire all workflows/blueprints on API-created leads. Should send "trigger": ["workflow"] to explicitly control which automations run, or "trigger": [] to suppress if unwanted.

  8. Support requests not forwarded to Zoho — Support form submissions go to SQLite only. Should create Cases (or at minimum Contacts + notes) in Zoho for ticket tracking.

  9. Datacenter configurationZOHO_API_DOMAIN defaults to https://www.zohoapis.com (US). Queue North is a US company, so this is correct. But the accounts-server URL (https://accounts.zoho.com) is a different domain and must be configured separately. Currently there's no ZOHO_ACCOUNTS_DOMAIN env var.

  10. CSP connect-src missing Zoho domains — Helmet CSP only allows 'self' for connect-src. This doesn't affect server-to-server calls, but if any client-side code ever calls Zoho directly it would be blocked. (Not currently an issue since all Zoho calls are server-side.)

  11. No retry on token refresh failure — If the refresh token request fails, the lead is silently dropped. Should implement at least one retry with exponential backoff.

  12. Zip_Code is not a standard Zoho field — Standard Zoho Leads have Zip_Code as a field API name... actually this IS correct. Zip_Code is the standard field.

Batch 7.5 — the one that was never done

Zoho Webhook Verification — Future Enhancement Optional: verify the Zoho integration works via a webhook callback. Not blocking for initial enablement. Could add a /api/zoho/webhook endpoint that Zoho calls on record changes — useful for confirming a lead was created, syncing status back, or notifying the team. Requires Zoho webhook setup in CRM and signature verification.

Deliberately not filed as an issue on adoption. It was written as a "defer unless requested" item and nobody has requested it; filing it would pad the tracker with work nobody has chosen. It is recorded here so the idea is not lost.

The Phase 13 build record

From the root-level BUILD_SUMMARY.md, migrated 2026-08-18 and then deleted.

What it recorded as complete: the Vite/React/Tailwind foundation with all routes; the Express backend serving /api/health, /api/leads and /api/support; SQLite with leads and support_requests; layout components and the shadcn-style primitives (Button, Card, Input, Textarea, Select, Badge, Sheet); every page including the then-standalone /8x8; and the seven services and four industries data files.

Its "known issues" list is worth keeping, because three of the four were later fixed and the record of them being known is what makes the fixes legible: the Sheet component skipping TypeScript generics, image assets still being placeholders rather than Queue North branding, db/ needing to be gitignored, and rate limiting not yet existing on the API endpoints.

Its embedded SQL schema was deliberately not carried forward. It predated the UNIQUE constraint on leads.email and the Zoho-related columns, so copying it here would have created exactly the stale second record this tree exists to prevent. server/index.js owns the schema.