Skip to content

FRD: Inbox Preview Panel

Document Summary

FieldDetails
Feature NameInbox Preview Panel
StatusDraft
OwnerDavid Holmes
ContributorsDesign, Engineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #52
Last Updated2026-05-26

Introduction

Overview

Inbox Preview Panel is a messaging widget that renders a compact list of inbox threads showing sender, subject snippet, timestamp, and unread count. It builds on the existing InboxThreadList pattern component, elevating it into a self-contained panel widget with header, unread badge, and integration-ready callbacks. The widget is designed for SaaS sidebar panels, notification centers, and messaging overviews.

Goals

  • Deliver an <InboxPreviewPanel> component that wraps InboxThreadList with a panel header, unread count badge, and thread-selection callback.
  • Display sender name/avatar, subject or snippet, timestamp, and unread indicator per thread.
  • Support loading, empty, and error states.
  • Ship Storybook stories with realistic inbox data.

Non-Goals

  • Full email client functionality (compose, forward, archive, search).
  • Message body rendering (this is a preview list, not a reader pane).
  • Real-time sync or push notification integration.
  • Pagination or infinite scroll (deferred to future enhancement).

Scope

In Scope

AreaDescription
Component<InboxPreviewPanel> wrapping InboxThreadList with panel chrome
Panel HeaderTitle, total unread count badge, optional action button (e.g., “Mark all read”)
Thread ItemsSender avatar, sender name, snippet, timestamp, unread dot
SelectiononThreadSelect callback when a thread is clicked
StatesLoading skeleton, empty inbox, error state
StoriesStorybook stories for populated inbox, empty inbox, loading, error, and themed variants

Out of Scope

