250 lines
15 KiB
Markdown
250 lines
15 KiB
Markdown
# 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 `.tsx` UI (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):**
|
||
1. **Stage 1: Google Places Lookup (Physical Context):**
|
||
- Query **Places API (New) Text Search** using `GOOGLE_PLACES_API_KEY` with `${name} ${city.name} ${city.region}` to find matching business entities.
|
||
- Extract: `id`, `displayName`, `formattedAddress`, `primaryType` (e.g., `"health"`), and `types`.
|
||
2. **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.
|
||
3. **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 from `smb_to_software_mapping_final.json` and return it in `presetTools`.
|
||
**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.
|
||
|
||
### 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):**
|
||
1. **Workspace row:** Insert a new row into `fs_workspaces`.
|
||
- `name` = `profile.name`
|
||
- `slug` = derived slug from `profile.name` (idempotently deduplicated)
|
||
- Store all metadata inside a structured `agency_onboarding` JSONB field in `fs_workspaces.data` (or related column):
|
||
```json
|
||
{
|
||
"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"]
|
||
}
|
||
```
|
||
2. **Workspace member row:** Link the signed-in NextAuth user (`userId`) as the `'owner'` of this workspace in `fs_workspace_members`.
|
||
3. **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 in `fs_workspaces` and `fs_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 `.tsx` files except T11's documented swap.
|
||
- Keep `onboarding-agency-types.ts` as 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.
|