Skip to content
43 linesCodeBlameRaw
1/**
2 * Cards in chat: how a card's state reads, which of its actions show (its
3 * links), and when its preview folds. Pure, so it is tested on its own;
4 * components/chat/card.tsx draws them.
5 */
6import type { CardAction, MessageCard } from "@g1t/contracts";
7
8import type { SessionChip } from "./session-card";
9
10/** A card's state chip, as a session's is, outlined only when it is waiting quietly. */
11export type CardChip = { tone: SessionChip["tone"] | "merged"; live: boolean; outline?: boolean };
12
13const DRAFT_CHIPS: Record<string, CardChip> = {
14 // Waiting on someone to file it: lavender, outlined only.
15 draft: { tone: "accent", live: false, outline: true },
16 filed: { tone: "success", live: false },
17 discarded: { tone: "neutral", live: false },
18};
19
20/**
21 * How a draft issue's state reads; null for other kinds (a session's chip
22 * is lib/session-card.ts's, others are guessed from their words).
23 */
24export function cardChip(card: Pick<MessageCard, "kind" | "state">): CardChip | null {
25 if (!card.state) return null;
26 if (card.kind === "draft_issue") return DRAFT_CHIPS[card.state.trim().toLowerCase()] ?? { tone: "neutral", live: false };
27 return null;
28}
29
30/**
31 * The actions a card shows: its links. The service that answered the
32 * others (agents) is gone, so an old card's buttons that ran something are
33 * left out; the card stays to read.
34 */
35export function shownActions(card: Pick<MessageCard, "actions">): CardAction[] {
36 return (card.actions ?? []).filter((a) => !!a.href);
37}
38
39/** Whether a body is long enough that its preview starts folded (over about eight lines). */
40export function foldsBody(body: string, lines = 8): boolean {
41 const rows = body.split("\n").reduce((n, line) => n + Math.max(1, Math.ceil(line.length / 80)), 0);
42 return rows > lines;
43}