15 KiB
Vibn — Backend Handoff Tickets (for Sonnet)
Companion to
VIBN_PRODUCT_BLUEPRINT.md. These are the backend / plumbing tasks to hand to a cheaper model (latest Sonnet, standard reasoning; use high reasoning only on T2/T3/T6, which Opus should also review).Rule of the seam: the frontend is already built against typed contracts + mock data. Implement endpoints to the exact shapes; do not change the contract types or the frontend components. When an endpoint is live, swap the mock call for a fetch — that's the only frontend edit allowed.
Disjoint write scope: these tickets touch
app/api/**,lib/**, the agent runner, and the prompt files — NOT the onboarding.tsxUI (except the one documented mock→fetch swap in T11).
Milestone 0 — Foundation (do first; nothing is safe until these land)
T1 — One task ledger: markdown everywhere · ⚠️ Opus review
Problem: three prompts disagree on task tracking (route.ts says plan_* are
retired; the agent-runner coder.ts says call plan_task_complete; the session
runner toggles markdown). This causes the "loops on task 1" bug.
Do: make .vibncode/specs/*.md markdown checkboxes (- [ ] / - [x]) the
single source of truth in all three. Retire the DB plan_* tools (or make them
thin markdown writers). Ensure .vibncode/ is committed and never removed by
git clean -fd.
Files: vibn-frontend/app/api/chat/route.ts, vibn-agent-runner/src/prompts/coder.ts, vibn-agent-runner/src/agent-session-runner.ts.
Accept: a delegated run that completes a task flips the markdown checkbox AND
the desktop Interactive Backlog reflects it; no prompt references plan_*.
T2 — Extract BASE + MODE prompt modules · ⚠️ Opus review
Do: factor the shared prompt into BASE (identity, voice, spine/task-ledger
contract, infra model, hard rules, untrusted-content rule, project state) +
three MODE deltas (Collab / Build / Grow). Both route.ts and the agent-runner
import the same modules. See blueprint §5.3.
Accept: one source of truth for BASE/MODE; route + runner import it; Architect
("Collab") no longer contains the code/deploy body.
T3 — MODE_TOOLS map + enforced gating · ⚠️ Opus review
Do: one MODE_TOOLS: Record<"collab"|"build"|"grow", ToolName[]> (blueprint
§5.2). Filter exposed tool schemas per mode in the prompt builder AND reject
out-of-mode calls in the dispatcher. Apply in route + runner.
Accept: Collab cannot call ship/shell_exec/apps_create (not in schema +
dispatcher rejects); market_research_run only callable in Collab; tool count
per turn drops to ~20–30.
T4 — Phantom-tool + template-literal fixes (mechanical)
Do: in route.ts: apps_envs_set→apps_envs_upsert; apps_containers_list→
apps_containers_ps; remove plan_decision_log (doesn't exist); un-escape the
\${activeProject.slug} at ~L306 so it interpolates.
Accept: every tool name in prose exists in the registry; no literal
${activeProject.slug} in the compiled prompt.
T5 — De-contaminate hardcoded specs
Do: the 10-file spec manifest in route.ts (~L346–357, COPPA/Missinglettr/
Dracula) ships to every user. Derive it from the active project, or replace with a
generic "read whatever exists in .vibncode/specs/."
Accept: a fresh project's prompt contains no GetAcquired-specific spec names.
T6 — Metering ledger foundation · ⚠️ Opus review
Do: a per-event usage ledger { workspaceId, clientId, projectId, costType, quantity, rawCost, ts } (blueprint §8.2). Emit an event from every cost-incurring
tool (AI tokens, deploys, domains, market research, media, Missinglettr). Build on
lib/quotas.ts.
Accept: every billable action writes a ledger row tagged by project; a query
can total raw cost per client per period. (Invoicing UI is T16 — not now.)
Milestone 1 — Onboarding + dashboard endpoints (implement to the contracts)
Contracts:
vibn-frontend/app/(onboarding)/onboarding/onboarding-agency-types.ts. Mock to replace:…/onboarding-agency-mock.ts. Flow reminder: onboarding ends at ideal customer → dashboard; the targeting recommendation lives in the dashboard (T8), not onboarding. Steps are: Identity (T7b) → Presence → Ideal Customer (T7) → POST (T9) → Dashboard (T10).
T7 — POST /api/agency/analyze-expertise { text }
Returns: { tools: string[] }. Do: an LLM call that maps the consultant's
free-text ideal customer / problem description to canonical tool-category
labels that match smb_to_software_mapping (so they join in T8). The FE mock is
extractTools() (keyword stub) — replace with the LLM, keep the output shape.
Accept: "I want to help dentists automate booking" -> ["Appointment Scheduling Software"] (or better); labels exist in the mapping. Matches them with potential customers in their area, and drives brand awareness.
T7b — GET /api/agency/cities?q= (city autocomplete)
Returns: CityRef[] (global). Do: proxy Places API (New) Autocomplete
(places:autocomplete, restrict to localities/cities) for predictions, then
Place Details (New) to resolve each into CityRef (name = locality,
region = admin area level 1 short name, country + countryCode = ISO alpha-2,
lat/lng = location). Key stays server-side. The frontend CityLookup already
calls this and falls back to the seed list when absent.
Accept: typing "Vic" returns Victoria BC and global matches (not a fixed list).
T7c — POST /api/agency/places/search { name: string, city: CityRef }
Returns: PlaceMatch[] (top 3 matching businesses).
Do (Three-Stage AI Category Resolver):
- Stage 1: Google Places Lookup (Physical Context):
- Query Places API (New) Text Search using
GOOGLE_PLACES_API_KEYwith${name} ${city.name} ${city.region}to find matching business entities. - Extract:
id,displayName,formattedAddress,primaryType(e.g.,"health"), andtypes.
- Query Places API (New) Text Search using
- Stage 2: DataForSEO Website Lookup (Digital Context):
- If the business has a website, query DataForSEO's OnPage/Database APIs (or scrape the URL, falls back to raw query if offline) to retrieve the website's meta-title, description, and domain tags.
- Stage 3: AI Assessment (The Reasoning Bridge):
- Feed both Stage 1 (Google Places categories/types) and Stage 2 (website title, description, domain tags) into a Gemini LLM call (
gemini-3.5-flash). - Prompt:
Google Places details: - Name: {{displayName}} - Primary Type: {{primaryType}} - Types: {{types}} Website Details: - Title: {{scrapedTitle}} - Description: {{scrapedDescription}} Based on the above, select the single most relevant category ID (gcid) for this business from our canonical mapping list. Return only the raw GCID string (e.g., "gcid:dental_hygienist" or "gcid:plumber"). - Map the returned GCID to one of our 5 baseline categories:
"service"(trades/FSM),"appointments"(scheduling),"food"(dining),"retail"(pos/shop), or"events". - Retrieve the matched GCID's exact
"softwareNeeds"list fromsmb_to_software_mapping_final.jsonand return it inpresetTools. Accept: looking up "Wheely Clean" (which Google labels"health") correctly maps to"gcid:dental_hygienist"via AI assessment (by reading their teeth whitening website title), loading their actual dental scheduling, billing, and EHR/EMR custom blocks on Step 2! Shows loading spinner during execution.
- Feed both Stage 1 (Google Places categories/types) and Stage 2 (website title, description, domain tags) into a Gemini LLM call (
T8 — POST /api/agency/targets { city: CityRef, tools: string[] } (powers the dashboard)
Returns: TerritoryOpportunity[], sorted by opportunityScore desc.
Do: intersect tools with each SMB type's softwareNeeds
(smb_to_software_mapping_final.json) to pick candidate niches; for each, get
business counts via the Places Aggregate API (:computeInsights,
INSIGHT_COUNT) filtered by the niche's Places type (mapped from gcid)
within the city's area (circle around city.lat/lng). opportunityScore =
demand × weak/no-software gap × low Vibn saturation, biased by tool-fit. Treat
"Reporting / Dashboard Software" as a universal need. Mirror mockTargets;
set matchedTools per result.
Accept: real per-city counts for any city worldwide; vibnClaimedCount from
our DB; honest numbers only (no fabricated scarcity — blueprint honesty guardrail).
T9 — POST /api/agency AgencyOnboardingResult { profile, expertise, tools }
Returns: { workspaceSlug }.
Do (DB Storage Spec):
- Workspace row: Insert a new row into
fs_workspaces.name=profile.nameslug= derived slug fromprofile.name(idempotently deduplicated)- Store all metadata inside a structured
agency_onboardingJSONB field infs_workspaces.data(or related column):{ "city": { "id": "victoria-bc", "name": "Victoria", "region": "BC", "country": "Canada", "countryCode": "CA", "lat": 48.4284, "lng": -123.3656 }, "hasWebsite": true, "websiteUrl": "yoursite.com", "hasSocials": true, "hasBlog": false, "hasCustomDomain": false, "hasExistingClients": false, "expertise": "I want to help dentists automate booking", "tools": ["Appointment Scheduling Software"] }
- Workspace member row: Link the signed-in NextAuth user (
userId) as the'owner'of this workspace infs_workspace_members. - Provisioning: Trigger the standard workspace provision pipeline (Gitea org, Coolify project boundary via
lib/workspaces.ts) asynchronously so the tenant stands up. Accept: round-trips the posted result; a new row is created infs_workspacesandfs_workspace_members; metadata is saved perfectly in JSONB; and the workspace slug is returned. (No pitch, no claimed territory — those were removed from onboarding.)
T10 — The dashboard (the screen they land on) · ⚠️ Opus may build
Do: the agency dashboard at /[workspace] (light paper/ink theme). On load,
call T7 (analyze-expertise, or use stored tools) + T8 (targets for their city)
and render the recommended local businesses to target (the gold-rush list
with businesses / weak-software / claimed + matched-tools chips). Plus clients/
prospects, projects, retainer MRR. Claiming a target creates a prospect.
Accept: lands from onboarding seeded with their ideal-customer description; shows real
recommendations; claim → prospect. (FE craft — likely an Opus task; reuses
extractTools + mockTargets until T7/T8 are live.)
T11 — Wire onboarding + dashboard to the endpoints
Do: CityLookup already calls GET /api/agency/cities (T7b) with a seed
fallback — just stand up the route. Implement finishAgency in page.tsx to
POST T9 and route to /[workspace]. In the dashboard, swap extractTools/
mockTargets for T7/T8. No styling changes to onboarding.
Accept: the flow runs end-to-end on real data; onboarding behavior unchanged.
T12 — Preserve homepage intent through auth
Do: if the homepage hero captures input, persist it across Google OAuth
(localStorage/draft, like vibn:firstName) so onboarding resumes seeded.
Accept: typing on the homepage → sign in → onboarding has the value.
Milestone 2 — Design-first delivery (the custom tool)
T13 — Ingest the 4 design-kit families
Do: register vibn-ai-templates, vibn-app, vibn-crm, vibn-marketplace
(in design-templates/VIBN (2)/) into the design-kit registry: one kit per family,
themes as overrides; add DESIGN.md + tokens.css (+ SKILL.md) per the existing
lib/scaffold/open-design/design-systems/<id>/ structure.
Accept: get_design_template returns each; they appear on the Design tab.
T14 — Build recipe: scaffold-from-kit first
Do: rewrite the Build mode recipe so building a client's custom tool
starts from a kit (fork into the client repo) + token reskin, instead of
create-next-app. The tool is scoped from the consultant's expertise + the
client's softwareNeeds; SMB domain → family; client brand → accent.
Accept: a build starts from a polished themed template, not an empty Next app.
T17 — Onboarding hardening note (low priority)
Do: page.tsx has pre-existing unused imports (useState/useEffect/
useMemo on line 3) flagged as warnings — not from the agency work. Clean up if
touching the file.
Accept: no behavior change.
Milestone 3+ — Grow & billing (later)
T15 — missinglettr_* tool wrapper
Do: wrap the Missinglettr API (workspaces.create, posts.create, analytics).
Grow mode only. Accept: can schedule a multi-platform post; metered (T6).
T16 — Stripe retainers + invoicing
Do: on the metering ledger, roll up cost → apply pricing (retainer / cost-plus / fixed) → Stripe one-off invoice + recurring subscription for retainers. Accept: an agency can invoice a client and start a monthly retainer.
T18 — Google Business Profile (GMB) OAuth & Token storage
Do: add https://www.googleapis.com/auth/business.manage to the NextAuth Google Provider config.
Upon user sign-in, save the authorized OAuth access_token and refresh_token in fs_users.data.
On the backend, write a helper to list GMB locations for the authorized user and support posting Google Local Business posts.
This is the core engine for automated GBP posting and review management in Grow mode.
Accept: signing in with Google requests the GMB permission; tokens are securely saved in fs_users.data and are queryable by the server.
T19 — DataForSEO OnPage API Website Auditor
Do: implement a backend helper to post and retrieve data from DataForSEO's OnPage API (/v3/on_page/task_post -> /v3/on_page/summary).
Extract domain-wide metrics: domain_info.cms (to auto-detect what builder they are renting), domain_info.ssl_info, page_metrics.broken_links, and favicon availability.
⚠️ Hard constraint: DataForSEO's OnPage crawler strictly requires the target URL to include the protocol (e.g., must be "https://allardcontractorsltd.com" or "http://...", NOT a bare domain). Ensure the server-side payload prepends "https://" automatically when creating the crawler task.
Expose this audit dataset to the dashboard so consultants can auto-generate SEO health audits for their prospects.
Accept: posting a scan request triggers the DataForSEO crawl; returns unified CMS, SSL, and link metrics; tags them to the client's project row.
Notes for the implementer
- Don't touch the onboarding
.tsxfiles except T11's documented swap. - Keep
onboarding-agency-types.tsas the contract; if a shape must change, change it there and flag it (the UI depends on it). - Honesty guardrail (T8/T9): never show fabricated market/scarcity numbers.
- Flag T1/T2/T3/T6 for an Opus review pass before merge.