import {
CalendarClockIcon,
Clock3Icon,
HourglassIcon,
Loader2Icon,
TriangleAlertIcon,
} from "lucide-react";
import { Children, isValidElement, useId } from "react";
import { CreatorMonogram } from "../creator-monogram";
import { type CreatorRef, creatorAnnouncement, creatorInitials } from "../../lib/creator-initials";
import { formatWaitingSince } from "../../lib/format";
import { cn } from "../../lib/utils";
import type { RailAggregateStatus } from "../../lib/sessions-group";
export function ActiveWorkMark({ className }: { className?: string | undefined }) {
const maskId = `active-work-${useId().replaceAll(":", "")}`;
return (
);
}
/** The one descendant-aware status marker shared by rows and section headers. */
function RailAggregateDot({ summary }: { summary: RailAggregateStatus }) {
if (summary.kind === "neutral") return null;
if (summary.kind === "active") {
return (
);
}
if (summary.kind === "queued") {
return ;
}
if (summary.kind === "waiting") {
// Parked until input or its wake-up time: nothing is executing, so no spinner.
return ;
}
if (summary.kind === "active_work") {
return ;
}
if (summary.kind === "send_failed") {
return (
);
}
const tone =
summary.kind === "needs_attention"
? "bg-status-waiting"
: summary.kind === "failed"
? "bg-status-failed"
: "bg-session-update";
return (
);
}
/**
* The row's announced name.
*
* `aria-label` replaces name-from-content, so this string is the whole of what
* a screen reader hears for the row: the creator chip is `aria-hidden`, and
* even the sr-only state line inside the link is inert for it. Every fact a
* sighted user reads off the row belongs here, and in exactly one place.
*/
export function sessionRowAccessibleName({
title,
stateLabel,
pinned,
statusLabel,
spawnedLabel,
creator,
}: {
title: string;
stateLabel: string;
pinned: boolean;
statusLabel: string;
spawnedLabel?: string | null | undefined;
creator?: CreatorRef | null | undefined;
}): string {
const spokenCreator = creatorAnnouncement(creator ?? null);
return `Open ${title}. ${stateLabel}${pinned ? ". Pinned" : ""}. ${statusLabel}${
spawnedLabel ? `. ${spawnedLabel}` : ""
}${spokenCreator ? `. Created by ${spokenCreator}` : ""}`;
}
export function RailTrailingMetadata({
summary,
scheduled = false,
relativeTime,
jumpKey,
creator,
quickActionSlots = 0,
}: {
summary: RailAggregateStatus;
scheduled?: boolean | undefined;
relativeTime?: string | undefined;
/** The key that opens this row ("⌘3"), shown in place of the time while ⌘ is held. */
jumpKey?: string | undefined;
/** Rightmost items replaced in place by the row's desktop quick actions. */
quickActionSlots?: number | undefined;
/**
* Session creator for a top-level row, else null; callers decide which rows
* are roots. A chip is its own reason to render this block: a mobile root row
* can have no status marker and no time and still name who started it.
*/
creator?: CreatorRef | null | undefined;
}) {
const hasStatusMarker = summary.kind !== "neutral";
const hasRelativeTime = Boolean(relativeTime);
const hasMonogram = creator ? creatorInitials(creator) !== null : false;
// How long the longest-waiting session behind a "needs you" marker has been
// blocked on a human. A collapsed parent with a child parked for ten hours
// must say so, not hide it behind a dot.
const waitingFor =
summary.kind === "needs_attention" && summary.attentionSince
? formatWaitingSince(summary.attentionSince)
: "";
if (
!scheduled &&
!hasStatusMarker &&
!hasRelativeTime &&
!jumpKey &&
!hasMonogram &&
!quickActionSlots
)
return null;
const items = Children.toArray([
scheduled ? (
) : null,
hasMonogram && creator ? (
) : null,
waitingFor ? (
{waitingFor}
) : null,
hasStatusMarker ? (
) : null,
jumpKey ? (
{jumpKey}
) : hasRelativeTime ? (
{relativeTime}
) : null,
]).filter(isValidElement);
if (!quickActionSlots) {
return (
{items}
);
}
// Empty rows still reserve their action footprint. Visibility (never display
// or padding) swaps the last items, keeping every other box stationary.
while (items.length < quickActionSlots) items.unshift();
return (
{items.map((item, index) => (
= items.length - quickActionSlots &&
"pointer-fine:group-hover:invisible pointer-fine:group-focus-within:invisible",
)}
>
{item}
))}
);
}
export function SessionRowContent({
title,
stateLabel,
depthLabel,
descendantLabel,
mobile,
summary,
scheduled = false,
relativeTime,
jumpKey,
creator,
quickActionSlots = 0,
}: {
title: string;
stateLabel: string;
depthLabel?: string | null | undefined;
descendantLabel?: string | null | undefined;
mobile: boolean;
summary: RailAggregateStatus;
scheduled?: boolean | undefined;
relativeTime?: string | undefined;
/** See RailTrailingMetadata. */
jumpKey?: string | undefined;
quickActionSlots?: number | undefined;
/** Session creator for a top-level row, else null. See RailTrailingMetadata. */
creator?: CreatorRef | null | undefined;
}) {
return (
<>
{/* Inert for the row's accessible name, which its `aria-label` owns -
including the creator. Kept as-is so this change regresses nothing. */}
{stateLabel}.
{title}
{mobile ? (
{[stateLabel, depthLabel, descendantLabel, relativeTime].filter(Boolean).join(" · ")}
) : null}
>
);
}