From 0ab3d64daaf2bcf8ac8c4cce244f6f27a122efe8 Mon Sep 17 00:00:00 2001 From: limiteinductive Date: Sun, 29 Mar 2026 20:52:00 +0200 Subject: [PATCH] =?UTF-8?q?redesign:=20Concrete=20Brutalism=20=E2=80=94=20?= =?UTF-8?q?warm=20concrete=20palette,=20Inconsolata=20+=20Instrument=20San?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kill the AI slop. New design language: - Warm concrete grays (#1a1917 base) with hot orange (#F26522) accent - Inconsolata mono for body, Instrument Sans for headings - Zero border-radius everywhere — brutalist, no rounded corners - Thick 4px type slabs on messages (green/red/blue/yellow) - Thick 2px borders on all structural elements - Agent messages in warm card bg, names in hot orange - Ape emoji logo in sidebar - Command terminal compose box with > prompt - Blocky type selector buttons Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/colony/package-lock.json | 20 ++ ui/colony/package.json | 2 + ui/colony/src/App.tsx | 16 +- ui/colony/src/components/ChannelSidebar.tsx | 49 +++-- ui/colony/src/components/ComposeBox.tsx | 58 +++--- ui/colony/src/components/MessageItem.tsx | 216 ++++++++++---------- ui/colony/src/index.css | 80 ++++---- 7 files changed, 234 insertions(+), 207 deletions(-) diff --git a/ui/colony/package-lock.json b/ui/colony/package-lock.json index 53887e0..696f5a9 100644 --- a/ui/colony/package-lock.json +++ b/ui/colony/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/geist": "^5.2.8", + "@fontsource-variable/instrument-sans": "^5.2.8", + "@fontsource/inconsolata": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8", "@tailwindcss/vite": "^4.2.2", "class-variance-authority": "^0.7.1", @@ -907,6 +909,24 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/instrument-sans": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/instrument-sans/-/instrument-sans-5.2.8.tgz", + "integrity": "sha512-mTCaukbdIjjoipj2E3Q5XoZM3ZxJWdzyHevf/LG/0PHlfF9Q85pxOM7B7A9MerFyxmRzz5kVlumgIvgDSG4CPg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/inconsolata": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.8.tgz", + "integrity": "sha512-lIZW+WOZYpUH91g9r6rYYhfTmptF3YPPM54ZOs8IYVeeL4SeiAu4tfj7mdr8llYEq31DLYgi6JtGIJa192gB0Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource/jetbrains-mono": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", diff --git a/ui/colony/package.json b/ui/colony/package.json index d2770a9..c886da8 100644 --- a/ui/colony/package.json +++ b/ui/colony/package.json @@ -12,6 +12,8 @@ "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/geist": "^5.2.8", + "@fontsource-variable/instrument-sans": "^5.2.8", + "@fontsource/inconsolata": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8", "@tailwindcss/vite": "^4.2.2", "class-variance-authority": "^0.7.1", diff --git a/ui/colony/src/App.tsx b/ui/colony/src/App.tsx index 3c74db2..d216f06 100644 --- a/ui/colony/src/App.tsx +++ b/ui/colony/src/App.tsx @@ -93,31 +93,31 @@ export default function App() {
-
+
- + = - + {sidebar} {activeChannel ? ( <> - # - {activeChannel.name} + # + {activeChannel.name} {activeChannel.description && ( - + {activeChannel.description} )} - + {messages.length} msg ) : ( - select a channel + no channel selected )}
diff --git a/ui/colony/src/components/ChannelSidebar.tsx b/ui/colony/src/components/ChannelSidebar.tsx index f79eedc..fc36c03 100644 --- a/ui/colony/src/components/ChannelSidebar.tsx +++ b/ui/colony/src/components/ChannelSidebar.tsx @@ -31,55 +31,60 @@ export function ChannelSidebar({ } return ( -
- {/* Header */} -
-
- COLONY -
-
- apes.unslope.com +
+ {/* Header — ape logo */} +
+
+ 🐒 +
+
+ Colony +
+
+ apes.unslope.com +
+
{/* Channel list */} -
-
- CHANNELS +
+
+ Channels
{channels.map((ch) => ( ))}
- {/* Current user */} -
- as - {getCurrentUsername()} + {/* User strip */} +
+ usr: + {getCurrentUsername()}
{/* New channel */} -
+
setNewName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleCreate()} disabled={creating} - className="w-full bg-white/[0.04] text-[12px] md:text-[11px] text-foreground placeholder:text-muted-foreground/40 px-3 py-2 md:py-1 rounded-sm border border-white/[0.06] focus:outline-none focus:border-blue-500/30 min-h-[40px] md:min-h-0" + className="w-full bg-input text-xs font-mono text-foreground placeholder:text-muted-foreground px-2 py-1.5 border-2 border-border focus:outline-none focus:border-primary min-h-[36px] md:min-h-0" />
diff --git a/ui/colony/src/components/ComposeBox.tsx b/ui/colony/src/components/ComposeBox.tsx index da4d878..5fb0f40 100644 --- a/ui/colony/src/components/ComposeBox.tsx +++ b/ui/colony/src/components/ComposeBox.tsx @@ -1,8 +1,6 @@ import { useState } from "react"; import type { MessageType } from "@/types/MessageType"; import { postMessage } from "@/api"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; interface Props { @@ -12,12 +10,12 @@ interface Props { onMessageSent: () => void; } -const MSG_TYPES: { value: MessageType; label: string }[] = [ - { value: "text", label: "T" }, - { value: "code", label: "C" }, - { value: "result", label: "R" }, - { value: "error", label: "E" }, - { value: "plan", label: "P" }, +const MSG_TYPES: { value: MessageType; label: string; color: string }[] = [ + { value: "text", label: "TXT", color: "" }, + { value: "code", label: "COD", color: "text-[var(--color-msg-code)]" }, + { value: "result", label: "RES", color: "text-[var(--color-msg-result)]" }, + { value: "error", label: "ERR", color: "text-[var(--color-msg-error)]" }, + { value: "plan", label: "PLN", color: "text-[var(--color-msg-plan)]" }, ]; export function ComposeBox({ @@ -49,29 +47,34 @@ export function ComposeBox({ } return ( -
+
{replyTo && ( -
- ^ #{replyTo.slice(0, 8)} - +
)} -
- {/* Type selector */} -
+
+ {/* Type selector — blocky, no-radius buttons */} +
{MSG_TYPES.map((t) => (
- setContent(e.target.value)} onKeyDown={(e) => { @@ -88,19 +93,20 @@ export function ComposeBox({ handleSend(); } }} - placeholder="message..." + placeholder="> message..." disabled={sending} - className="flex-1 h-9 md:h-8 text-sm" + className="flex-1 bg-input text-sm font-mono text-foreground placeholder:text-muted-foreground/50 px-3 py-1.5 h-9 md:h-8 border-2 border-border focus:outline-none focus:border-primary" /> - + {sending ? "..." : "Send"} +
); diff --git a/ui/colony/src/components/MessageItem.tsx b/ui/colony/src/components/MessageItem.tsx index e7ee205..72e2996 100644 --- a/ui/colony/src/components/MessageItem.tsx +++ b/ui/colony/src/components/MessageItem.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import type { Message } from "@/types/Message"; +import { cn } from "@/lib/utils"; interface Props { message: Message; @@ -7,19 +8,12 @@ interface Props { onReply: (id: string) => void; } -const TYPE_BORDER: Record = { - text: "", - code: "border-l-2 border-amber-500/70", - result: "border-l-2 border-emerald-500/70", - error: "border-l-2 border-red-500/70", - plan: "border-l-2 border-blue-500/70", -}; - -const TYPE_LABEL: Record = { - code: { text: "CODE", color: "text-amber-500" }, - result: { text: "RES", color: "text-emerald-500" }, - error: { text: "ERR", color: "text-red-500" }, - plan: { text: "PLAN", color: "text-blue-500" }, +const TYPE_CONFIG: Record = { + text: { border: "border-l-transparent", label: "", labelBg: "" }, + code: { border: "border-l-[var(--color-msg-code)]", label: "CODE", labelBg: "bg-[var(--color-msg-code)]/15 text-[var(--color-msg-code)]" }, + result: { border: "border-l-[var(--color-msg-result)]", label: "RESULT", labelBg: "bg-[var(--color-msg-result)]/15 text-[var(--color-msg-result)]" }, + error: { border: "border-l-[var(--color-msg-error)]", label: "ERROR", labelBg: "bg-[var(--color-msg-error)]/15 text-[var(--color-msg-error)]" }, + plan: { border: "border-l-[var(--color-msg-plan)]", label: "PLAN", labelBg: "bg-[var(--color-msg-plan)]/15 text-[var(--color-msg-plan)]" }, }; function timeAgo(dateStr: string): string { @@ -36,120 +30,116 @@ export function MessageItem({ message, replyTarget, onReply }: Props) { const [metaOpen, setMetaOpen] = useState(false); const isAgent = message.user.role === "agent"; const isDeleted = !!message.deleted_at; - const border = TYPE_BORDER[message.type] || ""; - const label = TYPE_LABEL[message.type]; + const cfg = TYPE_CONFIG[message.type] || TYPE_CONFIG.text; const meta = message.metadata as Record | null; return (
- {/* Agent glow — left edge */} - {isAgent && ( -
+ className={cn( + "group border-b border-border/50 border-l-4 transition-colors", + cfg.border, + isAgent ? "bg-card" : "bg-background", + "hover:bg-muted/30", )} - + > {/* Reply context */} {replyTarget && ( -
- ^ - {replyTarget.user.display_name} - {replyTarget.content} +
+ ^ + {replyTarget.user.display_name} + {replyTarget.content}
)} -
- {/* Avatar — small on mobile */} -
- {message.user.display_name[0]} +
+ {/* Header */} +
+ {/* Name */} + + {message.user.display_name} + + + {/* Agent badge */} + {isAgent && ( + + AGT + + )} + + {/* Type badge */} + {cfg.label && ( + + {cfg.label} + + )} + + {/* Time */} + + {timeAgo(message.created_at)} + + + {new Date(message.created_at).toLocaleTimeString("en-US", { + hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", + })} + + + {/* Seq */} + + #{Number(message.seq)} + + + {/* Reply button */} +
-
- {/* Header */} -
- - {message.user.display_name} - - {isAgent && ( - - AGT - - )} - {label && ( - - {label.text} - - )} - {/* Mobile: relative time. Desktop: full time */} - - {timeAgo(message.created_at)} - - - {new Date(message.created_at).toLocaleTimeString("en-US", { - hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", - })} - - - #{Number(message.seq)} - - {/* Reply — always visible on mobile (no hover), hover on desktop */} - -
- - {/* Content */} -
- {isDeleted ? ( - [deleted] - ) : ( - message.content - )} -
- - {/* Agent metadata — tap to expand on mobile, always compact on desktop */} - {meta && isAgent && ( - <> - {/* Desktop: inline strip */} -
- {meta.model && {meta.model}} - {meta.hostname && {meta.hostname}} - {meta.cwd && {meta.cwd}} - {meta.skill && {meta.skill}} -
- {/* Mobile: tap to expand */} - - {metaOpen && ( -
- {meta.model &&
model: {meta.model}
} - {meta.hostname &&
host: {meta.hostname}
} - {meta.cwd &&
cwd: {meta.cwd}
} - {meta.skill &&
skill: {meta.skill}
} -
- )} - + {/* Content */} +
+ {isDeleted ? ( + [deleted] + ) : ( + message.content )}
+ + {/* Agent metadata */} + {meta && isAgent && ( + <> +
+ {meta.model && {meta.model}} + {meta.hostname && {meta.hostname}} + {meta.cwd && {meta.cwd}} + {meta.skill && {meta.skill}} +
+ + {metaOpen && ( +
+ {meta.model &&
{meta.model}
} + {meta.hostname &&
{meta.hostname}
} + {meta.cwd &&
{meta.cwd}
} + {meta.skill &&
{meta.skill}
} +
+ )} + + )}
); diff --git a/ui/colony/src/index.css b/ui/colony/src/index.css index a9cc1d3..112263a 100644 --- a/ui/colony/src/index.css +++ b/ui/colony/src/index.css @@ -1,13 +1,13 @@ @import "tailwindcss"; -@import "@fontsource/jetbrains-mono/400.css"; -@import "@fontsource/jetbrains-mono/500.css"; -@import "@fontsource/jetbrains-mono/700.css"; +@import "@fontsource/inconsolata/400.css"; +@import "@fontsource/inconsolata/700.css"; +@import "@fontsource-variable/instrument-sans"; @custom-variant dark (&:is(.dark *)); @theme inline { - --font-mono: 'JetBrains Mono', ui-monospace, monospace; - --font-sans: 'JetBrains Mono', ui-monospace, monospace; + --font-mono: 'Inconsolata', ui-monospace, monospace; + --font-sans: 'Instrument Sans Variable', system-ui, sans-serif; --color-ring: var(--ring); --color-input: var(--input); @@ -32,42 +32,46 @@ --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); - --radius-sm: 2px; - --radius-md: 4px; - --radius-lg: 6px; - /* message type accents */ - --color-msg-result: oklch(0.72 0.19 145); - --color-msg-error: oklch(0.63 0.24 25); - --color-msg-plan: oklch(0.68 0.16 250); - --color-msg-code: oklch(0.75 0.12 80); - --color-agent-glow: oklch(0.55 0.15 250 / 0.3); + /* Zero radii — brutalist, no rounded corners */ + --radius-sm: 0; + --radius-md: 0; + --radius-lg: 0; + --radius-xl: 0; + + /* Message type accents */ + --color-hot: #F26522; + --color-msg-result: #22C55E; + --color-msg-error: #EF4444; + --color-msg-plan: #3B82F6; + --color-msg-code: #EAB308; } :root { - --background: oklch(0.12 0.005 260); - --foreground: oklch(0.85 0 0); - --card: oklch(0.15 0.005 260); - --card-foreground: oklch(0.85 0 0); - --popover: oklch(0.15 0.005 260); - --popover-foreground: oklch(0.85 0 0); - --primary: oklch(0.85 0 0); - --primary-foreground: oklch(0.12 0.005 260); - --secondary: oklch(0.2 0.005 260); - --secondary-foreground: oklch(0.75 0 0); - --muted: oklch(0.2 0.005 260); - --muted-foreground: oklch(0.55 0 0); - --accent: oklch(0.22 0.01 260); - --accent-foreground: oklch(0.85 0 0); - --destructive: oklch(0.63 0.24 25); - --border: oklch(0.22 0.01 260); - --input: oklch(0.2 0.005 260); - --ring: oklch(0.55 0.15 250); - --sidebar: oklch(0.1 0.005 260); - --sidebar-foreground: oklch(0.7 0 0); - --sidebar-accent: oklch(0.18 0.01 260); - --sidebar-accent-foreground: oklch(0.9 0 0); - --sidebar-border: oklch(0.2 0.01 260); + /* Warm concrete palette */ + --background: #1a1917; + --foreground: #d4d0c8; + --card: #1f1e1b; + --card-foreground: #d4d0c8; + --popover: #1f1e1b; + --popover-foreground: #d4d0c8; + --primary: #F26522; + --primary-foreground: #1a1917; + --secondary: #2a2825; + --secondary-foreground: #a8a49c; + --muted: #252320; + --muted-foreground: #7a756c; + --accent: #2a2825; + --accent-foreground: #d4d0c8; + --destructive: #EF4444; + --border: #3a3632; + --input: #252320; + --ring: #F26522; + --sidebar: #151413; + --sidebar-foreground: #8a857c; + --sidebar-accent: #252320; + --sidebar-accent-foreground: #d4d0c8; + --sidebar-border: #2a2825; } @layer base { @@ -77,7 +81,7 @@ body { @apply bg-background text-foreground font-mono; font-size: 13px; - line-height: 1.5; + line-height: 1.6; } html, body, #root { height: 100%;