Files
fusion/plugins/fusion-plugin-even-cards/src/cards/format.ts
Fusion 7582a32cda feat(FN-3739): add fusion-plugin-even-cards workspace plugin
- Add a new fusion-plugin-even-cards package with typed card formatting utilities and board/task card generation
- Implement authenticated plugin routes for board card endpoints and plugin registration wiring
- Add unit tests for auth handling, board routes, and card formatting behavior
- Document plugin in PLUGIN_AUTHORING guide and register it in pnpm workspace

Fusion-Task-Id: FN-3739
2026-05-08 12:46:12 -07:00

71 lines
2.1 KiB
TypeScript

import type { CardStatusBadge, FusionColumn } from "./types.js";
export const DEFAULT_MAX_CHARS_PER_LINE = 24;
export const DEFAULT_MAX_LINES_PER_CARD = 4;
export const DEFAULT_MAX_CARDS_PER_DECK = 8;
export function truncateLine(input: string, max: number): string {
const value = input.trim();
if (value.length <= max) return value;
if (max <= 1) return "…";
return `${value.slice(0, max - 1)}`;
}
export function wrapLines(text: string, max: number, maxLines: number): string[] {
const words = text.trim().split(/\s+/).filter(Boolean);
if (words.length === 0) return [];
const lines: string[] = [];
let current = "";
for (const word of words) {
const candidate = current ? `${current} ${word}` : word;
if (candidate.length <= max) {
current = candidate;
continue;
}
if (current) {
lines.push(current);
if (lines.length === maxLines) return lines.map((line, idx) => (idx === maxLines - 1 ? truncateLine(line, max) : line));
}
current = word.length <= max ? word : truncateLine(word, max);
}
if (current && lines.length < maxLines) {
lines.push(current);
}
return lines.slice(0, maxLines).map((line, idx, arr) => (idx === arr.length - 1 ? truncateLine(line, max) : line));
}
export function statusBadge(column: FusionColumn): CardStatusBadge {
switch (column) {
case "triage":
case "todo":
case "in-progress":
case "in-review":
case "done":
return { label: column, tone: column };
case "archived":
default:
return { label: column, tone: "neutral" };
}
}
export function formatTaskId(id: string): string {
return id.trim().toUpperCase();
}
export function formatRelativeAge(createdAtIso: string, nowIso: string): string {
const created = Date.parse(createdAtIso);
const now = Date.parse(nowIso);
if (!Number.isFinite(created) || !Number.isFinite(now) || now <= created) return "0m";
const diffMs = now - created;
const minutes = Math.floor(diffMs / 60000);
if (minutes < 60) return `${minutes}m`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h`;
const days = Math.floor(hours / 24);
return `${days}d`;
}