S2: Colony chat UI — dark industrial design, JetBrains Mono

- Channel sidebar with create
- Message timeline with type-based styling (code/result/error/plan)
- Agent messages get glow line + AGENT badge
- Agent metadata strip (model, hostname, cwd, skill)
- Reply-to with context preview
- Compose box with message type selector (Alt+1-5)
- 3s polling for live updates (WebSocket in S5)
- Vite proxy to backend, TypeScript strict mode, Biome linting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 19:10:46 +02:00
parent 2698694d08
commit 0b6244390e
11 changed files with 448 additions and 487 deletions

View File

@@ -1,9 +1,5 @@
import { useState } from "react";
import type { Channel } from "@/types/Channel";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { createChannel } from "@/api";
interface Props {
@@ -13,7 +9,12 @@ interface Props {
onChannelCreated: () => void;
}
export function ChannelSidebar({ channels, activeId, onSelect, onChannelCreated }: Props) {
export function ChannelSidebar({
channels,
activeId,
onSelect,
onChannelCreated,
}: Props) {
const [newName, setNewName] = useState("");
const [creating, setCreating] = useState(false);
@@ -27,39 +28,50 @@ export function ChannelSidebar({ channels, activeId, onSelect, onChannelCreated
}
return (
<div className="flex flex-col h-full w-60 border-r bg-sidebar text-sidebar-foreground">
<div className="p-4 font-semibold text-lg">Colony</div>
<Separator />
<ScrollArea className="flex-1">
<div className="p-2 space-y-1">
{channels.map((ch) => (
<button
type="button"
key={ch.id}
onClick={() => onSelect(ch.id)}
className={`w-full text-left px-3 py-1.5 rounded-md text-sm transition-colors ${
ch.id === activeId
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
: "hover:bg-sidebar-accent/50"
}`}
>
# {ch.name}
</button>
))}
<div className="flex flex-col h-full w-52 border-r border-sidebar-border bg-sidebar text-sidebar-foreground">
{/* Header */}
<div className="px-3 py-3 border-b border-sidebar-border">
<div className="text-[15px] font-bold tracking-tight text-foreground">
COLONY
</div>
</ScrollArea>
<Separator />
<div className="p-2 flex gap-1">
<Input
placeholder="new channel"
<div className="text-[10px] text-muted-foreground mt-0.5">
apes.unslope.com
</div>
</div>
{/* Channel list */}
<div className="flex-1 overflow-y-auto py-2">
<div className="px-2 mb-1 text-[10px] font-bold text-muted-foreground tracking-widest">
CHANNELS
</div>
{channels.map((ch) => (
<button
type="button"
key={ch.id}
onClick={() => onSelect(ch.id)}
className={`w-full text-left px-3 py-1 text-[12px] transition-colors ${
ch.id === activeId
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
: "text-sidebar-foreground hover:bg-sidebar-accent/50"
}`}
>
<span className="text-muted-foreground mr-1">#</span>
{ch.name}
</button>
))}
</div>
{/* New channel input */}
<div className="p-2 border-t border-sidebar-border">
<input
type="text"
placeholder="+ new channel"
value={newName}
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleCreate()}
className="text-sm h-8"
disabled={creating}
className="w-full bg-sidebar-accent text-[11px] text-sidebar-foreground placeholder:text-muted-foreground px-2 py-1 rounded-sm border border-sidebar-border focus:outline-none focus:border-[var(--color-agent-glow)]"
/>
<Button size="sm" onClick={handleCreate} disabled={creating} className="h-8">
+
</Button>
</div>
</div>
);