summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-03 22:29:44 +0000
committersoryu <soryu@soryu.co>2026-02-03 22:29:44 +0000
commit78087b37d25ba0b0f955c0f8a13d73f3014f707e (patch)
treecf3fabbdef8abcee527b8dd93018980566cb76b3
parentcf0a25af1d2834bfe6c5ea892ce5769936e5a673 (diff)
downloadsoryu-78087b37d25ba0b0f955c0f8a13d73f3014f707e.tar.gz
soryu-78087b37d25ba0b0f955c0f8a13d73f3014f707e.zip
Reorganize makima navbar
-rw-r--r--makima/frontend/src/components/NavStrip.tsx2
-rw-r--r--makima/frontend/src/components/chains/ChainEditor.tsx22
-rw-r--r--makima/frontend/src/components/chains/ChainList.tsx187
3 files changed, 97 insertions, 114 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx
index 5937982..4f6cf32 100644
--- a/makima/frontend/src/components/NavStrip.tsx
+++ b/makima/frontend/src/components/NavStrip.tsx
@@ -10,8 +10,8 @@ interface NavLink {
const NAV_LINKS: NavLink[] = [
{ label: "Listen", href: "/listen" },
- { label: "Contracts", href: "/contracts", requiresAuth: true },
{ label: "Chains", href: "/chains", requiresAuth: true },
+ { label: "Contracts", href: "/contracts", requiresAuth: true },
{ label: "Board", href: "/workflow", requiresAuth: true },
{ label: "Mesh", href: "/mesh", requiresAuth: true },
{ label: "History", href: "/history", requiresAuth: true },
diff --git a/makima/frontend/src/components/chains/ChainEditor.tsx b/makima/frontend/src/components/chains/ChainEditor.tsx
index 9077d19..92bd496 100644
--- a/makima/frontend/src/components/chains/ChainEditor.tsx
+++ b/makima/frontend/src/components/chains/ChainEditor.tsx
@@ -5,6 +5,12 @@ import type {
ChainContractDetail,
} from "../../lib/api";
+const statusColors: Record<string, string> = {
+ active: "text-green-400",
+ completed: "text-blue-400",
+ archived: "text-[#555]",
+};
+
interface ChainEditorProps {
chain: ChainWithContracts;
graph: ChainGraphResponse | null;
@@ -116,12 +122,8 @@ export function ChainEditor({
</div>
<div className="flex items-center gap-2">
<span
- className={`px-2 py-1 font-mono text-[10px] uppercase rounded ${
- chain.chain.status === "active"
- ? "text-[#4ade80] bg-[#4ade80]/10"
- : chain.chain.status === "completed"
- ? "text-[#60a5fa] bg-[#60a5fa]/10"
- : "text-[#6b7280] bg-[#6b7280]/10"
+ className={`font-mono text-[10px] uppercase ${
+ statusColors[chain.chain.status] || "text-[#555]"
}`}
>
{chain.chain.status}
@@ -371,12 +373,8 @@ function ContractDetailPanel({
Status
</label>
<span
- className={`inline-block px-2 py-1 font-mono text-xs uppercase rounded ${
- contract.contractStatus === "active"
- ? "text-[#4ade80] bg-[#4ade80]/10"
- : contract.contractStatus === "completed"
- ? "text-[#60a5fa] bg-[#60a5fa]/10"
- : "text-[#6b7280] bg-[#6b7280]/10"
+ className={`font-mono text-xs uppercase ${
+ statusColors[contract.contractStatus] || "text-[#555]"
}`}
>
{contract.contractStatus}
diff --git a/makima/frontend/src/components/chains/ChainList.tsx b/makima/frontend/src/components/chains/ChainList.tsx
index eda79d7..befccd2 100644
--- a/makima/frontend/src/components/chains/ChainList.tsx
+++ b/makima/frontend/src/components/chains/ChainList.tsx
@@ -10,6 +10,12 @@ interface ChainListProps {
onArchive: (chain: ChainSummary) => void;
}
+const statusColors: Record<ChainStatus, string> = {
+ active: "text-green-400",
+ completed: "text-blue-400",
+ archived: "text-[#555]",
+};
+
export function ChainList({
chains,
loading,
@@ -18,101 +24,74 @@ export function ChainList({
selectedId,
onArchive,
}: ChainListProps) {
- const [statusFilter, setStatusFilter] = useState<ChainStatus | "all">("all");
- const [contextMenu, setContextMenu] = useState<{
- chain: ChainSummary;
- x: number;
- y: number;
- } | null>(null);
+ const [filter, setFilter] = useState<ChainStatus | "all">("all");
+ const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null);
+ const [contextMenuChain, setContextMenuChain] = useState<ChainSummary | null>(null);
- const filteredChains = chains.filter((chain) =>
- statusFilter === "all" ? true : chain.status === statusFilter
- );
+ const filteredChains =
+ filter === "all"
+ ? chains
+ : chains.filter((c) => c.status === filter);
const handleContextMenu = useCallback(
(e: React.MouseEvent, chain: ChainSummary) => {
e.preventDefault();
- setContextMenu({ chain, x: e.clientX, y: e.clientY });
+ setContextMenuPosition({ x: e.clientX, y: e.clientY });
+ setContextMenuChain(chain);
},
[]
);
const closeContextMenu = useCallback(() => {
- setContextMenu(null);
+ setContextMenuPosition(null);
+ setContextMenuChain(null);
}, []);
const handleArchive = useCallback(() => {
- if (contextMenu) {
- onArchive(contextMenu.chain);
- setContextMenu(null);
- }
- }, [contextMenu, onArchive]);
-
- const getStatusColor = (status: ChainStatus) => {
- switch (status) {
- case "active":
- return "text-[#4ade80] bg-[#4ade80]/10";
- case "completed":
- return "text-[#60a5fa] bg-[#60a5fa]/10";
- case "archived":
- return "text-[#6b7280] bg-[#6b7280]/10";
- default:
- return "text-[#8b949e] bg-[#8b949e]/10";
+ if (contextMenuChain) {
+ onArchive(contextMenuChain);
+ closeContextMenu();
}
- };
+ }, [contextMenuChain, onArchive, closeContextMenu]);
- const getStatusIcon = (status: ChainStatus) => {
- switch (status) {
- case "active":
- return (
- <svg className="w-3 h-3" viewBox="0 0 24 24" fill="currentColor">
- <circle cx="12" cy="12" r="4" />
- </svg>
- );
- case "completed":
- return (
- <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
- <polyline points="20 6 9 17 4 12" />
- </svg>
- );
- case "archived":
- return (
- <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
- <path d="M21 8v13H3V8" />
- <path d="M1 3h22v5H1z" />
- <path d="M10 12h4" />
- </svg>
- );
- default:
- return null;
- }
- };
+ if (loading) {
+ return (
+ <div className="panel h-full flex items-center justify-center">
+ <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
+ </div>
+ );
+ }
return (
<div className="panel h-full flex flex-col" onClick={closeContextMenu}>
{/* Header */}
- <div className="p-3 border-b border-[rgba(117,170,252,0.2)]">
+ <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]">
<div className="flex items-center justify-between mb-3">
- <h2 className="font-mono text-sm text-[#75aafc] uppercase">Chains</h2>
+ <h2 className="font-mono text-sm text-[#75aafc] uppercase tracking-wider">
+ Chains
+ </h2>
<button
onClick={onCreate}
- className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors"
+ className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
>
+ New
</button>
</div>
- {/* Status filter */}
+ {/* Filter tabs */}
<div className="flex gap-1">
{(["all", "active", "completed", "archived"] as const).map((status) => (
<button
key={status}
- onClick={() => setStatusFilter(status)}
- className={`px-2 py-1 font-mono text-[10px] uppercase transition-colors ${
- statusFilter === status
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
- }`}
+ onClick={() => setFilter(status)}
+ className={`
+ px-2 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors
+ ${
+ filter === status
+ ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]"
+ : "text-[#555] hover:text-[#75aafc]"
+ }
+ `}
>
{status}
</button>
@@ -122,78 +101,84 @@ export function ChainList({
{/* Chain list */}
<div className="flex-1 overflow-y-auto">
- {loading ? (
- <div className="flex items-center justify-center h-32">
- <p className="font-mono text-xs text-[#8b949e]">Loading chains...</p>
- </div>
- ) : filteredChains.length === 0 ? (
- <div className="flex items-center justify-center h-32">
- <p className="font-mono text-xs text-[#8b949e]">
- {statusFilter === "all" ? "No chains yet" : `No ${statusFilter} chains`}
+ {filteredChains.length === 0 ? (
+ <div className="p-4 text-center">
+ <p className="font-mono text-sm text-[#555]">
+ {filter === "all"
+ ? "No chains yet"
+ : `No ${filter} chains`}
</p>
</div>
) : (
- <div className="divide-y divide-[rgba(117,170,252,0.1)]">
+ <div className="divide-y divide-[rgba(117,170,252,0.15)]">
{filteredChains.map((chain) => (
- <div
+ <button
key={chain.id}
onClick={() => onSelect(chain.id)}
onContextMenu={(e) => handleContextMenu(e, chain)}
- className={`p-3 cursor-pointer transition-colors ${
- selectedId === chain.id
- ? "bg-[rgba(117,170,252,0.15)]"
- : "hover:bg-[rgba(117,170,252,0.05)]"
- }`}
+ className={`
+ w-full text-left p-4 transition-colors
+ ${
+ selectedId === chain.id
+ ? "bg-[rgba(117,170,252,0.1)]"
+ : "hover:bg-[rgba(117,170,252,0.05)]"
+ }
+ `}
>
- <div className="flex items-center justify-between mb-1">
- <span className="font-mono text-sm text-[#dbe7ff] truncate">
+ <div className="flex items-start justify-between gap-2 mb-2">
+ <h3 className="font-mono text-sm text-[#dbe7ff] truncate">
{chain.name}
- </span>
+ </h3>
<span
- className={`flex items-center gap-1 px-1.5 py-0.5 font-mono text-[10px] uppercase rounded ${getStatusColor(
- chain.status
- )}`}
+ className={`text-[10px] font-mono uppercase shrink-0 ${
+ statusColors[chain.status]
+ }`}
>
- {getStatusIcon(chain.status)}
{chain.status}
</span>
</div>
+
{chain.description && (
- <p className="font-mono text-xs text-[#8b949e] truncate mb-1">
+ <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2">
{chain.description}
</p>
)}
- <div className="flex items-center gap-3 font-mono text-[10px] text-[#556677]">
+
+ <div className="flex items-center gap-4 text-[10px] font-mono text-[#555]">
<span>{chain.contractCount} contracts</span>
- <span>
- {new Date(chain.updatedAt).toLocaleDateString()}
- </span>
+ <span>{chain.completedContractCount} completed</span>
+ {chain.loopEnabled && (
+ <span className="text-amber-400">
+ loop {chain.loopCurrentIteration || 0}/{chain.loopMaxIterations || "∞"}
+ </span>
+ )}
</div>
- </div>
+ </button>
))}
</div>
)}
</div>
- {/* Context menu */}
- {contextMenu && (
+ {/* Context Menu */}
+ {contextMenuPosition && contextMenuChain && (
<div
- className="fixed z-50 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] shadow-lg py-1"
- style={{ top: contextMenu.y, left: contextMenu.x }}
+ className="fixed z-50 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] shadow-lg py-1 min-w-[150px]"
+ style={{ top: contextMenuPosition.y, left: contextMenuPosition.x }}
+ onClick={(e) => e.stopPropagation()}
>
<button
onClick={() => {
- onSelect(contextMenu.chain.id);
- setContextMenu(null);
+ onSelect(contextMenuChain.id);
+ closeContextMenu();
}}
- className="w-full px-4 py-2 text-left font-mono text-xs text-[#dbe7ff] hover:bg-[rgba(117,170,252,0.1)]"
+ className="w-full px-4 py-2 text-left font-mono text-xs text-[#dbe7ff] hover:bg-[rgba(117,170,252,0.1)] transition-colors"
>
View Details
</button>
- {contextMenu.chain.status !== "archived" && (
+ {contextMenuChain.status !== "archived" && (
<button
onClick={handleArchive}
- className="w-full px-4 py-2 text-left font-mono text-xs text-red-400 hover:bg-red-400/10"
+ className="w-full px-4 py-2 text-left font-mono text-xs text-red-400 hover:bg-red-400/10 transition-colors"
>
Archive
</button>