Skip to content

feat(conversation): add ConversationThread - #126

Open
pratikbuilds wants to merge 2 commits into
conversation/11-mediafrom
conversation/14-conversation-thread
Open

pratikbuilds wants to merge 2 commits into
conversation/11-mediafrom
conversation/14-conversation-thread

Conversation

@pratikbuilds

Copy link
Copy Markdown
Collaborator

ConversationThread renders a reply conversation headed by its parent message, with a divider counting the replies, built from the same parts as the main conversation through a render prop. Each thread has its own Conversation, so a reply settles at the top of its own log, and in a keep-mounted panel tab a thread keeps its place and the host's draft while the reader switches tabs. The thread's PromptInput sits below the log, and aria-label or aria-labelledby names the log.

Stacked on #125: review only this commit. Part of #94.

Notes for review

  • Props: parent, replies, renderMessage(message), sentId (this thread only), scrollPolicy (default settle), labels {log, replies(count)}; aria-label or aria-labelledby name the log; children (the thread's PromptInput) sit below the log. Stateless: no "use client".
  • A thread that is unmounted, or given no size while hidden, loses its place: use PanelContent keepMounted.

Verification

  • This commit passes bun run typecheck and bun run lint on its own (the stack is checked commit by commit).
  • At the top of the stack: bun run check, bun run build, bun run dep-guard, bun run contrast-test and bun run test:e2e (Playwright specs and the Storybook axe sweep in light and dark) pass.

Closes #108

@corbits-triage

corbits-triage Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Triage: Unsure

Waiting on:

  • checks were cancelled and need a re-run: check

Stacked on #125: only this pull request's own changes were checked.

Notes:

  • added code matches a code pattern: src/ui/conversation-thread.tsx:27 Nested ternary; use if statements or a lookup., stories/chat/conversation-thread.stories.tsx:390 Invented fallback `?? 0`; handle the missing value instead., stories/chat/conversation-thread.stories.tsx:295 Invented fallback `?? []`; handle the missing value instead., stories/chat/conversation-thread.stories.tsx:384 Invented fallback `?? []`; handle the missing value instead.

Waiting on a maintainer to approve: assign.

Changed since the last verdict (Pass at dba5b9e): 33 new commits.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated multi-agent review: independent review + second-agent validation; only confirmed items listed. Clean composition PR — nothing blocking.

  • minor src/ui/conversation-thread.tsx:44-49 (vs src/ui/conversation.tsx:601-621) — the sentId doc says a foreign id "moves nothing", which holds under settle but not stick: sent(id) glides to the end before any registry lookup, so any foreign-id change glides the thread to its end. Qualify the doc ("under settle…") or guard the stick branch on registry membership.
  • file-for-later (plan follow-up) src/ui/conversation-thread.tsx:96-113 — no windowing slot: ConversationEarlier must sit first inside ConversationContent, but the thread owns the log's children and renders children outside the log as the input slot; the divider count is hardwired to replies.length. An optional earlier?: ReactNode slot plus seeding the count from parent.replies.count would close it.
  • nits — labels.log is documented as the name "when no aria-labelledby is given" but an aria-label also overrides it (:18-20, 99-105); data-thread-parent/data-thread-divider are set but undocumented as extension hooks; the ChatWithThreads story keeps a stale replies count on the main conversation after a send (update message.replies.count in the send handler or note it in the story).

@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch from 4bc9068 to dba5b9e Compare October 27, 2026 08:38
@pratikbuilds
pratikbuilds force-pushed the conversation/11-media branch from fb3faf7 to 9660d62 Compare October 27, 2026 08:38
@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch from dba5b9e to b402e73 Compare October 27, 2026 13:25
@pratikbuilds
pratikbuilds force-pushed the conversation/11-media branch from 40def38 to bc0fa3e Compare October 27, 2026 13:54
@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch from b402e73 to 5f8dcec Compare October 27, 2026 13:54
@pratikbuilds

pratikbuilds commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Here's what happened to each point.

Fixed

1. The sentId doc was wrong under stick (minor)

2. Nits

  • The labels.log doc now says it's used when neither aria-label nor aria-labelledby is given.
  • data-thread-parent and data-thread-divider are documented as styling hooks.
  • The reply count in the story now updates after a send. The replies live in the chat, and the count under the parent comes from them.

Not changed, and why

  • A slot for loading earlier replies inside a thread: left as the follow-up you filed. It needs the thread to run useHistoryWindow itself.

Commit: docs(conversation): correct ConversationThread docs and story count

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review · Approve

Adds ConversationThread. Scroll policy and sent id pass through, the log is named, and history and the map stay with the host.

Findings

No findings.

ConversationThread renders a reply conversation headed by its parent
message, with a divider counting the replies, built from the same parts
as the main conversation through a render prop. Each thread has its own
Conversation, so a reply settles at the top of its own log, and in a
keep-mounted panel tab a thread keeps its place and the host's draft
while the reader switches tabs. The thread's PromptInput sits below the
log, and aria-label or aria-labelledby names the log.
The sentId doc said an id from another conversation moves nothing.
Say instead that an id which is not one of the thread's items is
ignored, matching the engine guard the Conversation change adds.

Also note that an aria-label, like aria-labelledby, overrides
labels.log, and document data-thread-parent and data-thread-divider
as styling hooks.

In the ChatWithThreads story the thread's replies lived in the tab,
so the count under the parent never moved after a send. Keep them in
the chat and derive the count from them, as the full-chat story does.
Since the replies now outlive the tab, reply ids come from the
thread's earlier sends, and stopping or closing the tab mid-answer
marks the answer sent rather than leaving it streaming.
@pratikbuilds
pratikbuilds force-pushed the conversation/11-media branch from d86517e to c9738df Compare October 28, 2026 08:41
@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch from c8ba54f to 145cfc4 Compare October 28, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants