devist 0.8.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
import type { WorkerEvent } from "@/types";
import { formatDistanceToNow } from "date-fns";
import { Check, Undo2 } from "lucide-react";

const SEV_COLOR: Record<string, string> = {
  info: "text-muted-foreground",
  suggest: "text-cyan-600",
  warn: "text-yellow-600",
  block: "text-red-600",
};

type EventRowProps = {
  event: WorkerEvent;
  onAck?: (event: WorkerEvent) => void;
  onUnack?: (event: WorkerEvent) => void;
};

export function EventRow({ event, onAck, onUnack }: EventRowProps) {
  const ts = formatDistanceToNow(new Date(event.created_at), { addSuffix: true });
  const text = extractText(event);
  const acked = !!event.acked_at;
  const showAck = !!onAck && !acked;
  const showUnack = !!onUnack && acked;

  return (
    <div
      className={`border-b py-2 px-3 text-sm flex items-start gap-3 hover:bg-muted/40 ${
        acked ? "opacity-60" : ""
      }`}
    >
      <span
        className={`text-[10px] uppercase font-semibold w-14 mt-0.5 ${
          SEV_COLOR[event.severity] ?? "text-muted-foreground"
        }`}
      >
        {event.severity}
      </span>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2">
          <span className={`font-medium ${acked ? "line-through" : ""}`}>
            {event.event_type}
          </span>
          {event.path && (
            <span className="text-muted-foreground font-mono text-xs truncate">
              {event.path}
            </span>
          )}
          {acked && (
            <span className="text-[10px] uppercase text-green-600 border border-green-300 rounded px-1">
              acked
            </span>
          )}
        </div>
        {text && <div className="text-muted-foreground mt-0.5">{text}</div>}
      </div>
      <div className="flex items-center gap-2 shrink-0">
        <span className="text-xs text-muted-foreground whitespace-nowrap">{ts}</span>
        {showAck && (
          <button
            type="button"
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              onAck?.(event);
            }}
            title="Mark acknowledged"
            className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground"
          >
            <Check size={14} />
          </button>
        )}
        {showUnack && (
          <button
            type="button"
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              onUnack?.(event);
            }}
            title="Restore"
            className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground"
          >
            <Undo2 size={14} />
          </button>
        )}
      </div>
    </div>
  );
}

function extractText(ev: WorkerEvent): string | null {
  const p = ev.payload as Record<string, unknown>;
  if (typeof p?.text === "string") return p.text;
  if (typeof p?.error === "string") return `error: ${p.error}`;
  return null;
}