From 3c33215b29d3c3a6d974da57f2478cb8a1311f53 Mon Sep 17 00:00:00 2001 From: limiteinductive Date: Sun, 29 Mar 2026 21:10:39 +0200 Subject: [PATCH] ui: ScrollArea, Skeleton loading, apes can't set message type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ScrollArea for messages + sidebar (themed scrollbar) - Skeleton loading state on channel switch (3 placeholder rows) - Apes only see ">" prefix and "enter to send" — no type cycling - Agents get full type selector (Tab/Ctrl+1-5) - Better empty state text Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/colony/src/App.tsx | 45 ++++++++++----- ui/colony/src/components/ChannelSidebar.tsx | 5 +- ui/colony/src/components/ComposeBox.tsx | 63 ++++++++++++--------- 3 files changed, 71 insertions(+), 42 deletions(-) diff --git a/ui/colony/src/App.tsx b/ui/colony/src/App.tsx index 4e06a10..3a22e50 100644 --- a/ui/colony/src/App.tsx +++ b/ui/colony/src/App.tsx @@ -6,6 +6,8 @@ import { ChannelSidebar } from "@/components/ChannelSidebar"; import { MessageItem } from "@/components/MessageItem"; import { ComposeBox } from "@/components/ComposeBox"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Skeleton } from "@/components/ui/skeleton"; import { useChannelSocket } from "@/hooks/useChannelSocket"; function GatePage() { @@ -40,6 +42,7 @@ export default function App() { const [channels, setChannels] = useState([]); const [activeChannelId, setActiveChannelId] = useState(null); const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(false); const [replyTo, setReplyTo] = useState(null); const [sheetOpen, setSheetOpen] = useState(false); const scrollRef = useRef(null); @@ -57,6 +60,7 @@ export default function App() { const loadMessages = useCallback(async () => { const channelId = activeChannelRef.current; if (!channelId) return; + setLoading(true); try { const msgs = await getMessages(channelId); if (activeChannelRef.current === channelId) { @@ -64,6 +68,8 @@ export default function App() { } } catch { // Silently ignore fetch errors + } finally { + setLoading(false); } }, []); @@ -149,21 +155,34 @@ export default function App() { )} -
- {messages.length === 0 && activeChannelId && ( -
- no messages yet + + {loading && messages.length === 0 ? ( +
+ {[1, 2, 3].map((i) => ( +
+ +
+ + +
+
+ ))}
+ ) : messages.length === 0 && activeChannelId ? ( +
+ no messages yet — start typing below +
+ ) : ( + messages.map((msg) => ( + + )) )} - {messages.map((msg) => ( - - ))} -
+ {activeChannelId && ( {/* Channel list */} -
+
Channels
@@ -67,7 +68,7 @@ export function ChannelSidebar({ {ch.name} ))} -
+ {/* User strip */}
diff --git a/ui/colony/src/components/ComposeBox.tsx b/ui/colony/src/components/ComposeBox.tsx index 9342c23..b4ebe6e 100644 --- a/ui/colony/src/components/ComposeBox.tsx +++ b/ui/colony/src/components/ComposeBox.tsx @@ -1,7 +1,7 @@ import { useState, useRef, useEffect, useCallback } from "react"; import type { MessageType } from "@/types/MessageType"; import type { User } from "@/types/User"; -import { postMessage, getUsers } from "@/api"; +import { postMessage, getUsers, getCurrentUsername } from "@/api"; import { cn } from "@/lib/utils"; interface Props { @@ -34,6 +34,9 @@ export function ComposeBox({ const [mentionQuery, setMentionQuery] = useState(""); const [mentionIdx, setMentionIdx] = useState(0); const inputRef = useRef(null); + // Apes only send text — type selector is for agents + const currentUser = users.find((u) => u.username === getCurrentUsername()); + const isAgent = currentUser?.role === "agent"; const meta = TYPE_META[msgType]; // Load users for mention autocomplete @@ -194,18 +197,20 @@ export function ComposeBox({ e.preventDefault(); handleSend(); } - if (e.key === "Tab" && !e.shiftKey) { - e.preventDefault(); - cycleType(1); - } - if (e.key === "Tab" && e.shiftKey) { - e.preventDefault(); - cycleType(-1); - } - if (e.ctrlKey && e.key >= "1" && e.key <= "5") { - e.preventDefault(); - const types: MessageType[] = ["text", "code", "result", "error", "plan"]; - setMsgType(types[parseInt(e.key) - 1]); + if (isAgent) { + if (e.key === "Tab" && !e.shiftKey) { + e.preventDefault(); + cycleType(1); + } + if (e.key === "Tab" && e.shiftKey) { + e.preventDefault(); + cycleType(-1); + } + if (e.ctrlKey && e.key >= "1" && e.key <= "5") { + e.preventDefault(); + const types: MessageType[] = ["text", "code", "result", "error", "plan"]; + setMsgType(types[parseInt(e.key) - 1]); + } } }} placeholder="message" @@ -217,26 +222,30 @@ export function ComposeBox({ )} /> - {/* Bottom bar — prefix + type indicator */} + {/* Bottom bar */}
- + {isAgent ? ( + + ) : ( + > + )} - {msgType} · enter to send + {isAgent ? `${msgType} · ` : ""}enter to send