102 lines
3.4 KiB
React
102 lines
3.4 KiB
React
/**
|
|
* Investigation Map — user-facing workspace card.
|
|
*
|
|
* Shows the progress of reasoning as a set of investigation topics with
|
|
* simple status indicators. Does NOT expose graph internals.
|
|
*
|
|
* Design principles:
|
|
* - Calm, spacious, accessible
|
|
* - No percentages, no progress bars, no confidence scores
|
|
* - Topics evolve naturally across turns
|
|
*/
|
|
|
|
import getInvestigationMapTopics from "@/lib/map/investigation-map-adapter";
|
|
import React from "react";
|
|
|
|
/* ── Status icons (unicode — no icon library dependency) ─── */
|
|
|
|
const STATUS_ICONS = {
|
|
established: "✓",
|
|
current: "●",
|
|
unknown: "○",
|
|
};
|
|
|
|
function topicRowColor(status) {
|
|
switch (status) {
|
|
case "established":
|
|
return "text-gray-900";
|
|
case "current":
|
|
return "text-blue-800";
|
|
default:
|
|
return "text-gray-400";
|
|
}
|
|
}
|
|
|
|
function topicIconColor(status) {
|
|
switch (status) {
|
|
case "established":
|
|
return "text-green-600";
|
|
case "current":
|
|
return "text-blue-500";
|
|
default:
|
|
return "text-gray-300";
|
|
}
|
|
}
|
|
|
|
/* ── Single topic row ───────────────────────────────────── */
|
|
|
|
function TopicRow({ title, status }) {
|
|
const icon = STATUS_ICONS[status];
|
|
const colorClass = topicRowColor(status);
|
|
const iconColor = topicIconColor(status);
|
|
const ariaLabel = `${status === "established" ? "Established" : status === "current" ? "Currently investigating" : "Still to explore"}: ${title}`;
|
|
|
|
return (
|
|
<div
|
|
className={`flex items-center gap-3 py-2.5 text-sm transition-opacity duration-300 ease-in-out ${colorClass}`}
|
|
aria-label={ariaLabel}
|
|
role="listitem"
|
|
data-testid={`map-topic-${status === "established" ? "established" : status === "current" ? "current" : "unknown"}`}
|
|
>
|
|
<span className={`flex-none text-base ${iconColor} leading-none`} aria-hidden="true">
|
|
{icon}
|
|
</span>
|
|
<span className="flex-1">{title}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ── Card wrapper ────────────────────────────────────────── */
|
|
|
|
export default function InvestigationMap({ turnCount = 0 }) {
|
|
const topics = getInvestigationMapTopics(turnCount);
|
|
|
|
// Group topics by status for cleaner rendering
|
|
const groups = {
|
|
established: topics.filter((t) => t.status === "established"),
|
|
current: topics.filter((t) => t.status === "current"),
|
|
unknown: topics.filter((t) => t.status === "unknown"),
|
|
};
|
|
|
|
// Only render the card if there are non-established topics (during active investigation)
|
|
const hasActiveTopics = groups.current.length > 0 || groups.unknown.length > 0;
|
|
if (!hasActiveTopics && groups.established.length === 0) return null;
|
|
|
|
return (
|
|
<div className="rounded-lg border border-gray-200/60 bg-gray-50/30 p-4" role="region" aria-label="Investigation map preview">
|
|
<h2 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-300">
|
|
Investigation Map
|
|
</h2>
|
|
<p className="mb-3 text-xs text-gray-400/70">
|
|
Active investigation topics and their status.
|
|
</p>
|
|
|
|
<div className="space-y-px border-t border-gray-200/60 pt-3" role="list" aria-label="Investigation topics">
|
|
{topics.map((topic, i) => (
|
|
<TopicRow key={`${topic.title}-${i}`} title={topic.title} status={topic.status} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|