fix(desktop): refine inbox context header

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Thomas Petersen
2026-05-15 07:55:25 -04:00
parent babfbe2f00
commit 1e2ccec60e
5 changed files with 71 additions and 66 deletions

View File

@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useChannelsQuery } from "@/features/channels/hooks";
import { useIdentityQuery } from "@/shared/api/hooks";
import { HomeScreen } from "@/features/home/ui/HomeScreen";
@@ -9,6 +10,7 @@ export const Route = createFileRoute("/")({
});
function HomeRouteComponent() {
const { goChannel } = useAppNavigation();
const channelsQuery = useChannelsQuery();
const identityQuery = useIdentityQuery();
const channels = channelsQuery.data ?? [];
@@ -18,6 +20,9 @@ function HomeRouteComponent() {
<HomeScreen
availableChannelIds={availableChannelIds}
currentPubkey={identityQuery.data?.pubkey}
onOpenContext={(channelId, messageId) => {
void goChannel(channelId, { messageId });
}}
/>
);
}

View File

@@ -5,11 +5,13 @@ import { HomeView } from "@/features/home/ui/HomeView";
type HomeScreenProps = {
availableChannelIds: ReadonlySet<string>;
currentPubkey?: string;
onOpenContext: (channelId: string, messageId: string) => void;
};
export function HomeScreen({
availableChannelIds,
currentPubkey,
onOpenContext,
}: HomeScreenProps) {
const homeFeedQuery = useHomeFeedQuery();
@@ -33,6 +35,7 @@ export function HomeScreen({
}
feed={homeFeedQuery.data}
isLoading={homeFeedQuery.isLoading}
onOpenContext={onOpenContext}
onRefresh={() => {
void homeFeedQuery.refetch();
}}

View File

@@ -104,6 +104,7 @@ type HomeViewProps = {
errorMessage?: string;
currentPubkey?: string;
availableChannelIds: ReadonlySet<string>;
onOpenContext: (channelId: string, messageId: string) => void;
onRefresh: () => void;
};
@@ -113,6 +114,7 @@ export function HomeView({
errorMessage,
currentPubkey,
availableChannelIds,
onOpenContext,
onRefresh,
}: HomeViewProps) {
const [filter, setFilter] = React.useState<InboxFilter>("all");
@@ -355,6 +357,7 @@ export function HomeView({
item={selectedItem}
messages={contextMessages}
replies={selectedItemReplies}
contextChannelName={selectedChannel?.name ?? null}
onDelete={() => {
if (!selectedItem || !canDelete) {
return;
@@ -369,6 +372,7 @@ export function HomeView({
setIsDeletingMessage(false);
});
}}
onOpenContext={onOpenContext}
onSendReply={async ({
content,
mediaTags,

View File

@@ -44,7 +44,9 @@ type InboxDetailPaneProps = {
item: InboxItem | null;
messages?: InboxContextMessage[];
replies?: InboxReply[];
contextChannelName?: string | null;
onDelete: () => void;
onOpenContext?: (channelId: string, messageId: string) => void;
onSendReply: (input: {
content: string;
mediaTags?: string[][];
@@ -88,7 +90,9 @@ export function InboxDetailPane({
item,
messages = [],
replies = [],
contextChannelName = null,
onDelete,
onOpenContext,
onSendReply,
onToggleDone,
onToggleReaction,
@@ -179,6 +183,11 @@ export function InboxDetailPane({
id: replyTarget.id,
}
: null;
const channelContextName = contextChannelName ?? item.channelLabel;
const contextLabel = channelContextName
? `#${channelContextName}`
: item.categoryLabel;
const contextChannelId = item.item.channelId;
const handleSelectReplyTarget = (message: InboxDisplayMessage) => {
setReplyTargetId((currentReplyTargetId) =>
@@ -193,76 +202,60 @@ export function InboxDetailPane({
data-testid="home-inbox-detail"
ref={detailPaneRef}
>
{!canOpenChannel ? (
<div className="px-6 pb-4 pt-14">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex min-w-0 items-center gap-3">
<UserAvatar
avatarUrl={item.avatarUrl}
className="h-8 w-8 rounded-xl"
displayName={item.senderLabel}
size="md"
/>
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<p className="truncate text-base font-semibold">
{item.senderLabel}
</p>
<span
className={cn(
"inline-flex items-center text-[10px] font-semibold uppercase tracking-[0.14em]",
item.isActionRequired
? "text-amber-600 dark:text-amber-300"
: "text-primary",
)}
>
{item.categoryLabel}
</span>
{item.channelLabel ? (
<span className="inline-flex items-center text-[11px] font-medium text-muted-foreground">
#{item.channelLabel}
</span>
) : null}
</div>
<div className="mt-1 flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<span>{item.fullTimestampLabel}</span>
<span>Inbox only</span>
</div>
</div>
<div className="px-6 pb-1 pt-14">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-center gap-2">
{canOpenChannel && contextChannelId && onOpenContext ? (
<button
className="truncate text-left text-sm font-semibold text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
onClick={() => onOpenContext(contextChannelId, item.id)}
type="button"
>
{contextLabel}
</button>
) : (
<p className="truncate text-sm font-semibold text-foreground">
{contextLabel}
</p>
)}
</div>
<div className="flex shrink-0 items-center gap-4">
<TooltipProvider delayDuration={200}>
<div className="flex items-center gap-4">
<div className="flex items-center gap-0.5">
<HeaderIconAction
label={isDone ? "Mark unread" : "Mark done"}
onClick={onToggleDone}
icon={
isDone ? (
<MailOpen className="h-4 w-4" />
) : (
<CheckCheck className="h-4 w-4" />
)
}
/>
</div>
{canDelete ? (
<HeaderMoreMenu
isDeletingMessage={isDeletingMessage}
onDelete={onDelete}
/>
) : null}
</div>
</TooltipProvider>
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-2 text-xs text-muted-foreground">
<span>{item.fullTimestampLabel}</span>
</div>
</div>
<div className="flex shrink-0 items-center gap-4">
<TooltipProvider delayDuration={200}>
<div className="flex items-center gap-4">
<div className="flex items-center gap-0.5">
<HeaderIconAction
label={isDone ? "Mark unread" : "Mark done"}
onClick={onToggleDone}
icon={
isDone ? (
<MailOpen className="h-4 w-4" />
) : (
<CheckCheck className="h-4 w-4" />
)
}
/>
</div>
{canDelete ? (
<HeaderMoreMenu
isDeletingMessage={isDeletingMessage}
onDelete={onDelete}
/>
) : null}
</div>
</TooltipProvider>
</div>
</div>
) : null}
</div>
<div className="relative min-h-0 flex-1 overflow-hidden">
<div className="absolute inset-0 overflow-y-auto overscroll-contain pb-32 pt-14">
<div className="absolute inset-0 overflow-y-auto overscroll-contain pb-32 pt-1">
<div>
{isThreadContextLoading ? (
<div className="px-6 pb-3 text-[11px] text-muted-foreground">

View File

@@ -29,8 +29,8 @@ export function InboxListPane({
selectedId,
}: InboxListPaneProps) {
return (
<section className="flex min-h-0 min-w-0 flex-col overflow-hidden border-r border-border/70 bg-background/60">
<div className="px-4 pb-3 pt-14">
<section className="flex min-h-0 min-w-0 flex-col overflow-hidden bg-background/60">
<div className="px-5 pb-3 pt-14">
<div className="flex flex-nowrap gap-1">
{FILTER_OPTIONS.map((option) => (
<Button
@@ -72,7 +72,7 @@ export function InboxListPane({
return (
<button
className={cn(
"flex w-full items-start gap-2.5 border-l px-4 py-2 text-left transition-colors",
"flex w-full items-start gap-2.5 border-l px-5 py-2 text-left transition-colors",
isSelected
? "border-l-primary bg-muted/30"
: "border-l-transparent hover:bg-muted/25 active:bg-muted/40",