Code โ€บ tail-villain

Operating Before Charging

Building credits, consent, and operational boundaries before accepting payment

Before charging users, I needed the product to know what it was charging for.

Tail Villain is an AI interview product, so user behavior maps directly to cost. Roadmap generation, interview turns, study sessions, and reports all involve model calls. Adding a pricing page would not be enough if the product could not count usage, refund failures, keep sessions alive, and record the consent state behind an account.

The work in early May was mostly operational. Separate the frontend and backend cleanly. Keep authentication stable across that boundary. Introduce credits as a real ledger. Reduce onboarding friction without losing identity. Make consent explicit before money enters the system.


The first boundary was deployment.

I split the frontend and backend into separate domains: the product site and the API server. I also removed the redundant API prefix from the backend routes. The shorter URLs were not the point. The real point was that once the frontend and API live on different domains, cookies, CORS, refresh tokens, and session behavior become explicit contracts.

That exposed a session persistence bug. The refresh token cookie was still scoped to the old path. After the route prefix changed, refresh requests no longer carried the cookie, so users could be logged out after a restart or an overnight session.

That had to be fixed before credits or pricing mattered. A paid product cannot make users wonder whether their session will survive basic infrastructure changes. I updated the cookie path to match the new route shape and verified that token persistence depended on the expected environment configuration.

Build reliability belonged in the same category. A corrupted TypeScript incremental cache caused emitted files to go missing. The fix was not interesting as a feature, but it was important as an operating habit: clean builds, predictable scripts, and an explicit Prisma schema path make deployment less dependent on whatever happened in the previous build.


The next boundary was credits.

A role-based plan can say what a user is allowed to access, but an AI product also needs to know what each action costs. Roadmap generation is not the same as one interview turn. A report is not the same as an opening message. If the system cannot express those differences, pricing becomes decorative.

I moved the model toward a credit ledger. The subscription stores the current and monthly credit values. A separate transaction log records deductions, refunds, charges, and bonuses. That audit trail matters because users will eventually ask why their balance changed.

I also added a database-level guard against negative balances. Balance integrity should not rely only on application code behaving perfectly. The database should reject states the business never wants to allow.

Refunds were part of the same design. If a roadmap analysis fails in the background, the user should not pay for the failure. Once the product starts counting cost, it also has to count reversals.


Pricing was less about picking numbers and more about making expectations readable.

For beta, the credit offer needed to be clear: what users get now, what is temporary, and which future tiers are not available yet. The Novice plan could carry a beta credit bonus and temporary access to stronger features, while higher tiers could show the intended ladder without pretending everything was already purchasable.

That transparency mattered more than making the pricing page look finished. If a tier is coming later, the page should say so. If credits are the unit of usage, the page should explain how they are spent.

I also added the basic SEO surfaces around this time. They were not the core story, but a public product needs public entry points. Search metadata, robots, and sitemap configuration are part of making the product reachable instead of only deployable.


Onboarding needed the opposite shape: fewer choices at the start, clearer records underneath.

I removed nickname and language fields from registration. The nickname can be generated. The preferred language can follow the current UI locale. Asking for both before the user has tried the product only adds friction.

That does not mean identity disappears. The system still needs a display name, so it generates one. The user gets to move forward without deciding everything up front, and the product still has a stable identifier for the account.

Consent moved in the other direction. Terms, privacy, and marketing consent needed a stored model. Overseas data transfer and AI processing partners needed to be visible in the registration flow. A cleaner form is useful only if it does not hide the parts the user actually needs to understand.

This is the awkward trade-off in onboarding. Some fields are removable friction. Some checkboxes are legal and trust boundaries. Treating both as the same kind of UI clutter would be a mistake.


There was also asset optimization during this stretch. Persona avatars and landing screenshots moved to WebP, and a favicon conflict was cleaned up. That work was not the narrative center, but it belonged to the same public-product phase. A first screen that loads unnecessarily heavy still affects whether a user gets far enough to care about pricing.

The larger pattern was consistent. Deployment had to survive a separated frontend and backend. Sessions had to persist across that separation. Credits had to form a ledger, not a loose counter. Pricing had to describe real usage. Onboarding had to be light, while consent had to be explicit.

Charging is not a button. It is an operating model.

Before Tail Villain could ask users to pay, it had to be able to explain usage, protect balances, recover from failures, and keep the account state trustworthy.