Skip to content

AI Tutor — Resume Conversation

⚠️ Superseded (2026-06-21) by chat-new-and-search.md. The auto-resume-on-entry behavior described below was removed: "New Chat" now always opens a fresh composer, and resuming is an explicit action via "Search Chats" or a deep link. The self-flagged hypothesis here ("auto-resume over a blank box is a reasoned guess pending validation") did not hold up in dogfooding — the default blocked users from starting a new chat.

Status: superseded LNO: L — Leverage (restores a broken return path to the primary surface) Owners: @bhanu49 Last updated: 2026-06-09 Roles affected: Learner · Trainer · B2C User Primary routes: /new-chat Source of the ask: Internal observation — owner-initiated, single reproduction, flagged as hypothesis

TL;DR

When a learner or trainer clicks "AI Tutor" in the sidebar, they land on /new-chat. If they were mid-conversation recently and haven't started a new one, the page now redirects them back to that conversation instead of showing a blank composer. The gate reads the server conversation list, so it survives hard reloads and tab restores. Flows that explicitly request a fresh chat (the New Chat button, intent routes, prefill launches) always bypass the gate.

Why this exists

Who asked for it

  • Primary source: Owner-initiated (Bhanu), from a reproduction during regular use on 2026-06-09. Flagged as a single-observation hypothesis — not derived from aggregate session data or user interviews.
  • Self-initiated: yes. The lost-thread behavior is an objective gap, but the preference for auto-resume over showing a blank box is a reasoned guess pending broader validation.

What user pain does it solve

"I asked the tutor something, glanced away, came back, and my conversation was gone — a blank box. I had to hunt the sidebar to find what I was just doing."

Any navigation that does not carry the conversation URL drops the active thread. The sidebar "AI Tutor" link goes to /new-chat, which previously always rendered a blank composer. A learner returning mid-question loses context immediately.

Cost of not doing it

Any navigation to the sidebar link during an active session drops the thread. The AI tutor is the primary surface for learners and B2C users — losing threads erodes trust and is worst for a learner who is mid-question and expecting continuity. Without this fix, a hard refresh or sidebar click during an active session silently discards the conversation reference from the in-memory store.

Validation

Moderate. The lost-thread behavior reproduces deterministically — it is an objective gap, not a perception issue. The preference for auto-resume is a reasoned inference (returning users expect continuity), not a validated user preference. The 24-hour recency window is a conservative heuristic to avoid dumping users into stale week-old threads; this threshold is not user-validated and may need adjustment.

Personas

  • Priya, 2nd-year CSE learner. Opens the tutor mid-afternoon, asks a question, gets called away, returns via the sidebar — expects to land back in the same thread.
  • Arjun, software trainer. Uses the tutor between back-to-back sessions. Sidebar click mid-day should restore his last thread, not show a blank box.

ICP exclusions

  • K-12 learners using /learn/:intent intent routes — those flows seed a specific context and are not affected by this gate.
  • Mobile app users — this is a web-only change.
  • Learners with only archived conversations or conversations older than 24 hours — they see a fresh blank composer, same as before.

Outcomes for the user

  • Priya clicks "AI Tutor" in the sidebar after stepping away and lands directly in her recent conversation.
  • Hard refresh on /new-chat restores the conversation because the gate reads from the server, not the in-memory store.
  • Clicking "New chat" or using an intent link always opens a blank composer — the gate does not interfere.

Non-goals

  • No user-facing toggle or setting for resume behavior.
  • No backend API change — the gate uses the existing conversation list endpoint.
  • No separate persisted lastConversationId — the resumable conversation is derived from the server list on each mount.
  • No change to /ai-tutor/c/:conversationId or intent routes.

How it works

/new-chat now renders NewChatRoute instead of AITutorPage directly. On a bare visit (no location.state, no search params), NewChatRoute calls useConversations and passes the result to findResumableConversation. If the most-recent conversation has messages and its last_message_at is within RESUME_WINDOW_MS (24 hours), the route renders a <Navigate> to /ai-tutor/c/:id. Otherwise it renders AITutorPage with a blank composer.

