thetechprinciple.com — Widget Rollout & Lead Capture

Phase 1: bring the shared AI assistant widget to thetechprinciple.com itself, at parity with every other client site it already serves. Phase 2 (starts only once Phase 1 is confirmed live): DB-backed persistence, a real-time owner reporting console, reactive on-site highlighting of successful use cases, and a structured lead-capture form with a fixed reason-for-visit taxonomy — because the objective throughout is to capture the lead and understand what the visitor actually needs.

Framework at epics/ep_043_AI_native_serviceand _SEO/ai_website_assistant_framework, deployed on Render as shared-website-assistant-api (deploys from master). Target site at epics/ep_046_thetechprinciple/site/index.html. Current state, updated 2026-08-11: 24 of 28 nodes complete, 2 partial (correctness verified locally — burst-tested and soak-tested — but real production traffic and elapsed burn-in time still can't be simulated), 2 remaining as owner-token/deploy follow-through. origin/master has been updated: forensics traced production's live deploy to a specific commit (113ba608) that had been orphaned from master's history by an earlier reset; that commit was recovered by SHA from GitHub and confirmed as an ancestor of this branch's tip, meaning this branch already carried master's full lost lineage plus everything built since. With explicit, specifically-scoped user authorization, a backup ref was taken and master was force-updated to match — verified via a fresh fetch. What has NOT happened yet: an actual redeploy. This Render service's deploys appear to run on an external ~30-minute cycle or a manual dashboard trigger, not a push webhook, so production (confirmed reachable) is still serving the old client profile as of this writing. The ADMIN_TOKEN was supplied and validated (auth succeeded against the live admin API) but the owner-token call itself returned "Client not found" — direct confirmation that this and the deploy gap are the same root cause, not two separate problems.

Click any node for its detailed plan — steps, completion test, evidence and dependency. Phase 2 lanes are blocked on Phase 1 shipping to production and being confirmed live.

Phase 1 — Chat widget parity on the main site

Actor: admin/engineer · one-time rollout, same mechanism every other client site already uses

Phase 1b — Answer any question about the website

Actor: visitor asks, widget answers from approved content, or captures an email to follow up · framework-wide capability, not thetechprinciple-specific, but currently unused because no client has populated content or the email-fallback flow yet

Phase 2a — Persist all data in a database

Actor: engineer · foundational, everything else in Phase 2 reads/writes through this

Phase 2b — Real-time owner reporting

Actor: business owner · watching traffic as it happens, not on a manual-refresh cycle

Phase 2c — Reactive success-story highlighting

Actor: system, on the owner's behalf · surfaces proof points to the visitor in the moment, not after the fact

Phase 2d — Structured lead capture (the objective)

Actor: visitor → business owner · every other Phase 2 piece exists to drive traffic here

Why Phase 2 is gated on Phase 1 shipping first

Every Phase 2 lane assumes real visitor traffic is already flowing through the widget on the live domain. Building DB persistence, real-time push, or reactive highlighting against zero live traffic means testing against fixtures only — the same trap the framework already hit once with data/clients.json seed changes silently breaking assumptions elsewhere (see 20260808_203000_ep043_998_compare_endpoint_event_vocabulary_fix.md). Confirm Phase 1 is live and generating real events before starting Phase 2 work.

Current state, verified against source — not the commit message

  • site/index.html — now has both analytics-embed.js and widget.js tags, both pointed at https://shared-website-assistant-api.onrender.com (confirmed live and reachable via curl before wiring anything to it). Local change only — not deployed.
  • Client profile the-tech-principle-local — now allowedHosts: ["thetechprinciple.com","www.thetechprinciple.com","localhost","127.0.0.1"], status: "live", enabledModules: ["navigation","leadCapture","leadFollowup"] ("assistant" auto-included), theme set to the real site palette, 13 knowledge entries, notificationDestinations set to the confirmed real contact email. "contact" module deliberately left disabled — no verified phone/address exists. Now on master (force-updated, verified) — production is still serving the old profile only because it hasn't redeployed yet, not because the change is unpushed.
  • .env.example — still BUSINESS_OWNER_TOKENS_JSON={"the-tech-principle-local":"local-thetechprinciple-owner"}, unchanged. The real production owner token still needs POST /api/admin/owners against production with the real ADMIN_TOKEN — a Render-only secret this session doesn't have (render.yaml marks it sync: false, deliberately excluded from the repo).
  • StorageJsonStore still the default. A new SqliteStore (src/sqliteStore.js) implements the identical interface, built on Node's built-in node:sqlite — no external provisioning needed. Selectable via STORE_DRIVER=sqlite at boot; tools/migrate_to_sqlite.js does a self-verifying one-time import. Both stores pass the same test suite via createApp({ store }). Not cut over in production — that's still a real decision, now genuinely available rather than theoretical.
  • Owner reportingowner.js now has four tabs plus a live "● Live" indicator on Overview: a new GET /api/owner/reporting/stream SSE endpoint pushes a full snapshot on every tracked write for that tenant. Verified in a real browser: posting a test event updated the dashboard's stat cards with zero manual refresh. Compare tab is still pull-based — it reads a different endpoint shape than the snapshot the stream pushes.
  • Promotion banner mechanism — extended, not replaced: the promotion-fetch-and-render code is now a shared renderBanner() helper. A new dwell-time trigger on the existing service_view IntersectionObserver fetches /api/public/highlights and renders a matched case study when no promotion is occupying the one banner slot. highlight_impression/highlight_click events are captured the same way promotion events already were.
  • Lead schema — extended: POST /api/public/leads still stores the original fields unchanged, now also accepts reasonForVisit, validated server-side against the client's leadReasonOptions (six real values seeded for this client) when configured. The widget's lead form renders this as a required <select> in place of the free-text service field when the client has options configured. window.aiwOpenLeadForm() lets the promotion and highlight banners open this same form directly instead of just navigating to #contact. Owner Overview shows a leadsByReason breakdown, flowing through the live push too. A separate, simpler POST /api/public/question-followups endpoint still exists alongside it (qa 1.9) — not gated behind a module, since answering questions is core assistant behaviour, not an opt-in feature.
  • Knowledge retrieval and the no-answer fallbackretrieveKnowledge() unchanged. client.knowledge for this client now has 13 real entries (qa 1.6), grounded verbatim in the site's own copy. The old flat "I don't have approved information for that yet" fallback is gone from both the deterministic and LLM-configured paths (qa 1.7-1.8) — replaced with an acknowledgement, an email offer on the first unanswered question per session only, and silent accumulation after that, all test-verified.