Skip to content

feat(conversation): add ConversationThread - #126

Merged
TheGreatAxios merged 2 commits into
conversation/11-mediafrom
conversation/14-conversation-thread
Oct 28, 2026
Merged

TheGreatAxios merged 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

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.
@corbits-triage

corbits-triage Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Triage: In progress

Triage is waiting for CI to finish before it decides. This comment shows the verdict once it has.

@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).

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/14-conversation-thread branch from dba5b9e to b402e73 Compare October 27, 2026 13:25
@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.

@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch 2 times, most recently from 145cfc4 to 4501612 Compare October 28, 2026 11:28
@pratikbuilds
pratikbuilds force-pushed the conversation/14-conversation-thread branch from 4501612 to e1ebaba Compare October 28, 2026 14:47
@pratikbuilds

Copy link
Copy Markdown
Collaborator Author

Round 3

No new findings on this PR. It is rebased onto the updated PR below; its own diff is unchanged.

bun run check and dep-guard pass on every branch of the stack, and the full e2e suite passes at the top of the stack (163/163, including the stories axe sweep). Each behaviour fix comes with a test that fails without it; the forwardRef tests can only guard, since the suite runs on React 19.

@TheGreatAxios
TheGreatAxios added this pull request to stack #130 October 28, 2026 21:28
@TheGreatAxios
TheGreatAxios removed this pull request from stack #130 October 28, 2026 21:38
@TheGreatAxios
TheGreatAxios added this pull request to stack #139 October 28, 2026 21:38
@TheGreatAxios
TheGreatAxios force-pushed the conversation/14-conversation-thread branch from e1ebaba to 87dcfa6 Compare October 28, 2026 21:42
@TheGreatAxios
TheGreatAxios merged commit 464bace into main Oct 28, 2026
3 of 6 checks passed
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.

Add ConversationThread

2 participants