AreaReason
Message body / reader paneSeparate component; this is a preview list only
Compose / replyHandled by Compose Panel widget (#53)
Thread actions (archive, delete, star)Future enhancement; initial version is read + select
Search / filterFuture enhancement
Pagination / infinite scrollFuture enhancement

Users and Pain Points

User Groups

UserDescriptionNeeds
DevelopersEngineers building SaaS messaging featuresA ready-made inbox preview using DS components
DesignersDesign-system consumersConsistent inbox UX aligned with DS tokens
End UsersSaaS application usersQuick scan of recent messages with clear unread indicators

Pain Points

UserPain PointImpact
DevelopersBuilding inbox previews requires assembling avatar, text truncation, timestamp formatting, and unread indicators from scratchSlow development, inconsistent results
End UsersInconsistent inbox UIs across SaaS apps make it hard to quickly identify unread messagesMissed messages, poor UX

Definitions

TermDefinition
Inbox ThreadA conversation thread in the inbox, represented by its most recent message
SnippetA truncated preview of the most recent message text
Unread CountThe total number of threads with unread messages
Preview PanelA compact panel showing a list of thread summaries

Current State

Existing Behavior

InboxThreadList in src/components/patterns/inbox-thread-list.tsx renders a list of inbox threads. It exists as a pattern component with stories but is not wrapped in a panel with header, unread count, or standardized callbacks.

Current Limitations

  • InboxThreadList is a raw list without panel chrome (header, badge, actions).
  • No standardized empty, loading, or error state handling at the panel level.
  • No unread count badge aggregation.

Existing Workarounds

  • Developers wrap InboxThreadList in custom panel containers and add their own headers and badges.

Proposed Solution

Summary

Introduce <InboxPreviewPanel> that wraps InboxThreadList with a panel header containing a title, unread count badge, and optional action slot. The component accepts a threads array, renders them via the existing list pattern, and calls onThreadSelect when a thread is clicked.

Key Capabilities

  • Panel header with title and unread count badge.
  • Optional header action (e.g., “Mark all read” button).
  • Thread list rendering via InboxThreadList composition.
  • Thread selection callback.
  • Empty inbox state with configurable message.
  • Loading skeleton matching thread item shapes.
  • Error state with retry.

User Experience

Users see a panel with a header showing “Inbox” and an unread count badge (e.g., “3”). Below is a scrollable list of thread previews. Each thread shows a sender avatar, name, message snippet (truncated), and relative timestamp. Unread threads have a dot indicator and bolder text. Clicking a thread fires the selection callback.

Developer Experience

<InboxPreviewPanel
title="Inbox"
threads={threads}
onThreadSelect={(threadId) => openThread(threadId)}
onMarkAllRead={() => markAllRead()}
/>

Requirements

IDRequirementPriorityNotes
FR-001InboxPreviewPanel renders a panel header with title and unread badgeMust-
FR-002Thread list renders sender, snippet, timestamp, and unread indicatorMustDelegates to InboxThreadList
FR-003onThreadSelect fires when a thread item is clickedMust-
FR-004Optional “Mark all read” action in headerShould-
FR-005Loading, empty, and error states are handledMust-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001threads prop accepts InboxThread[] with id, senderName, senderAvatar, snippet, timestamp, and isUnread fieldsTyped thread data structureMust
FUNC-002Unread count badge in header auto-calculates from threads.filter(t => t.isUnread).lengthNo manual count managementMust
FUNC-003Thread items show sender avatar (via Avatar), sender name, truncated snippet, and relative timestampFamiliar inbox layoutMust
FUNC-004Unread threads display a dot indicator and bolder sender nameQuick visual identificationMust
FUNC-005Clicking a thread fires onThreadSelect(threadId)Navigation to thread detailMust
FUNC-006Header action slot accepts a button (e.g., “Mark all read”) with onClickBatch actionsShould
FUNC-007Empty state shows configurable message when threads is empty and not loadingClear feedbackMust
FUNC-008Loading skeleton renders 3-5 placeholder thread itemsSmooth loading transitionMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Renders in under 30ms for up to 50 threadsPerformanceMust
NFR-002Thread items are keyboard-navigable with arrow keysAccessibilityMust
NFR-003Works in light and dark themesThemingMust
NFR-004No new runtime dependenciesMaintainabilityMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
titlestringPanel header titleNo (default: “Inbox”)
threadsInboxPreviewThread[]Array of thread summariesYes
onThreadSelect(threadId: string) => voidThread click callbackYes
onMarkAllRead() => voidMark-all-read action callbackNo
isLoadingbooleanShow loading skeletonNo
errorstringError messageNo
onRetry() => voidRetry callbackNo
emptyMessagestringText for empty stateNo
classNamestringAdditional CSS classesNo

InboxPreviewThread Type

interface InboxPreviewThread {
id: string;
senderName: string;
senderAvatar?: string;
snippet: string;
timestamp: string;
isUnread: boolean;
}

Example Usage

import { InboxPreviewPanel } from "@/components/ui/inbox-preview-panel";
<InboxPreviewPanel
threads={[
{ id: "1", senderName: "Alice Chen", snippet: "Hey, can we sync on the Q3 roadmap?", timestamp: "2m ago", isUnread: true },
{ id: "2", senderName: "Bob Smith", snippet: "Updated the design spec with your feedback", timestamp: "1h ago", isUnread: false },
]}
onThreadSelect={(id) => navigate(`/inbox/${id}`)}
onMarkAllRead={() => api.markAllRead()}
/>

API Notes

  • Reuses concepts from existing InboxThreadList but with a simplified, widget-focused API.
  • Snippet truncation happens internally (max ~80 characters with ellipsis).
  • Timestamp is a pre-formatted string; the consumer handles relative time formatting.

Accessibility Requirements

IDRequirementNotes
A11Y-001Thread list has role="listbox" with aria-label describing the inbox-
A11Y-002Each thread item is role="option" and focusableArrow key navigation
A11Y-003Unread status is conveyed to screen readersaria-label includes “unread” for unread threads
A11Y-004Unread count badge has aria-label (e.g., “3 unread messages”)Not just visual number
A11Y-005Mark-all-read button has descriptive label-

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page with overview and props tableStorybookMust
DOC-002”When to use / When not to use” guidanceStorybook docsMust
DOC-003Stories for populated inbox, empty inbox, loading, and error statesStorybookMust
DOC-004Story demonstrating keyboard navigationStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
InboxThreadListEngineeringDesign SystemReadyExisting pattern component
AvatarEngineeringDesign SystemReadySender avatars
BadgeEngineeringDesign SystemReadyUnread count badge
ButtonEngineeringDesign SystemReadyHeader action
Design tokensDesignDesign SystemReady-

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
No pagination may not scale for users with large inboxesPerformance and usabilityDocument as a limitation; pagination is a planned follow-up
Simplified API compared to InboxThreadList may not cover all use casesPower users may need the raw componentExport both; InboxPreviewPanel is the opinionated widget, InboxThreadList remains available
Pre-formatted timestamp string puts formatting burden on consumerInconsistent time displayDocument recommended time formatting utility

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the panel support thread grouping (Today, Yesterday, Older)?David HolmesOpen
Q-002Should swipe-to-archive be supported on touch devices?David HolmesOpen
Q-003Should the panel support a compact mode with less padding?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001Panel header shows title and unread count badgeFR-001, FUNC-002
AC-002Thread items display sender avatar, name, snippet, timestamp, and unread dotFR-002, FUNC-003, FUNC-004
AC-003Clicking a thread fires onThreadSelect with the thread IDFR-003, FUNC-005
AC-004Mark-all-read button renders and fires callback when providedFR-004, FUNC-006
AC-005Empty state displays when threads is empty and not loadingFUNC-007
AC-006Loading skeleton displays placeholder thread itemsFUNC-008
AC-007All Storybook stories render without errorsDOC-003
AC-008Component passes axe accessibility auditNFR-002

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Compose InboxThreadList (or its internal patterns) for the thread list rendering.
  • Use Avatar for sender avatars, Badge for unread count, Button for actions.
  • Auto-calculate unread count from the threads array.
  • Place stories under the SaaS Widgets Storybook section.

LLM Should Not

  • Build a full email client.
  • Add pagination or infinite scroll.
  • Add search or filter functionality.
  • Modify existing InboxThreadList component.

Decision Log

DateDecisionReasonOwner
2026-05-26Wrap InboxThreadList rather than rebuild from scratchReuses existing pattern; avoids duplicationDavid Holmes
2026-05-26Consumer provides pre-formatted timestamp stringsAvoids opinionated date-formatting dependencyDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft