10 KiB
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.0–9.9 | Complete |
10 — Low-priority fixes (0.7.1) |
6 issues across batches 10.0–10.2 | Complete |
— (0.8.x–0.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 1–7 and 11 were fixed by batches 7.0–7.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.emailwith 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:
-
Service_Interestis not a standard Zoho field — Zoho usesLead_Source(picklist) for tracking where leads come from, and custom fields for service interest.Service_Interestwill be silently ignored unless a custom field with that exact API name exists in the Zoho org. Should map toLead_Source: "Website"plus a custom field orDescriptionfor the specific service. -
Name field mapping is wrong — Code maps
leadData.nametoLast_Name(correct that it's required), but the contact form has a single "Name" field. Zoho requiresLast_Name(mandatory) and hasFirst_Name(optional). Current mappingLast_Name: leadData.name || "Unknown"puts the full name in Last_Name which works but is ugly in Zoho UI. Should split on last space. -
Missing
Lead_Sourcefield — Every web-submitted lead should haveLead_Source: "Website"so it's trackable in Zoho. Currently absent. -
Token endpoint uses wrong URL — The refresh token POST goes to
{ZOHO_API_DOMAIN}/oauth/v2/tokenbut Zoho requires the token endpoint to be on the accounts server (accounts.zoho.comfor US,accounts.zoho.eufor EU, etc.), NOT the API domain (www.zohoapis.com). This is a bug — it will fail in production. -
Missing
redirect_uriin refresh token request — The Zoho docs don't requireredirect_urifor the refresh token grant, but our code sends it. It won't cause an error (Zoho ignores it), but it's unnecessary. -
No
$approvedflag — For web-to-lead submissions, Zoho recommends sending"$approved": falseto route leads through approval workflows, preventing unverified web submissions from immediately entering the active pipeline. -
No
triggerparameter — 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. -
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.
-
Datacenter configuration —
ZOHO_API_DOMAINdefaults tohttps://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 noZOHO_ACCOUNTS_DOMAINenv var. -
CSP
connect-srcmissing Zoho domains — Helmet CSP only allows'self'forconnect-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.) -
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.
-
Zip_Codeis not a standard Zoho field — Standard Zoho Leads haveZip_Codeas a field API name... actually this IS correct.Zip_Codeis 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/webhookendpoint 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 1–3 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.