mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): keep thread-open affordance in archived channels (#4012)
## Problem
Threaded replies "disappeared" from archived Buzz channels: the **"N
replies →"** summary row and the huddle-started **"View thread"** button
vanished, so existing threads were unreachable from the channel
timeline. The thread data was intact — this was a UI gate, not data
loss.
## Root cause
A single `onReply` prop drove two distinct affordances:
- the **compose** affordances (hover "Reply" button, inline reply
target), and
- the **view** affordances ("N replies →" summary row, huddle "View
thread").
`ChannelPane` nulls `onReply` on archived channels to keep them
read-only. That correctly hid composing — but also hid the view
affordances, since they keyed off the same prop.
## Fix
Two independent props, one per concern:
- **`onReply`** drives the compose affordances and is gated on
`archivedAt` — nulled on archived channels, so no new replies can be
started.
- **`onOpenThread`** drives the view affordances and is passed
regardless of archived state, threaded `ChannelPane → MessageTimeline →
TimelineMessageList → MessageRow`.
Opening a thread on an archived channel is read-only: the thread panel's
composer is independently gated via `isComposerDisabled` (includes
`archivedAt !== null`, `ChannelPane.tsx:318`).
### Before
<img width="811" height="794" alt="Screenshot 2026-07-31 at 20 26 00"
src="https://github.com/user-attachments/assets/670d9db4-30da-4c6d-97dc-275b5dbebca8"
/>
### After
<img width="873" height="791" alt="Screenshot 2026-07-31 at 20 28 04"
src="https://github.com/user-attachments/assets/88525231-2539-4eb3-8117-8e58a0cb3855"
/>
## Validation
- `pnpm typecheck` clean
- biome lint clean on touched files
- full `pnpm test` suite green (3885 tests)
- pre-push `branch-skew` / `desktop-check` / `desktop-test` hooks passed
Signed-off-by: Trey Wood <treyw@squareup.com>
Co-authored-by: npub14h0tw3uj7jm77qfxcwn6um2s5h55l0klrt2w9srzp3m3yvjc0mpsjsuk6e <addeb74792f4b7ef0126c3a7ae6d50a5e94fbedf1ad4e2c0620c771232587ec3@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
npub14h0tw3uj7jm77qfxcwn6um2s5h55l0klrt2w9srzp3m3yvjc0mpsjsuk6e
parent
19d57b0d46
commit
8e81afa431
@@ -681,6 +681,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
onMarkUnread={onMarkUnread}
|
||||
onMarkRead={onMarkRead}
|
||||
onReply={activeChannel?.archivedAt ? undefined : onOpenThread}
|
||||
onOpenThread={onOpenThread}
|
||||
channelName={activeChannel?.name}
|
||||
channelType={activeChannel?.channelType ?? null}
|
||||
isSendingVideoReviewComment={isSending}
|
||||
|
||||
@@ -88,6 +88,7 @@ export const MessageRow = React.memo(
|
||||
onMarkRead,
|
||||
onToggleReaction,
|
||||
onReply,
|
||||
onOpenThread,
|
||||
onEntranceComplete,
|
||||
playEntrance = false,
|
||||
onUnfollowThread,
|
||||
@@ -135,6 +136,7 @@ export const MessageRow = React.memo(
|
||||
remove: boolean,
|
||||
) => Promise<void>;
|
||||
onReply?: (message: TimelineMessage) => void;
|
||||
onOpenThread?: (message: TimelineMessage) => void;
|
||||
onUnfollowThread?: (message: TimelineMessage) => void;
|
||||
onEntranceComplete?: (messageId: string) => void;
|
||||
playEntrance?: boolean;
|
||||
@@ -335,7 +337,7 @@ export const MessageRow = React.memo(
|
||||
<HuddleAttachment
|
||||
channelId={channelId}
|
||||
message={message}
|
||||
onOpenThread={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
||||
@@ -755,6 +755,7 @@ export function MessageThreadPanel({
|
||||
onMarkUnread={onMarkUnread}
|
||||
onMarkRead={onMarkRead}
|
||||
onReply={onSelectReplyTarget}
|
||||
onOpenThread={onExpandReplies}
|
||||
onToggleReaction={onToggleReaction}
|
||||
profiles={profiles}
|
||||
showDepthGuides={shouldShowThreadBranchGuides}
|
||||
|
||||
@@ -84,6 +84,7 @@ type MessageTimelineProps = {
|
||||
onMarkUnread?: (message: TimelineMessage) => void;
|
||||
onMarkRead?: (message: TimelineMessage) => void;
|
||||
onReply?: (message: TimelineMessage) => void;
|
||||
onOpenThread?: (message: TimelineMessage) => void;
|
||||
isSendingVideoReviewComment?: boolean;
|
||||
onSendVideoReviewComment?: (
|
||||
message: TimelineMessage,
|
||||
@@ -178,6 +179,7 @@ const MessageTimelineBase = React.forwardRef<
|
||||
onMarkUnread,
|
||||
onMarkRead,
|
||||
onReply,
|
||||
onOpenThread,
|
||||
channelName,
|
||||
channelType,
|
||||
isSendingVideoReviewComment = false,
|
||||
@@ -635,6 +637,7 @@ const MessageTimelineBase = React.forwardRef<
|
||||
onMarkUnread={onMarkUnread}
|
||||
onMarkRead={onMarkRead}
|
||||
onReply={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
isSendingVideoReviewComment={isSendingVideoReviewComment}
|
||||
onSendVideoReviewComment={onSendVideoReviewComment}
|
||||
onStartReached={loadOlderViaVirtualizer}
|
||||
|
||||
@@ -77,6 +77,7 @@ type TimelineMessageListProps = {
|
||||
onMarkUnread?: (message: TimelineMessage) => void;
|
||||
onMarkRead?: (message: TimelineMessage) => void;
|
||||
onReply?: (message: TimelineMessage) => void;
|
||||
onOpenThread?: (message: TimelineMessage) => void;
|
||||
isSendingVideoReviewComment?: boolean;
|
||||
onSendVideoReviewComment?: (
|
||||
message: TimelineMessage,
|
||||
@@ -142,6 +143,7 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
|
||||
onMarkUnread,
|
||||
onMarkRead,
|
||||
onReply,
|
||||
onOpenThread,
|
||||
isSendingVideoReviewComment = false,
|
||||
onSendVideoReviewComment,
|
||||
onToggleReaction,
|
||||
@@ -255,6 +257,7 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
|
||||
onMarkRead={onMarkRead}
|
||||
onMarkUnread={onMarkUnread}
|
||||
onReply={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
onToggleReaction={onToggleReaction}
|
||||
profiles={profiles}
|
||||
searchActiveMessageId={searchActiveMessageId}
|
||||
@@ -286,6 +289,7 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
|
||||
onMarkRead,
|
||||
onMarkUnread,
|
||||
onReply,
|
||||
onOpenThread,
|
||||
onToggleReaction,
|
||||
profiles,
|
||||
ownerProfiles,
|
||||
@@ -699,6 +703,7 @@ type MessageRowItemProps = Pick<
|
||||
| "onMarkUnread"
|
||||
| "onMarkRead"
|
||||
| "onReply"
|
||||
| "onOpenThread"
|
||||
| "onToggleReaction"
|
||||
| "profiles"
|
||||
| "searchActiveMessageId"
|
||||
@@ -737,6 +742,7 @@ function MessageRowItem({
|
||||
onMarkUnread,
|
||||
onMarkRead,
|
||||
onReply,
|
||||
onOpenThread,
|
||||
onToggleReaction,
|
||||
profiles,
|
||||
searchActiveMessageId,
|
||||
@@ -755,7 +761,7 @@ function MessageRowItem({
|
||||
const canDelete = canManage && onDelete ? onDelete : undefined;
|
||||
const canEdit = canManage && onEdit ? onEdit : undefined;
|
||||
|
||||
if (summary && onReply) {
|
||||
if (summary && onOpenThread) {
|
||||
const isHighlighted = message.id === highlightedMessageId;
|
||||
return (
|
||||
<div
|
||||
@@ -790,6 +796,7 @@ function MessageRowItem({
|
||||
onMarkUnread={onMarkUnread}
|
||||
onToggleReaction={onToggleReaction}
|
||||
onReply={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
onUnfollowThread={
|
||||
unfollowThreadById
|
||||
? () => unfollowThreadById(message.id)
|
||||
@@ -802,7 +809,7 @@ function MessageRowItem({
|
||||
<MessageThreadSummaryRow
|
||||
depth={message.depth}
|
||||
message={message}
|
||||
onOpenThread={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
showDepthGuides={false}
|
||||
summary={summary}
|
||||
summaryIndentOffsetRem={-THREAD_REPLY_ROW_MARGIN_INLINE_REM}
|
||||
@@ -839,6 +846,7 @@ function MessageRowItem({
|
||||
onMarkUnread={onMarkUnread}
|
||||
onToggleReaction={onToggleReaction}
|
||||
onReply={onReply}
|
||||
onOpenThread={onOpenThread}
|
||||
profiles={profiles}
|
||||
searchQuery={isSearchMatch ? searchQuery : undefined}
|
||||
showDepthGuides={false}
|
||||
|
||||
Reference in New Issue
Block a user