Skip to content

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, no More dropdown, 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/:id Source 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 Editor link. 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() returns undefined for them, so lockedModes is empty and renderDownload stays 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 the More drawer.
  • Not wiring RazorpayPaywallModal still link-outs to kwilo.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 More dropdown, add chips for B2C only. Rejected because it keeps trainer discovery broken and forks the chip-row logic by role.
  • Central FreemiumGate HOC around ChatEngine. Rejected because ChatEngine should stay freemium-agnostic — its job is to render the chat, not to decide subscription tiers. All B2C policy lives in ChatPage.
  • Compute lock state in ChatEngine from a plan prop. Same rejection: leaks B2C concepts into the shared engine. Current design: ChatPage derives lockedModes and passes it down; ChatEngine only knows "locked = true/false".
  • Pure-quota check for hard locks too (let backend return remaining: 0, limit: 0 for 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:

  1. In-chat research downloads (DOCX, MD): already wrapped by renderDownload in ChatPageDownloadLockButton. No change beyond adding pdf to the format union.
  2. In-chat presentation downloads: currently no inline buttons — presentations funnel through Open in Editor to the viewer page.
  3. Presentation viewer page (/presentations/:id): new code reads useB2CQuota() and, when plan === 'free', intercepts handleExport(pptx|pdf) → opens PaywallModal. 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 — added KWILO_B2C_MODES, getModesForUser, widened KwiloModeId to include learner IDs.
  • apps/web/src/pages/shared/AITutorPage/ChatEngine.tsx — computes modes via the selector, passes to ChatView; added pdf to renderDownload.format union.
  • apps/web/src/pages/shared/AITutorPage/components/ChatView.tsx — drops unused audience prop, forwards modes.
  • apps/web/src/pages/shared/AITutorPage/components/ChatInput/index.tsx — chip row now renders for all roles (gate changed from isTeacher to modes.length > 0).
  • apps/web/src/pages/shared/AITutorPage/components/ChatInput/ModeChipRow.tsx — removed More dropdown, all chips inline with flex-wrap.
  • apps/web/src/pages/b2c/ChatPage/index.tsx — hard+soft lockedModes memo, pdf in renderDownload, 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, intercepts pptx/pdf export for free users.
  • apps/web/src/i18n/locales/en/aiTutor.json — added aiTutor.presentationCard.downloadPptx.
  • apps/web/src/i18n/locales/en/b2c.json — added b2c.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 /chat as a free user. She sees Presentation and Exam Prep as 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 More dropdown.
  • 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_bankUnit Test for B2C users after funnel analytics (#569) lands? Default: no.
  • Does the QuotaPill belong only in the chat header, or also in the sidebar when the user isn't in chat? Current scope: header only.
  • Should the More removal also land a story in @kwilo/ui for the flex-wrap chip row pattern? Currently the wrap is local to ModeChipRow; 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.