The "New Chat" button and any prefill or intent flow always carry either location.state or search params, so they render AITutorPage directly and the resume logic never runs. Cold reloads resume because the gate reads from the TanStack Query cache backed by the server, not the in-memory Zustand store.

useTutorHomePrefill no longer contains resume logic — the resume decision type and its navigate call have been removed. The hook now only handles: defer (while a launch prompt is pending) and reset (fresh blank session). This removes the in-memory store dependency (useConversationId) from the prefill hook.

Design decisions

Decision 1: Route gate over in-memory store resume

Chose: NewChatRoute gate that reads the server conversation list.

Why: The prior approach called navigate() inside useTutorHomePrefill based on useConversationId() from the Zustand store. That store is empty on cold load (hard reload, new tab), so the feature silently failed in the most common return scenario. A route gate that reads the server list works on every entry, including tab restores.

Alternatives rejected: - In-memory store-only resume (the prior approach) — dies on hard reload and tab restore; no recency gate. - Persisting lastConversationId to localStorage — adds a second source of truth that can go stale (archived conversation, deleted conversation); derived from the conversation list is simpler and always correct.

Decision 2: Recency window of 24 hours

Chose: RESUME_WINDOW_MS = 24 * 60 * 60 * 1000.

Why: Resume should feel helpful, not disorienting. A learner returning the next day to "start fresh" should not be dropped into a week-old thread. 24 hours matches a natural work/study session boundary. The threshold is a heuristic — not user-validated — and can be changed in one place.

Alternatives rejected: - Always resume last, no recency window — dumps users into stale threads from last week or last month. - Shorter window (e.g. 1 hour) — too aggressive; a learner who steps away for dinner would lose the thread.

Decision 3: Bypass gate on location.state or search params

Chose: Any location.state != null or location.search.length > 0 skips the gate and renders AITutorPage directly.

Why: The New Chat button, HeroComposer prefills, and intent routes all carry either location state or search params. Without this bypass, the gate would hijack explicit new-session requests and redirect users back to their old conversation — exactly the wrong behavior.

Alternatives rejected: - Always-resume with no bypass — hijacks prefill flows and intent sessions. - Per-flow flags passed as props — more coupling than a simple state/search check.

Risks and mitigations

  • Risk: A learner clicks "New Chat" intending to start fresh but is redirected to their old thread if the button doesn't set location.state. Mitigation: The New Chat button was verified to set location.state; any new entry point that expects a blank session must do the same. The bypass check is documented in this file.
  • Risk: The recency threshold is wrong for some learner cohorts (e.g., weekend learners returning Monday). Mitigation: The threshold is a single named constant (RESUME_WINDOW_MS) and can be adjusted without touching component logic.

Success signals

What good looks like

Priya clicks "AI Tutor" in the sidebar after a 30-minute break and lands in her thread without touching the sidebar conversation list. Hard refresh on /new-chat restores the same thread.

Observable signals

  • Navigation to /new-chat during an active session no longer shows a blank composer.
  • Hard refresh and tab restore on /new-chat resume the active thread.
  • "New Chat" button, intent routes, and prefill launches still open a blank composer.

Sean Ellis counterfactual

If we removed this a week after shipping, learners would start filing "it keeps forgetting my chat" complaints with specifics: sidebar click loses the thread, hard refresh loses the thread. The blank-box-after-navigation symptom is distinctive enough that complaints would be specific rather than vague. If we hear silence on removal, the feature was not solving a real pain at scale.

Changelog

  • 2026-06-09 — Initial implementation. Replaces the in-memory store-resume from PR #1061. NewChatRoute wired into SharedRoutes.tsx; useTutorHomePrefill resume branch removed; conversationEntry.ts simplified to three decisions (noop, defer, reset).