Chat mode surface — direct access + freemium locks¶
⚠️ Superseded (2026-04-23): The hard-vs-soft lock model in this doc is replaced by the unified model in
b2c-freemium-unified-locks.md. The mode chip row design (direct access, noMoredropdown, per-role mode lists) is unchanged.Status: in-progress LNO: L — the chat mode chips ARE the discovery surface for the freemium product (#558). Without them, free users can't find presentations / research / exam prep and the paywall never fires, so Pro conversion is zero by construction. Owners: @bhanu Last updated: 2026-04-23 Roles affected: Trainer (B2B), Learner (B2B), B2C free, B2C Learner (₹199), B2C Pro (₹499) Primary routes:
/chat(B2C),/ai-tutor(B2B),/presentations/:idSource of the ask: self-initiated, after noticing that (a) the trainer chip row hid 3 of 7 modes behind a "More" dropdown and (b) the mode chip row never rendered for learners or B2C users — so every paywall and quota lock we had built in ChatPage was a no-op. Parent issue: #558 (B2C epic). Ties into #568 (paywall UI), #563 (download gating).
TL;DR¶
Every chat mode the user can access is now a top-level chip — no More dropdown, no role gate. B2C free users see the same chip row but with two lock flavours: hard locks on Pro-tier modes (question_bank, lesson_plan), and soft locks on quota-limited modes (presentation, research, exam_prep) that flip to locked when quota.remaining <= 0. Every artifact download is gated for free users: in-chat downloads via renderDownload/DownloadLockButton, and the presentation viewer's pptx / pdf export via a paywall modal at the handler. The QuotaPill is now clickable end-to-end so the indicator itself opens the paywall, not just the Upgrade button.
Why this exists¶
Who asked for it¶
Self-initiated. Trigger: while reviewing the freemium plumbing on feat/b2c-dashboard we found the chip row never rendered for B2C users (gated by isTeacher), so the PRO_ONLY_MODES set we'd wired up was dead code — B2C free users couldn't even reach the chips to trigger the paywall. Separately, trainer audiences with 7 modes only showed the first 4 inline; the last 3 sat behind a "More" dropdown that buries them by design.
No external user asked for this specific change. The underlying PRD (#558) does ask for the free-tier preview-then-paywall flow to be discoverable.
User pain¶
Priya (B2C free — persona from #558). Signs up, lands in chat, sees a text input and nothing else. She doesn't know the product makes presentations, unit tests, or exam prep — those features exist but have no surface. She types a generic question, gets a text reply, and leaves. The "aha moment" the PRD defines (generate → preview → want-to-download) never happens.
Dr. Anita (B2B trainer, k12 audience). Sees 4 chips and a More button. Clicks More, sees Create Content, Teaching Ideas, Learner Feedback — three features she didn't know existed. She picks one once out of curiosity, forgets, and never uses them again. Engineering's usage dashboard will show these three modes at <3% of chat starts.
Prof. Suresh (B2C free — persona from #558). Generates a presentation (the mode happens to be accessible via seeded deep-link from signup). Clicks Download PPTX on the viewer page — export runs, file downloads, no paywall, no conversion pressure. The PRD's core decision ("preview IS the sales pitch, paywall on download") is broken at the viewer surface.
Cost of not doing it¶
- B2C conversion is zero by construction — no chip row → no chance to surface
presentation/question_bank→ no paywall → no upgrade intent captured. - Trainer feature discovery stays bottom-quartile for the 3 hidden modes. The decision to hide them was based on chip-row real estate, not on usage — if they belong in the product they belong as chips.
- Download gating is partially bypassed on the presentation viewer page, which free users can reach via the chat's
Open in Editorlink. Backend already returns 402, but the UI just shows a generic toast — so we lose both the download and the upgrade moment.
Validation¶
Weak, flagged. No user interviews ran specifically for this surface. The PRD (#558) validates the upstream motion (free-tier preview + download paywall is a known pattern — Canva, Gamma, ChatGPT Go do this), and the existing design review note in ai-tutor/index.ts:339-343 validates chip ordering ("presentation in slot #2"). The B2C mode list composition (explain, exam_prep, research, presentation, question_bank, lesson_plan) is a product-team call that will be revised based on the #569 funnel analytics once live.
Personas¶
- Priya (B2C free, 3rd-year ECE). Wants to discover PPT generation within the first 60 seconds.
- Prof. Suresh (B2C free → paid candidate, freelance lecturer). Will pay if the paywall frames the download as the gate, not the generation.
- Dr. Anita (B2B trainer, k12). Already pays via institution — wants direct access to every mode, not a dropdown.
ICP exclusions¶
- B2B institutional users bypass every freemium lock —
useB2CQuota()returnsundefinedfor them, solockedModesis empty andrenderDownloadstays undefined (engine falls back to default download buttons). - Parent role — no chat access on the affected surfaces yet; deferred.
Non-goals¶
- Not redesigning the chip visual — same
<Chip variant="suggestion" size="sm">as before, just rendered for more roles and without theMoredrawer. - Not wiring Razorpay —
PaywallModalstill link-outs tokwilo.ai/pricing. Payment flow is Phase 2 (#570). - Not relabelling modes per audience — "Question Bank" stays "Question Bank" everywhere; we considered "Unit Test" for B2C and deferred. If funnel data shows confusion, revisit.
- Not changing backend quota values — free tier says 1 presentation or 3 or whatever the backend returns. The frontend just respects
quota.remaining. - Not adding watermarking — #574 covers that in Phase 2.
Rejected alternatives¶
- Keep
Moredropdown, add chips for B2C only. Rejected because it keeps trainer discovery broken and forks the chip-row logic by role. - Central
FreemiumGateHOC aroundChatEngine. Rejected becauseChatEngineshould stay freemium-agnostic — its job is to render the chat, not to decide subscription tiers. All B2C policy lives inChatPage. - Compute lock state in
ChatEnginefrom aplanprop. Same rejection: leaks B2C concepts into the shared engine. Current design:ChatPagederiveslockedModesand passes it down;ChatEngineonly knows "locked = true/false". - Pure-quota check for hard locks too (let backend return
remaining: 0, limit: 0for Pro-only modes). Considered — cleaner on paper but couples the mode-tier split to the quota schema, which is meant to represent monthly allowances. Pro tier ≠ quota of zero; it's a different axis.
Design decisions¶
Decision 1 — role selects availability, plan+quota selects lockedness¶
getModesForUser({ role, audience }) in services/ai-tutor/index.ts returns the ordered mode list per role. Lock policy lives in ChatPage/index.tsx as deriveLockedModes(plan, quota). These are orthogonal concerns — role can't cause a lock, plan can't add a chip.
Decision 2 — soft vs hard locks¶
| Lock type | Trigger | Modes | Rationale |
|---|---|---|---|
| Hard (tier) | plan === 'free' always |
question_bank, lesson_plan |
PRD #558 places these in Rs.499 Pro tier. No monthly quota — they're simply not in the free plan. |
| Soft (quota) | plan === 'free' AND quota[feature].remaining <= 0 |
presentation, research, exam_prep |
PRD defines these as "preview N/mo free". Free users get to try them until the counter runs out. |
Both flavours resolve to the same Set<string> passed to ModeChipRow.lockedModes — the chip UI doesn't care why it's locked. Clicking either kind opens the paywall via onLockedModeClick.
Decision 3 — downloads blocked end-to-end for free users¶
Three entry points for artifact downloads on the B2C surface:
- In-chat research downloads (DOCX, MD): already wrapped by
renderDownloadinChatPage→DownloadLockButton. No change beyond addingpdfto the format union. - In-chat presentation downloads: currently no inline buttons — presentations funnel through
Open in Editorto the viewer page. - Presentation viewer page (
/presentations/:id): new code readsuseB2CQuota()and, whenplan === 'free', interceptshandleExport(pptx|pdf)→ opensPaywallModal. Backend still returns 402 as the source of truth (#563).
Decision 4 — QuotaPill is a button, not a read-out¶
Per #568: "Clicking the indicator opens the paywall modal." Old code only wired the Upgrade button. New code wraps the entire quota segment block in a <button> for free plans — any click on the pill opens the paywall. Paid plans render the pill as a static display.
Surfaces changed¶
apps/web/src/services/ai-tutor/index.ts— addedKWILO_B2C_MODES,getModesForUser, widenedKwiloModeIdto include learner IDs.apps/web/src/pages/shared/AITutorPage/ChatEngine.tsx— computesmodesvia the selector, passes toChatView; addedpdftorenderDownload.formatunion.apps/web/src/pages/shared/AITutorPage/components/ChatView.tsx— drops unusedaudienceprop, forwardsmodes.apps/web/src/pages/shared/AITutorPage/components/ChatInput/index.tsx— chip row now renders for all roles (gate changed fromisTeachertomodes.length > 0).apps/web/src/pages/shared/AITutorPage/components/ChatInput/ModeChipRow.tsx— removedMoredropdown, all chips inline withflex-wrap.apps/web/src/pages/b2c/ChatPage/index.tsx— hard+softlockedModesmemo,pdfinrenderDownload, download label map.apps/web/src/pages/b2c/components/QuotaPill.tsx— clickable pill for free plans.apps/web/src/pages/shared/PresentationViewerPage.tsx— reads B2C quota, interceptspptx/pdfexport for free users.apps/web/src/i18n/locales/en/aiTutor.json— addedaiTutor.presentationCard.downloadPptx.apps/web/src/i18n/locales/en/b2c.json— addedb2c.quotaPill.clickToUpgradeAria.
Metrics¶
- North star: first-session artifact-preview rate for B2C free users (chip click → mode selected → message sent → artifact rendered). Target ≥ 30% (PRD #558).
- Guardrail 1: paywall-open rate for B2C free users who attempt a locked action (hard-lock chip click, soft-lock chip click, viewer export click). Expect > 0 — if zero, the gates aren't firing.
- Guardrail 2: no regression in B2B trainer mode-click distribution. Previously-hidden modes (
create_content,teaching_ideas,student_feedback) should rise from <3% each to somewhere in the 5–15% band once directly visible.
Success signals¶
- Priya lands on
/chatas a free user. She seesPresentationandExam Prepas chips within her first second of page load. She clicks one, generates an artifact, hits the paywall on download, and either upgrades or bounces — either way the funnel has data. - Suresh cannot download the generated PPT; the paywall opens with
featureName="presentation"and the copy matches the preview he just saw. - Dr. Anita sees all 7 trainer modes, no
Moredropdown. - Per-mode click rates for the previously hidden modes move off the floor.
Sean Ellis counterfactual¶
If we removed this change a week after shipping: - Priya wouldn't notice — the modes had no surface before either, so "no surface → no surface" is a null change. That's the point: the old state was invisible to her. - Suresh would complain if he'd already paid — his reason to pay disappears. - Dr. Anita would complain loudly — having all 7 chips inline is a real convenience gain she'd feel.
Honest prediction: week 1, quiet. Month 1, if B2C free→paid conversion is flat and trainer mode-distribution flat, we'll know the chip surface alone isn't enough — the upstream PRD's copy + onboarding hypotheses are the weaker link.
Open questions¶
- Do we relabel
question_bank→Unit Testfor B2C users after funnel analytics (#569) lands? Default: no. - Does the
QuotaPillbelong only in the chat header, or also in the sidebar when the user isn't in chat? Current scope: header only. - Should the
Moreremoval also land a story in@kwilo/uifor the flex-wrap chip row pattern? Currently the wrap is local toModeChipRow; if another surface wants it, extract later.
Changelog¶
- 2026-04-23 — Doc created alongside implementation on
feat/b2c-dashboard. Product Mindset Gate answered in conversation; captured under Why this exists.