Files
cobble/src/utils/crew-status.ts
T
tbenandClaude Opus 5.5 3286b80029 Build Cobble project structure from the handoff
- Remove the preset document suite (letters, emails, invoices, quotes,
  PDF generation, line items) with its routes, API handlers and tables
- Rebrand to Cobble: site data, Lato, handoff design tokens (light and
  dark) in main.css, brand assets in public/brand, ember stone logo and
  regenerated favicons
- Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate
  DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots)
- Add the chat shell at /admin/bots: rail with pinned coordinator and
  user popover, bottom-anchored conversation with composer, working-on
  panel, and a send-message API scoped to the session user
- Add BotStone and Wordmark components, crew status and relative time
  utils with tests, and update AGENTS.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 17:10:36 +01:00

79 lines
2.0 KiB
TypeScript

import type { BotFace, BotKind } from "@/src/dict/bot-dict";
type CrewBot = {
kind?: BotKind | string | null;
face?: BotFace | string | null;
};
type Params = {
bots: CrewBot[];
};
const NUMBER_WORDS = [
"No",
"One",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
"Nine",
"Ten",
] as const;
function countWord({ n }: { n: number }): string {
return NUMBER_WORDS[n] ?? String(n);
}
/**
* Working and needs-you counts across the workers (the coordinator excluded).
*/
export function crewCounts({ bots }: Params) {
const workers = bots.filter((bot) => bot.kind !== "coordinator");
return {
working: workers.filter((bot) => bot.face === "working").length,
needs_you: workers.filter((bot) => bot.face === "needs-you").length,
};
}
/**
* The coordinator's rail subtitle: `3 working · 1 needs you`.
*/
export function crewStatusLine({ bots }: Params): string {
const { working, needs_you } = crewCounts({ bots });
const parts: string[] = [];
if (working) parts.push(`${working} working`);
if (needs_you) parts.push(`${needs_you} need${needs_you == 1 ? "s" : ""} you`);
return parts.length ? parts.join(" · ") : "All quiet";
}
/**
* The signature status sentence: `Three bots are working. One needs you.`
*/
export function crewStatusSentence({ bots }: Params): string {
const { working, needs_you } = crewCounts({ bots });
const sentences: string[] = [];
if (working) {
sentences.push(
working == 1
? `One bot is working.`
: `${countWord({ n: working })} bots are working.`,
);
}
if (needs_you) {
const subject = working
? countWord({ n: needs_you })
: `${countWord({ n: needs_you })} bot${needs_you == 1 ? "" : "s"}`;
sentences.push(`${subject} need${needs_you == 1 ? "s" : ""} you.`);
}
return sentences.length ? sentences.join(" ") : "Nothing needs you.";
}