Skip to content

ai-partner: mini-conversation component (inside peek) #1463

@CraigBuckmaster

Description

@CraigBuckmaster

Parent epic: #1446 (Amicus — AI Study Partner v1)
Phase: 3 · Size: M · Depends on: #1462 (peek), #1455 (streaming chat)

A 2-3 turn mini-conversation experience that lives inside the FAB peek sheet. Reuses the streaming and citation infrastructure from #1455 but keeps thread state ephemeral (not persisted until user promotes to a full thread via #1464).


Files to create

  • app/src/components/amicus/PeekMiniConversation.tsx — the component
  • app/src/hooks/usePeekConversation.ts — ephemeral conversation state hook
  • app/src/components/amicus/__tests__/PeekMiniConversation.test.tsx

Files to modify

  • app/src/components/amicus/AmicusPeekSheet.tsx — mount PeekMiniConversation when a chip or input triggers it

Ephemeral state

Conversation in the peek is NOT persisted to user.db until user taps "Continue in Amicus tab →" (#1464). Before that, it lives in component/hook state only. If user dismisses the peek without promoting, the conversation is discarded.

This is a deliberate UX choice: the peek is quick-and-disposable; if a conversation is worth keeping, the user explicitly escalates it.

usePeekConversation hook

export interface PeekMessage {
  role: 'user' | 'assistant';
  content: string;
  citations?: AmicusCitation[];
  isStreaming?: boolean;
}

export interface UsePeekConversation {
  messages: PeekMessage[];
  isStreaming: boolean;
  turnCount: number;                    // 0, 1, 2, 3
  error: AmicusError | null;
  send: (text: string, chapterRef?: ChapterRef) => Promise<void>;
  reset: () => void;                    // clear conversation
  snapshotForPromotion: () => PeekMessage[];  // called by #1464
}

Turn counting: a "turn" = user message + assistant response. At turn 3, the UI surfaces the handoff CTA. Send is not disabled at turn 3 — user can still continue, but handoff is strongly suggested.

Streaming: reuses streamChat from #1455 (same proxy endpoint). The difference is purely state location — messages live in hook memory, not user.db.

Component layout

Rendered inside the peek sheet below the chip area (chips hide once conversation starts).

┌─────────────────────────────────────┐
│ [drag handle]                       │
│ Amicus · Romans 9                   │
│ ─────────────────────────────────── │
│ [user bubble]                       │
│ [assistant bubble streaming...]     │
│ [follow-up chip] [chip] [chip]      │
│ ─────────────────────────────────── │
│ At turn 3:                          │
│ ┌─ Continue in Amicus tab → ──────┐ │
│ └─────────────────────────────────┘ │
│ ─────────────────────────────────── │
│ [input field]                  [→]  │
└─────────────────────────────────────┘

Reuses AssistantMessageBubble, UserMessageBubble, CitationPill, FollowUpChips from #1455. Do not duplicate rendering logic.

Differences from full thread

  • Auto-collapses if peek is closed (lose ephemeral state)
  • No thread title, no back button, no thread-level actions (pin/delete/rename)
  • Scroll contained within peek — doesn't interfere with underlying screen scroll

Citation interactions

Citation pill taps in the peek do navigate to source (via #1456). This dismisses the peek and navigates the underlying stack — user can then reopen the FAB to return.


Error states

  • Rate limit 429 → inline banner in peek: "You've used your monthly Amicus queries. Upgrade to Amicus+ or wait until [date]." + upgrade CTA
  • Offline → inline banner: "Amicus needs a connection."
  • Other errors → inline banner with retry

No error state collapses the peek — user can always dismiss manually.


Performance

  • First token ≤ 1.5s (same as full tab, uses same proxy)
  • Sheet height transitions smoothly as conversation grows
  • Streaming does not cause peek to re-layout its container (use internal scroll)

Acceptance criteria

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions