159 lines
10 KiB
Markdown
159 lines
10 KiB
Markdown
|
|
# 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.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 configuration** — `ZOHO_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 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.
|