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-chatSource 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/:intentintent 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-chatrestores 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/:conversationIdor 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 setlocation.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-chatduring an active session no longer shows a blank composer. - Hard refresh and tab restore on
/new-chatresume 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.
NewChatRoutewired intoSharedRoutes.tsx;useTutorHomePrefillresumebranch removed;conversationEntry.tssimplified to three decisions (noop, defer, reset).