feat: update templates and improve UI styling across components

This commit is contained in:
Abhimanyu Saharan
2026-02-04 13:03:18 +05:30
parent b24e3e1dcd
commit f6105fa0d2
32 changed files with 399 additions and 321 deletions

View File

@@ -4,6 +4,8 @@ import re
from pathlib import Path from pathlib import Path
from uuid import uuid4 from uuid import uuid4
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from app.core.config import settings from app.core.config import settings
from app.integrations.openclaw_gateway import send_message from app.integrations.openclaw_gateway import send_message
from app.models.agents import Agent from app.models.agents import Agent
@@ -33,15 +35,25 @@ def _slugify(value: str) -> str:
return slug or uuid4().hex return slug or uuid4().hex
def _read_templates() -> dict[str, str]: def _template_env() -> Environment:
root = _templates_root() return Environment(
loader=FileSystemLoader(_templates_root()),
autoescape=False,
undefined=StrictUndefined,
keep_trailing_newline=True,
)
def _read_templates(context: dict[str, str]) -> dict[str, str]:
env = _template_env()
templates: dict[str, str] = {} templates: dict[str, str] = {}
for name in TEMPLATE_FILES: for name in TEMPLATE_FILES:
path = root / name path = _templates_root() / name
if path.exists(): if not path.exists():
templates[name] = path.read_text(encoding="utf-8").strip()
else:
templates[name] = "" templates[name] = ""
continue
template = env.get_template(name)
templates[name] = template.render(**context).strip()
return templates return templates
@@ -57,11 +69,28 @@ def _workspace_path(agent_name: str) -> str:
def build_provisioning_message(agent: Agent) -> str: def build_provisioning_message(agent: Agent) -> str:
templates = _read_templates() agent_id = str(agent.id)
agent_id = _slugify(agent.name)
workspace_path = _workspace_path(agent.name) workspace_path = _workspace_path(agent.name)
session_key = agent.openclaw_session_id or "" session_key = agent.openclaw_session_id or ""
base_url = settings.base_url or "" base_url = settings.base_url or "REPLACE_WITH_BASE_URL"
auth_token = "REPLACE_WITH_AUTH_TOKEN"
context = {
"agent_name": agent.name,
"agent_id": agent_id,
"session_key": session_key,
"workspace_path": workspace_path,
"base_url": base_url,
"auth_token": auth_token,
"main_session_key": settings.openclaw_main_session_key or "agent:main:main",
"workspace_root": settings.openclaw_workspace_root or "~/.openclaw/workspaces",
"user_name": "Unset",
"user_preferred_name": "Unset",
"user_timezone": "Unset",
"user_notes": "Fill in user context.",
}
templates = _read_templates(context)
file_blocks = "".join( file_blocks = "".join(
_render_file_block(name, templates.get(name, "")) for name in TEMPLATE_FILES _render_file_block(name, templates.get(name, "")) for name in TEMPLATE_FILES
@@ -73,14 +102,14 @@ def build_provisioning_message(agent: Agent) -> str:
f"Agent id: {agent_id}\n" f"Agent id: {agent_id}\n"
f"Session key: {session_key}\n" f"Session key: {session_key}\n"
f"Workspace path: {workspace_path}\n\n" f"Workspace path: {workspace_path}\n\n"
f"Base URL: {base_url or 'UNSET'}\n\n" f"Base URL: {base_url}\n"
f"Auth token: {auth_token}\n\n"
"Steps:\n" "Steps:\n"
"1) Create the workspace directory.\n" "1) Create the workspace directory.\n"
"2) Write the files below with the exact contents.\n" "2) Write the files below with the exact contents.\n"
f"3) Set BASE_URL to {base_url or '{{BASE_URL}}'} for the agent runtime.\n" "3) Update TOOLS.md if BASE_URL/AUTH_TOKEN must change.\n"
"4) Replace placeholders like {{AGENT_NAME}}, {{AGENT_ID}}, {{BASE_URL}}, {{AUTH_TOKEN}}.\n" "4) Leave BOOTSTRAP.md in place; the agent should run it on first start and delete it.\n"
"5) Leave BOOTSTRAP.md in place; the agent should run it on first start and delete it.\n" "5) Register agent id in OpenClaw so it uses this workspace path.\n\n"
"6) Register agent id in OpenClaw so it uses this workspace path.\n\n"
"Files:" + file_blocks "Files:" + file_blocks
) )

View File

@@ -12,3 +12,4 @@ rq==1.16.2
redis==5.1.1 redis==5.1.1
fastapi-clerk-auth==0.0.9 fastapi-clerk-auth==0.0.9
sse-starlette==2.1.3 sse-starlette==2.1.3
jinja2==3.1.4

View File

@@ -278,8 +278,8 @@ export default function AgentsPage() {
return ( return (
<DashboardShell> <DashboardShell>
<SignedOut> <SignedOut>
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center">
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Sign in to view operational status. Sign in to view operational status.
</p> </p>
<SignInButton <SignInButton
@@ -289,64 +289,56 @@ export default function AgentsPage() {
forceRedirectUrl="/agents" forceRedirectUrl="/agents"
signUpForceRedirectUrl="/agents" signUpForceRedirectUrl="/agents"
> >
<Button className="border-2 border-gray-900 bg-gray-900 text-white"> <Button>Sign in</Button>
Sign in
</Button>
</SignInButton> </SignInButton>
</div> </div>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex h-full flex-col gap-6 rounded-xl border-2 border-gray-200 bg-white p-8 shadow-lush"> <div className="flex h-full flex-col gap-6 rounded-2xl surface-panel p-8">
<div className="flex flex-wrap items-start justify-between gap-4"> <div className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2"> <div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
Operations Operations
</p> </p>
<h1 className="text-2xl font-semibold text-gray-900"> <h1 className="text-2xl font-semibold text-strong">Agents</h1>
Agents <p className="text-sm text-muted">
</h1>
<p className="text-sm text-gray-600">
Live status and heartbeat activity across all agents. Live status and heartbeat activity across all agents.
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-gray-700"
onClick={() => loadData()} onClick={() => loadData()}
disabled={isLoading} disabled={isLoading}
> >
Refresh Refresh
</Button> </Button>
<Button <Button onClick={() => setIsDialogOpen(true)}>
className="border-2 border-gray-900 bg-gray-900 text-white"
onClick={() => setIsDialogOpen(true)}
>
New agent New agent
</Button> </Button>
</div> </div>
</div> </div>
{error ? ( {error ? (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error} {error}
</div> </div>
) : null} ) : null}
<div className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]"> <div className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
<div className="overflow-hidden rounded-xl border border-gray-200"> <div className="overflow-hidden rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)]">
<div className="flex items-center justify-between border-b border-gray-200 bg-gray-50 px-4 py-3"> <div className="flex items-center justify-between border-b border-[color:var(--border)] bg-[color:var(--surface-muted)] px-4 py-3">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Agents Agents
</p> </p>
<p className="text-xs text-gray-500"> <p className="text-xs text-quiet">
{sortedAgents.length} total {sortedAgents.length} total
</p> </p>
</div> </div>
<div className="divide-y divide-gray-200 text-sm"> <div className="divide-y divide-[color:var(--border)] text-sm">
{sortedAgents.length === 0 && !isLoading ? ( {sortedAgents.length === 0 && !isLoading ? (
<div className="p-6 text-sm text-gray-500"> <div className="p-6 text-sm text-muted">
No agents yet. Add one or wait for a heartbeat. No agents yet. Add one or wait for a heartbeat.
</div> </div>
) : ( ) : (
@@ -356,10 +348,8 @@ export default function AgentsPage() {
className="flex flex-wrap items-center justify-between gap-3 px-4 py-3" className="flex flex-wrap items-center justify-between gap-3 px-4 py-3"
> >
<div> <div>
<p className="font-medium text-gray-900"> <p className="font-medium text-strong">{agent.name}</p>
{agent.name} <p className="text-xs text-quiet">
</p>
<p className="text-xs text-gray-500">
Last seen {formatRelative(agent.last_seen_at)} Last seen {formatRelative(agent.last_seen_at)}
</p> </p>
</div> </div>
@@ -367,7 +357,7 @@ export default function AgentsPage() {
<StatusPill status={agent.status} /> <StatusPill status={agent.status} />
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-xs text-gray-700" size="sm"
onClick={() => router.push(`/boards`)} onClick={() => router.push(`/boards`)}
> >
View work View work
@@ -379,7 +369,7 @@ export default function AgentsPage() {
</div> </div>
</div> </div>
<div className="rounded-xl border border-gray-200 bg-gray-50 p-5"> <div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-5">
<Tabs defaultValue="activity"> <Tabs defaultValue="activity">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<TabsList> <TabsList>
@@ -389,28 +379,28 @@ export default function AgentsPage() {
</div> </div>
<TabsContent value="activity"> <TabsContent value="activity">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Activity feed Activity feed
</p> </p>
<p className="text-xs text-gray-500"> <p className="text-xs text-quiet">
{events.length} events {events.length} events
</p> </p>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{events.length === 0 && !isLoading ? ( {events.length === 0 && !isLoading ? (
<div className="rounded-lg border border-dashed border-gray-200 bg-white p-4 text-sm text-gray-500"> <div className="rounded-lg border border-dashed border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted">
No activity yet. No activity yet.
</div> </div>
) : ( ) : (
events.map((event) => ( events.map((event) => (
<div <div
key={event.id} key={event.id}
className="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-700" className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted"
> >
<p className="font-medium text-gray-900"> <p className="font-medium text-strong">
{event.message ?? event.event_type} {event.message ?? event.event_type}
</p> </p>
<p className="mt-1 text-xs text-gray-500"> <p className="mt-1 text-xs text-quiet">
{formatTimestamp(event.created_at)} {formatTimestamp(event.created_at)}
</p> </p>
</div> </div>
@@ -420,45 +410,45 @@ export default function AgentsPage() {
</TabsContent> </TabsContent>
<TabsContent value="gateway"> <TabsContent value="gateway">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
OpenClaw Gateway OpenClaw Gateway
</p> </p>
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-xs text-gray-700" size="sm"
onClick={() => loadGateway()} onClick={() => loadGateway()}
> >
Refresh Refresh
</Button> </Button>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-700"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<p className="font-medium text-gray-900"> <p className="font-medium text-strong">
{gatewayStatus?.connected ? "Connected" : "Not connected"} {gatewayStatus?.connected ? "Connected" : "Not connected"}
</p> </p>
<StatusPill <StatusPill
status={gatewayStatus?.connected ? "online" : "offline"} status={gatewayStatus?.connected ? "online" : "offline"}
/> />
</div> </div>
<p className="mt-1 text-xs text-gray-500"> <p className="mt-1 text-xs text-quiet">
{gatewayStatus?.gateway_url ?? "Gateway URL not set"} {gatewayStatus?.gateway_url ?? "Gateway URL not set"}
</p> </p>
{gatewayStatus?.error ? ( {gatewayStatus?.error ? (
<p className="mt-2 text-xs text-red-500"> <p className="mt-2 text-xs text-[color:var(--danger)]">
{gatewayStatus.error} {gatewayStatus.error}
</p> </p>
) : null} ) : null}
</div> </div>
<div className="rounded-lg border border-gray-200 bg-white"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)]">
<div className="flex items-center justify-between border-b border-gray-200 px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <div className="flex items-center justify-between border-b border-[color:var(--border)] px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
<span>Sessions</span> <span>Sessions</span>
<span>{gatewaySessions.length}</span> <span>{gatewaySessions.length}</span>
</div> </div>
<div className="max-h-56 divide-y divide-gray-200 overflow-y-auto text-sm"> <div className="max-h-56 divide-y divide-[color:var(--border)] overflow-y-auto text-sm">
{gatewaySessions.length === 0 ? ( {gatewaySessions.length === 0 ? (
<div className="p-4 text-sm text-gray-500"> <div className="p-4 text-sm text-muted">
No sessions found. No sessions found.
</div> </div>
) : ( ) : (
@@ -473,7 +463,7 @@ export default function AgentsPage() {
<button <button
key={getSessionKey(session, index)} key={getSessionKey(session, index)}
type="button" type="button"
className="flex w-full items-center justify-between px-4 py-3 text-left text-sm hover:bg-gray-50" className="flex w-full items-center justify-between px-4 py-3 text-left text-sm transition hover:bg-[color:var(--surface-muted)]"
onClick={() => { onClick={() => {
setSelectedSession(session); setSelectedSession(session);
if (sessionId) { if (sessionId) {
@@ -482,12 +472,12 @@ export default function AgentsPage() {
}} }}
> >
<div> <div>
<p className="font-medium text-gray-900">{display}</p> <p className="font-medium text-strong">{display}</p>
<p className="text-xs text-gray-500"> <p className="text-xs text-quiet">
{session.status ?? "active"} {session.status ?? "active"}
</p> </p>
</div> </div>
<span className="text-xs text-gray-400">Open</span> <span className="text-xs text-quiet">Open</span>
</button> </button>
); );
}) })
@@ -496,19 +486,19 @@ export default function AgentsPage() {
</div> </div>
{selectedSession ? ( {selectedSession ? (
<div className="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-700"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted">
<div className="mb-3 space-y-1"> <div className="mb-3 space-y-1">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Session details Session details
</p> </p>
<p className="font-medium text-gray-900"> <p className="font-medium text-strong">
{selectedSession.displayName ?? {selectedSession.displayName ??
selectedSession.label ?? selectedSession.label ??
selectedSession.key ?? selectedSession.key ??
"Session"} "Session"}
</p> </p>
</div> </div>
<div className="mb-4 max-h-40 space-y-2 overflow-y-auto rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="mb-4 max-h-40 space-y-2 overflow-y-auto rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{sessionHistory.length === 0 ? ( {sessionHistory.length === 0 ? (
<p>No history loaded.</p> <p>No history loaded.</p>
) : ( ) : (
@@ -520,17 +510,17 @@ export default function AgentsPage() {
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-500"> <label className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Send message Send message
</label> </label>
<Input <Input
value={message} value={message}
onChange={(event) => setMessage(event.target.value)} onChange={(event) => setMessage(event.target.value)}
placeholder="Type a message to the session" placeholder="Type a message to the session"
className="h-10 rounded-lg border-2 border-gray-200 bg-white" className="h-10"
/> />
<Button <Button
className="w-full border-2 border-gray-900 bg-gray-900 text-white" className="w-full"
onClick={handleSendMessage} onClick={handleSendMessage}
disabled={isSending} disabled={isSending}
> >
@@ -541,7 +531,7 @@ export default function AgentsPage() {
) : null} ) : null}
{gatewayError ? ( {gatewayError ? (
<div className="rounded-lg border border-gray-200 bg-white p-3 text-xs text-red-500"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-3 text-xs text-[color:var(--danger)]">
{gatewayError} {gatewayError}
</div> </div>
) : null} ) : null}
@@ -571,20 +561,16 @@ export default function AgentsPage() {
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800"> <label className="text-sm font-medium text-strong">Agent name</label>
Agent name
</label>
<Input <Input
value={name} value={name}
onChange={(event) => setName(event.target.value)} onChange={(event) => setName(event.target.value)}
placeholder="e.g. Deployment bot" placeholder="e.g. Deployment bot"
className="h-11 rounded-lg border-2 border-gray-200 bg-white" className="h-11"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800"> <label className="text-sm font-medium text-strong">Status</label>
Status
</label>
<Select value={status} onValueChange={setStatus}> <Select value={status} onValueChange={setStatus}>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select status" /> <SelectValue placeholder="Select status" />
@@ -599,7 +585,7 @@ export default function AgentsPage() {
</Select> </Select>
</div> </div>
{createError ? ( {createError ? (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{createError} {createError}
</div> </div>
) : null} ) : null}
@@ -607,13 +593,11 @@ export default function AgentsPage() {
<DialogFooter> <DialogFooter>
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-gray-700"
onClick={() => setIsDialogOpen(false)} onClick={() => setIsDialogOpen(false)}
> >
Cancel Cancel
</Button> </Button>
<Button <Button
className="border-2 border-gray-900 bg-gray-900 text-white"
onClick={handleCreate} onClick={handleCreate}
disabled={isCreating} disabled={isCreating}
> >

View File

@@ -168,8 +168,8 @@ export default function BoardDetailPage() {
return ( return (
<DashboardShell> <DashboardShell>
<SignedOut> <SignedOut>
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center">
<p className="text-sm text-gray-600">Sign in to view boards.</p> <p className="text-sm text-muted">Sign in to view boards.</p>
<SignInButton <SignInButton
mode="modal" mode="modal"
afterSignInUrl="/boards" afterSignInUrl="/boards"
@@ -177,30 +177,27 @@ export default function BoardDetailPage() {
forceRedirectUrl="/boards" forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards" signUpForceRedirectUrl="/boards"
> >
<Button className="border-2 border-gray-900 bg-gray-900 text-white"> <Button>Sign in</Button>
Sign in
</Button>
</SignInButton> </SignInButton>
</div> </div>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex h-full flex-col gap-6 rounded-xl border-2 border-gray-200 bg-white p-8 shadow-lush"> <div className="flex h-full flex-col gap-6 rounded-2xl surface-panel p-8">
<div className="flex flex-wrap items-start justify-between gap-4"> <div className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2"> <div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
{board?.slug ?? "board"} {board?.slug ?? "board"}
</p> </p>
<h1 className="text-2xl font-semibold text-gray-900"> <h1 className="text-2xl font-semibold text-strong">
{board?.name ?? "Board"} {board?.name ?? "Board"}
</h1> </h1>
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Keep tasks moving through your workflow. Keep tasks moving through your workflow.
</p> </p>
</div> </div>
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-gray-700"
onClick={() => router.push("/boards")} onClick={() => router.push("/boards")}
> >
Back to boards Back to boards
@@ -208,13 +205,13 @@ export default function BoardDetailPage() {
</div> </div>
{error && ( {error && (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error} {error}
</div> </div>
)} )}
{isLoading ? ( {isLoading ? (
<div className="flex flex-1 items-center justify-center text-sm text-gray-500"> <div className="flex flex-1 items-center justify-center text-sm text-muted">
Loading {titleLabel} Loading {titleLabel}
</div> </div>
) : ( ) : (
@@ -245,27 +242,26 @@ export default function BoardDetailPage() {
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800">Title</label> <label className="text-sm font-medium text-strong">Title</label>
<Input <Input
value={title} value={title}
onChange={(event) => setTitle(event.target.value)} onChange={(event) => setTitle(event.target.value)}
placeholder="e.g. Prepare launch notes" placeholder="e.g. Prepare launch notes"
className="h-11 rounded-lg border-2 border-gray-200 bg-white"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800"> <label className="text-sm font-medium text-strong">
Description Description
</label> </label>
<Textarea <Textarea
value={description} value={description}
onChange={(event) => setDescription(event.target.value)} onChange={(event) => setDescription(event.target.value)}
placeholder="Optional details" placeholder="Optional details"
className="min-h-[120px] rounded-lg border-2 border-gray-200 bg-white" className="min-h-[120px]"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800"> <label className="text-sm font-medium text-strong">
Priority Priority
</label> </label>
<Select value={priority} onValueChange={setPriority}> <Select value={priority} onValueChange={setPriority}>
@@ -282,7 +278,7 @@ export default function BoardDetailPage() {
</Select> </Select>
</div> </div>
{createError ? ( {createError ? (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{createError} {createError}
</div> </div>
) : null} ) : null}
@@ -290,13 +286,11 @@ export default function BoardDetailPage() {
<DialogFooter> <DialogFooter>
<Button <Button
variant="outline" variant="outline"
className="border-2 border-gray-200 text-gray-700"
onClick={() => setIsDialogOpen(false)} onClick={() => setIsDialogOpen(false)}
> >
Cancel Cancel
</Button> </Button>
<Button <Button
className="border-2 border-gray-900 bg-gray-900 text-white"
onClick={handleCreateTask} onClick={handleCreateTask}
disabled={isCreating} disabled={isCreating}
> >

View File

@@ -66,8 +66,8 @@ export default function NewBoardPage() {
return ( return (
<DashboardShell> <DashboardShell>
<SignedOut> <SignedOut>
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush lg:col-span-2"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center lg:col-span-2">
<p className="text-sm text-gray-600">Sign in to create a board.</p> <p className="text-sm text-muted">Sign in to create a board.</p>
<SignInButton <SignInButton
mode="modal" mode="modal"
afterSignInUrl="/boards/new" afterSignInUrl="/boards/new"
@@ -75,47 +75,44 @@ export default function NewBoardPage() {
forceRedirectUrl="/boards/new" forceRedirectUrl="/boards/new"
signUpForceRedirectUrl="/boards/new" signUpForceRedirectUrl="/boards/new"
> >
<Button className="border-2 border-gray-900 bg-gray-900 text-white"> <Button>Sign in</Button>
Sign in
</Button>
</SignInButton> </SignInButton>
</div> </div>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex h-full flex-col justify-center rounded-xl border-2 border-gray-200 bg-white p-8 shadow-lush"> <div className="flex h-full flex-col justify-center rounded-2xl surface-panel p-8">
<div className="mb-6 space-y-2"> <div className="mb-6 space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
New board New board
</p> </p>
<h1 className="text-2xl font-semibold text-gray-900"> <h1 className="text-2xl font-semibold text-strong">
Spin up a board. Spin up a board.
</h1> </h1>
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Boards are where tasks live and move through your workflow. Boards are where tasks live and move through your workflow.
</p> </p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-gray-800"> <label className="text-sm font-medium text-strong">
Board name Board name
</label> </label>
<Input <Input
value={name} value={name}
onChange={(event) => setName(event.target.value)} onChange={(event) => setName(event.target.value)}
placeholder="e.g. Product ops" placeholder="e.g. Product ops"
className="h-11 rounded-lg border-2 border-gray-200 bg-white"
disabled={isLoading} disabled={isLoading}
/> />
</div> </div>
{error ? ( {error ? (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error} {error}
</div> </div>
) : null} ) : null}
<Button <Button
type="submit" type="submit"
className="w-full border-2 border-gray-900 bg-gray-900 text-white" className="w-full"
disabled={isLoading} disabled={isLoading}
> >
{isLoading ? "Creating…" : "Create board"} {isLoading ? "Creating…" : "Create board"}
@@ -123,7 +120,7 @@ export default function NewBoardPage() {
</form> </form>
<Button <Button
variant="outline" variant="outline"
className="mt-4 border-2 border-gray-200 text-gray-700" className="mt-4"
onClick={() => router.push("/boards")} onClick={() => router.push("/boards")}
> >
Back to boards Back to boards

View File

@@ -73,8 +73,8 @@ export default function BoardsPage() {
header: "Board", header: "Board",
cell: ({ row }) => ( cell: ({ row }) => (
<div> <div>
<p className="font-medium text-gray-900">{row.original.name}</p> <p className="font-medium text-strong">{row.original.name}</p>
<p className="text-xs text-gray-500">{row.original.slug}</p> <p className="text-xs text-quiet">{row.original.slug}</p>
</div> </div>
), ),
}, },
@@ -88,7 +88,7 @@ export default function BoardsPage() {
> >
<Link <Link
href={`/boards/${row.original.id}`} href={`/boards/${row.original.id}`}
className="inline-flex h-8 items-center justify-center rounded-lg border-2 border-gray-200 px-3 text-xs font-medium text-gray-700" className="inline-flex h-8 items-center justify-center rounded-lg border border-[color:var(--border)] px-3 text-xs font-medium text-muted transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
> >
Open Open
</Link> </Link>
@@ -108,8 +108,8 @@ export default function BoardsPage() {
return ( return (
<DashboardShell> <DashboardShell>
<SignedOut> <SignedOut>
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush lg:col-span-2"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center lg:col-span-2">
<p className="text-sm text-gray-600">Sign in to view boards.</p> <p className="text-sm text-muted">Sign in to view boards.</p>
<SignInButton <SignInButton
mode="modal" mode="modal"
afterSignInUrl="/boards" afterSignInUrl="/boards"
@@ -117,51 +117,46 @@ export default function BoardsPage() {
forceRedirectUrl="/boards" forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards" signUpForceRedirectUrl="/boards"
> >
<Button className="border-2 border-gray-900 bg-gray-900 text-white"> <Button>Sign in</Button>
Sign in
</Button>
</SignInButton> </SignInButton>
</div> </div>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex h-full flex-col gap-4 rounded-xl border-2 border-gray-200 bg-white p-8 shadow-lush"> <div className="flex h-full flex-col gap-6 rounded-2xl surface-panel p-8">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div> <div>
<h2 className="text-lg font-semibold text-gray-900">Boards</h2> <h2 className="text-lg font-semibold text-strong">Boards</h2>
<p className="text-sm text-gray-500"> <p className="text-sm text-muted">
{sortedBoards.length} board {sortedBoards.length} board
{sortedBoards.length === 1 ? "" : "s"} total. {sortedBoards.length === 1 ? "" : "s"} total.
</p> </p>
</div> </div>
<Button <Button onClick={() => router.push("/boards/new")}>
className="border-2 border-gray-900 bg-gray-900 text-white"
onClick={() => router.push("/boards/new")}
>
New board New board
</Button> </Button>
</div> </div>
{error && ( {error && (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error} {error}
</div> </div>
)} )}
{sortedBoards.length === 0 && !isLoading ? ( {sortedBoards.length === 0 && !isLoading ? (
<div className="flex flex-1 flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-gray-200 bg-gray-50 p-6 text-center text-sm text-gray-500"> <div className="flex flex-1 flex-col items-center justify-center gap-2 rounded-2xl border border-dashed border-[color:var(--border)] bg-[color:var(--surface-muted)] p-6 text-center text-sm text-muted">
No boards yet. Create your first board to get started. No boards yet. Create your first board to get started.
</div> </div>
) : ( ) : (
<div className="overflow-hidden rounded-lg border border-gray-200"> <div className="overflow-hidden rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)]">
<table className="min-w-full divide-y divide-gray-200 text-sm"> <table className="min-w-full divide-y divide-[color:var(--border)] text-sm">
<thead className="bg-gray-50"> <thead className="bg-[color:var(--surface-muted)]">
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<th <th
key={header.id} key={header.id}
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-gray-500" className="px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-[0.22em] text-quiet"
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@@ -174,11 +169,11 @@ export default function BoardsPage() {
</tr> </tr>
))} ))}
</thead> </thead>
<tbody className="divide-y divide-gray-200 bg-white"> <tbody className="divide-y divide-[color:var(--border)] bg-[color:var(--surface)]">
{table.getRowModel().rows.map((row) => ( {table.getRowModel().rows.map((row) => (
<tr <tr
key={row.id} key={row.id}
className="cursor-pointer hover:bg-gray-50" className="cursor-pointer transition hover:bg-[color:var(--surface-muted)]"
onClick={() => router.push(`/boards/${row.original.id}`)} onClick={() => router.push(`/boards/${row.original.id}`)}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (

View File

@@ -14,8 +14,8 @@ export default function DashboardPage() {
return ( return (
<DashboardShell> <DashboardShell>
<SignedOut> <SignedOut>
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center">
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Sign in to access your dashboard. Sign in to access your dashboard.
</p> </p>
<SignInButton <SignInButton
@@ -25,22 +25,17 @@ export default function DashboardPage() {
forceRedirectUrl="/boards" forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards" signUpForceRedirectUrl="/boards"
> >
<Button className="border-2 border-gray-900 bg-gray-900 text-white"> <Button>Sign in</Button>
Sign in
</Button>
</SignInButton> </SignInButton>
</div> </div>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-xl border-2 border-gray-200 bg-white p-10 text-center shadow-lush"> <div className="flex h-full flex-col items-center justify-center gap-4 rounded-2xl surface-panel p-10 text-center">
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Your work lives in boards. Jump in to manage tasks. Your work lives in boards. Jump in to manage tasks.
</p> </p>
<Button <Button onClick={() => router.push("/boards")}>
className="border-2 border-gray-900 bg-gray-900 text-white"
onClick={() => router.push("/boards")}
>
Go to boards Go to boards
</Button> </Button>
</div> </div>

View File

@@ -4,14 +4,33 @@
:root { :root {
color-scheme: light; color-scheme: light;
--bg: #f4f6fa;
--surface: #ffffff;
--surface-muted: #eef2f7;
--surface-strong: #e6ecf6;
--border: #d6dce6;
--border-strong: #c2cad9;
--text: #0b1220;
--text-muted: #4b5a73;
--text-quiet: #7c8aa0;
--accent: #1d4ed8;
--accent-strong: #1e40af;
--accent-soft: rgba(29, 78, 216, 0.12);
--success: #0f766e;
--warning: #b45309;
--danger: #b42318;
--shadow-panel: 0 18px 40px rgba(12, 17, 29, 0.08);
--shadow-card: 0 12px 24px rgba(12, 17, 29, 0.06);
} }
body { body {
@apply bg-white text-gray-900 font-body; @apply font-body;
background: var(--bg);
color: var(--text);
} }
* { * {
@apply border-black/10; border-color: var(--border);
} }
@keyframes fade-in-up { @keyframes fade-in-up {
@@ -54,6 +73,35 @@ body {
} }
@layer utilities { @layer utilities {
.bg-app {
background: var(--bg);
}
.text-strong {
color: var(--text);
}
.text-muted {
color: var(--text-muted);
}
.text-quiet {
color: var(--text-quiet);
}
.surface-card {
background: var(--surface);
border: 1px solid var(--border);
box-shadow: var(--shadow-card);
}
.surface-panel {
background: var(--surface);
border: 1px solid var(--border);
box-shadow: var(--shadow-panel);
}
.surface-muted {
background: var(--surface-muted);
border: 1px solid var(--border);
}
.border-strong {
border-color: var(--border-strong);
}
.animate-fade-in-up { .animate-fade-in-up {
animation: fade-in-up 0.6s ease-out both; animation: fade-in-up 0.6s ease-out both;
} }
@@ -66,25 +114,14 @@ body {
.animate-progress-shimmer { .animate-progress-shimmer {
animation: progress-shimmer 1.8s linear infinite; animation: progress-shimmer 1.8s linear infinite;
} }
.glass-panel {
background: #ffffff;
border: 1px solid #0b0b0b;
box-shadow: 4px 4px 0 #0b0b0b;
}
.shadow-lush { .shadow-lush {
box-shadow: 6px 6px 0 #0b0b0b; box-shadow: var(--shadow-panel);
}
.soft-shadow {
box-shadow: 0 24px 60px rgba(11, 11, 11, 0.12);
}
.soft-shadow-sm {
box-shadow: 0 16px 32px rgba(11, 11, 11, 0.08);
} }
.bg-landing-grid { .bg-landing-grid {
background-image: background-image:
linear-gradient(to right, rgba(11, 11, 11, 0.08) 1px, transparent 1px), linear-gradient(to right, rgba(12, 17, 29, 0.08) 1px, transparent 1px),
linear-gradient(to bottom, rgba(11, 11, 11, 0.08) 1px, transparent 1px); linear-gradient(to bottom, rgba(12, 17, 29, 0.08) 1px, transparent 1px);
background-size: 80px 80px; background-size: 120px 120px;
} }
} }

View File

@@ -4,23 +4,25 @@ import type { Metadata } from "next";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ClerkProvider } from "@clerk/nextjs"; import { ClerkProvider } from "@clerk/nextjs";
import { Inter, Space_Grotesk } from "next/font/google"; import { IBM_Plex_Sans, Sora } from "next/font/google";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "OpenClaw Mission Control", title: "OpenClaw Mission Control",
description: "A calm command center for every task.", description: "A calm command center for every task.",
}; };
const bodyFont = Inter({ const bodyFont = IBM_Plex_Sans({
subsets: ["latin"], subsets: ["latin"],
display: "swap", display: "swap",
variable: "--font-body", variable: "--font-body",
weight: ["400", "500", "600", "700"],
}); });
const headingFont = Space_Grotesk({ const headingFont = Sora({
subsets: ["latin"], subsets: ["latin"],
display: "swap", display: "swap",
variable: "--font-heading", variable: "--font-heading",
weight: ["500", "600", "700"],
}); });
export default function RootLayout({ children }: { children: ReactNode }) { export default function RootLayout({ children }: { children: ReactNode }) {
@@ -28,7 +30,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<ClerkProvider> <ClerkProvider>
<html lang="en"> <html lang="en">
<body <body
className={`${bodyFont.variable} ${headingFont.variable} min-h-screen bg-white text-gray-900 antialiased`} className={`${bodyFont.variable} ${headingFont.variable} min-h-screen bg-app text-strong antialiased`}
> >
{children} {children}
</body> </body>

View File

@@ -1,16 +1,17 @@
export function BrandMark() { export function BrandMark() {
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="grid h-10 w-10 place-items-center rounded-lg border-2 border-gray-200 bg-white text-sm font-bold text-gray-900 shadow-lush"> <div className="relative grid h-11 w-11 place-items-center rounded-2xl bg-[color:var(--accent)] text-sm font-semibold text-white shadow-lush">
<span className="font-heading tracking-[0.2em]"> <span className="font-heading tracking-[0.18em]">OC</span>
OC <span className="absolute -bottom-1 h-1 w-6 rounded-full bg-[color:var(--accent-strong)]" />
</span>
</div> </div>
<div className="leading-tight"> <div className="leading-tight">
<div className="font-heading text-sm uppercase tracking-[0.28em] text-gray-600"> <div className="font-heading text-sm uppercase tracking-[0.28em] text-strong">
OpenClaw OpenClaw
</div> </div>
<div className="text-[11px] font-medium text-gray-500">Mission Control</div> <div className="text-[11px] font-medium text-quiet">
Mission Control
</div>
</div> </div>
</div> </div>
); );

View File

@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
export function HeroKicker({ children }: { children: ReactNode }) { export function HeroKicker({ children }: { children: ReactNode }) {
return ( return (
<span className="inline-flex items-center rounded-full bg-gray-100 px-4 py-1 text-[11px] font-semibold uppercase tracking-[0.35em] text-gray-600"> <span className="inline-flex items-center rounded-full bg-[color:var(--accent-soft)] px-4 py-1 text-[11px] font-semibold uppercase tracking-[0.35em] text-[color:var(--accent-strong)]">
{children} {children}
</span> </span>
); );

View File

@@ -1,21 +1,24 @@
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
const STATUS_STYLES: Record<string, "default" | "outline" | "ember"> = { const STATUS_STYLES: Record<
string,
"default" | "outline" | "accent" | "success" | "warning" | "danger"
> = {
inbox: "outline", inbox: "outline",
assigned: "default", assigned: "accent",
in_progress: "ember", in_progress: "warning",
testing: "outline", testing: "accent",
review: "default", review: "accent",
done: "default", done: "success",
online: "default", online: "success",
busy: "ember", busy: "warning",
offline: "outline", offline: "outline",
}; };
export function StatusPill({ status }: { status: string }) { export function StatusPill({ status }: { status: string }) {
return ( return (
<Badge variant={STATUS_STYLES[status] ?? "default"}> <Badge variant={STATUS_STYLES[status] ?? "default"}>
{status.replace("_", " ")} {status.replaceAll("_", " ")}
</Badge> </Badge>
); );
} }

View File

@@ -5,14 +5,14 @@ export function HeroCopy() {
<div className="space-y-6"> <div className="space-y-6">
<HeroKicker>Mission Control</HeroKicker> <HeroKicker>Mission Control</HeroKicker>
<div className="space-y-4"> <div className="space-y-4">
<h1 className="font-heading text-4xl font-bold leading-tight text-gray-900 sm:text-5xl lg:text-6xl"> <h1 className="font-heading text-4xl font-semibold leading-tight text-strong sm:text-5xl lg:text-6xl">
Orchestrate work without Enterprise control for
<br /> <br />
the daily status chase. autonomous execution.
</h1> </h1>
<p className="max-w-xl text-base text-gray-600 sm:text-lg"> <p className="max-w-xl text-base text-muted sm:text-lg">
OpenClaw keeps every task, agent, and delivery signal in one place so Coordinate boards, agents, and approvals in one command layer. No
teams can spot momentum shifts fast. status meetings. No blind spots. Just durable execution.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -12,15 +12,15 @@ interface TaskCardProps {
export function TaskCard({ title, status, assignee, due }: TaskCardProps) { export function TaskCard({ title, status, assignee, due }: TaskCardProps) {
return ( return (
<Card className="border-gray-200 bg-white"> <Card className="border border-[color:var(--border)] bg-[color:var(--surface)]">
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="space-y-1"> <div className="space-y-2">
<p className="text-sm font-semibold text-gray-900">{title}</p> <p className="text-sm font-semibold text-strong">{title}</p>
<StatusPill status={status} /> <StatusPill status={status} />
</div> </div>
</div> </div>
<div className="flex items-center justify-between text-xs text-gray-600"> <div className="flex items-center justify-between text-xs text-muted">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<UserCircle className="h-4 w-4" /> <UserCircle className="h-4 w-4" />
<span>{assignee ?? "Unassigned"}</span> <span>{assignee ?? "Unassigned"}</span>

View File

@@ -2,6 +2,7 @@
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Bot, LayoutGrid } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -9,34 +10,44 @@ export function DashboardSidebar() {
const pathname = usePathname(); const pathname = usePathname();
return ( return (
<aside className="flex h-full flex-col gap-6 rounded-xl border-2 border-gray-200 bg-white p-6 shadow-lush"> <aside className="flex h-full flex-col gap-6 rounded-2xl surface-panel p-5">
<div className="space-y-2"> <div className="space-y-3">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-gray-500"> <p className="text-xs font-semibold uppercase tracking-[0.32em] text-quiet">
Work Navigation
</p> </p>
<nav className="space-y-1 text-sm"> <nav className="space-y-2 text-sm">
<Link
href="/agents"
className={cn(
"block rounded-lg border border-transparent px-3 py-2 font-medium text-gray-700 hover:border-gray-200 hover:bg-gray-50",
pathname.startsWith("/agents") &&
"border-gray-200 bg-gray-50 text-gray-900"
)}
>
Agents
</Link>
<Link <Link
href="/boards" href="/boards"
className={cn( className={cn(
"block rounded-lg border border-transparent px-3 py-2 font-medium text-gray-700 hover:border-gray-200 hover:bg-gray-50", "flex items-center gap-3 rounded-xl border border-transparent px-3 py-2 font-semibold text-muted transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface-muted)]",
pathname.startsWith("/boards") && pathname.startsWith("/boards") &&
"border-gray-200 bg-gray-50 text-gray-900" "border-[color:var(--accent-soft)] bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]"
)} )}
> >
<LayoutGrid className="h-4 w-4" />
Boards Boards
</Link> </Link>
<Link
href="/agents"
className={cn(
"flex items-center gap-3 rounded-xl border border-transparent px-3 py-2 font-semibold text-muted transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface-muted)]",
pathname.startsWith("/agents") &&
"border-[color:var(--accent-soft)] bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]"
)}
>
<Bot className="h-4 w-4" />
Agents
</Link>
</nav> </nav>
</div> </div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-4 text-xs text-quiet">
<p className="font-semibold uppercase tracking-[0.2em] text-strong">
Ops health
</p>
<p className="mt-2">
Live boards and agents appear here once data streams in.
</p>
</div>
</aside> </aside>
); );
} }

View File

@@ -7,16 +7,10 @@ import { Button } from "@/components/ui/button";
export function LandingHero() { export function LandingHero() {
return ( return (
<section className="grid w-full items-center gap-10 lg:grid-cols-[1.05fr_0.95fr]"> <section className="grid w-full items-center gap-12 lg:grid-cols-[1.1fr_0.9fr]">
<div <div className="space-y-8 animate-fade-in-up">
className="space-y-8 animate-fade-in-up"
style={{ animationDelay: "0.05s" }}
>
<HeroCopy /> <HeroCopy />
<div <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
className="flex flex-col gap-3 sm:flex-row sm:items-center animate-fade-in-up"
style={{ animationDelay: "0.12s" }}
>
<SignedOut> <SignedOut>
<SignInButton <SignInButton
mode="modal" mode="modal"
@@ -25,60 +19,85 @@ export function LandingHero() {
forceRedirectUrl="/boards" forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards" signUpForceRedirectUrl="/boards"
> >
<Button <Button size="lg" className="w-full sm:w-auto">
size="lg"
className="w-full sm:w-auto border-2 border-gray-900 bg-gray-900 text-white hover:bg-gray-900/90"
>
Sign in to open mission control Sign in to open mission control
</Button> </Button>
</SignInButton> </SignInButton>
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
<div className="text-sm text-gray-600"> <div className="text-sm text-muted">
You&apos;re signed in. Open your boards when you&apos;re ready. You&apos;re signed in. Open your boards when you&apos;re ready.
</div> </div>
</SignedIn> </SignedIn>
</div> </div>
<p <div className="flex flex-wrap gap-3 text-xs font-semibold uppercase tracking-[0.28em] text-quiet">
className="text-xs uppercase tracking-[0.3em] text-gray-500 animate-fade-in-up" <span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
style={{ animationDelay: "0.18s" }} Enterprise ready
> </span>
One login · clear ownership · faster decisions <span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
</p> Agent-first ops
</span>
<span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
24/7 visibility
</span>
</div>
</div> </div>
<div <div className="relative animate-fade-in-up">
className="relative animate-fade-in-up" <div className="surface-panel rounded-3xl p-6">
style={{ animationDelay: "0.3s" }} <div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
> <span>Command surface</span>
<div className="glass-panel rounded-2xl bg-white p-6"> <span className="rounded-full border border-[color:var(--border)] px-2 py-1 text-[10px]">
<div className="flex flex-col gap-6"> Live
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.3em] text-gray-500"> </span>
<span>Status</span> </div>
<span className="rounded-full border border-gray-200 px-2 py-1 text-[10px]"> <div className="mt-6 space-y-4">
Live <div>
</span> <p className="text-lg font-semibold text-strong">
</div> Tasks claimed, tracked, delivered.
<div className="space-y-2">
<p className="text-lg font-semibold text-gray-900">
Tasks claimed automatically
</p> </p>
<p className="text-sm text-gray-600"> <p className="text-sm text-muted">
Agents pick the next task in queue, report progress, and ship See every queue, agent, and handoff without chasing updates.
deliverables back to you.
</p> </p>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-3 gap-3">
{["Assignments", "In review", "Delivered", "Signals"].map( {[
(label) => ( { label: "Active boards", value: "12" },
<div { label: "Agents live", value: "08" },
key={label} { label: "Tasks in flow", value: "46" },
className="rounded-xl border-2 border-gray-200 bg-white p-4 text-sm font-semibold text-gray-900 soft-shadow-sm" ].map((item) => (
> <div
{label} key={item.label}
className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-4 text-center"
>
<div className="text-xl font-semibold text-strong">
{item.value}
</div> </div>
) <div className="text-[11px] uppercase tracking-[0.18em] text-quiet">
)} {item.label}
</div>
</div>
))}
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4">
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
<span>Signals</span>
<span>Updated 2m ago</span>
</div>
<div className="mt-3 space-y-2 text-sm text-muted">
<div className="flex items-center justify-between">
<span>Agent Delta moved task to review</span>
<span className="text-quiet">Just now</span>
</div>
<div className="flex items-center justify-between">
<span>Board Growth Ops hit WIP limit</span>
<span className="text-quiet">5m</span>
</div>
<div className="flex items-center justify-between">
<span>Release tasks stabilized</span>
<span className="text-quiet">12m</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -55,18 +55,16 @@ export function TaskBoard({
}, [tasks]); }, [tasks]);
return ( return (
<div className="grid grid-flow-col auto-cols-[minmax(260px,320px)] gap-6 overflow-x-auto pb-2"> <div className="grid grid-flow-col auto-cols-[minmax(260px,320px)] gap-6 overflow-x-auto pb-4">
{columns.map((column) => { {columns.map((column) => {
const columnTasks = grouped[column.status] ?? []; const columnTasks = grouped[column.status] ?? [];
return ( return (
<div key={column.title} className="space-y-4"> <div key={column.title} className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900"> <h3 className="text-sm font-semibold text-strong">
{column.title} {column.title}
</h3> </h3>
<span className="text-xs text-gray-500"> <span className="text-xs text-quiet">{columnTasks.length}</span>
{columnTasks.length}
</span>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{column.status === "inbox" ? ( {column.status === "inbox" ? (
@@ -75,7 +73,7 @@ export function TaskBoard({
onClick={onCreateTask} onClick={onCreateTask}
disabled={isCreateDisabled} disabled={isCreateDisabled}
className={cn( className={cn(
"flex w-full items-center justify-center rounded-xl border-2 border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 transition hover:border-gray-300 hover:bg-white", "flex w-full items-center justify-center rounded-2xl border border-dashed border-[color:var(--border)] bg-[color:var(--surface-muted)] px-4 py-6 text-[11px] font-semibold uppercase tracking-[0.2em] text-quiet transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface)]",
isCreateDisabled && "cursor-not-allowed opacity-60" isCreateDisabled && "cursor-not-allowed opacity-60"
)} )}
> >

View File

@@ -8,22 +8,24 @@ import { BrandMark } from "@/components/atoms/BrandMark";
export function DashboardShell({ children }: { children: ReactNode }) { export function DashboardShell({ children }: { children: ReactNode }) {
return ( return (
<div className="relative min-h-screen bg-white text-gray-900"> <div className="relative min-h-screen bg-app text-strong">
<div <div
className="absolute inset-0 bg-landing-grid opacity-[0.35] pointer-events-none" className="absolute inset-0 bg-landing-grid opacity-[0.18] pointer-events-none"
aria-hidden="true" aria-hidden="true"
/> />
<div className="relative flex min-h-screen w-full flex-col gap-8 px-6 pb-10 pt-8"> <div className="relative flex min-h-screen w-full flex-col">
<header className="flex flex-wrap items-center justify-between gap-4"> <header className="flex flex-wrap items-center justify-between gap-4 border-b border-[color:var(--border)] bg-[color:rgba(244,246,250,0.8)] px-6 py-5 backdrop-blur">
<BrandMark /> <BrandMark />
<SignedIn> <SignedIn>
<div className="rounded-lg border-2 border-gray-200 bg-white px-2 py-1"> <div className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-2 py-1 shadow-sm">
<UserButton /> <UserButton />
</div> </div>
</SignedIn> </SignedIn>
</header> </header>
<div className="grid flex-1 gap-6 lg:grid-cols-[320px_1fr]"> <div className="flex-1 px-6 py-6">
{children} <div className="grid gap-6 lg:grid-cols-[260px_1fr]">
{children}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -8,26 +8,26 @@ import { BrandMark } from "@/components/atoms/BrandMark";
export function LandingShell({ children }: { children: ReactNode }) { export function LandingShell({ children }: { children: ReactNode }) {
return ( return (
<div className="landing-page bg-white text-gray-900"> <div className="landing-page bg-app text-strong">
<section className="relative overflow-hidden pt-24 pb-16 px-4 sm:px-6 lg:px-8"> <section className="relative overflow-hidden px-4 pb-20 pt-16 sm:px-6 lg:px-8">
<div <div
className="absolute inset-0 bg-landing-grid opacity-[0.35] pointer-events-none" className="absolute inset-0 bg-landing-grid opacity-[0.18] pointer-events-none"
aria-hidden="true" aria-hidden="true"
/> />
<div <div
className="absolute -top-28 right-0 h-64 w-64 rounded-full bg-gray-100 blur-3xl pointer-events-none" className="absolute -top-40 right-0 h-72 w-72 rounded-full bg-[color:var(--accent-soft)] blur-3xl pointer-events-none"
aria-hidden="true" aria-hidden="true"
/> />
<div <div
className="absolute -bottom-32 left-0 h-72 w-72 rounded-full bg-gray-100 blur-3xl pointer-events-none" className="absolute -bottom-32 left-0 h-72 w-72 rounded-full bg-[color:var(--surface-strong)] blur-3xl pointer-events-none"
aria-hidden="true" aria-hidden="true"
/> />
<div className="relative w-full"> <div className="relative mx-auto flex w-full max-w-6xl flex-col gap-12">
<header className="flex items-center justify-between pb-12"> <header className="flex items-center justify-between gap-4">
<BrandMark /> <BrandMark />
<SignedIn> <SignedIn>
<div className="rounded-lg border-2 border-gray-200 bg-white px-2 py-1"> <div className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-2 py-1 shadow-sm">
<UserButton /> <UserButton />
</div> </div>
</SignedIn> </SignedIn>

View File

@@ -4,13 +4,20 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold", "inline-flex items-center rounded-full px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em]",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-gray-100 text-gray-800", default: "bg-[color:var(--surface-muted)] text-strong",
outline: "border border-gray-300 text-gray-800", outline:
ember: "bg-gray-900 text-white", "border border-[color:var(--border-strong)] text-[color:var(--text-muted)]",
accent: "bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]",
success:
"bg-[color:rgba(15,118,110,0.14)] text-[color:var(--success)]",
warning:
"bg-[color:rgba(180,83,9,0.15)] text-[color:var(--warning)]",
danger:
"bg-[color:rgba(180,35,24,0.15)] text-[color:var(--danger)]",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -6,15 +6,17 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-lg text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center gap-2 rounded-xl text-sm font-semibold transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{ {
variants: { variants: {
variant: { variant: {
primary: primary:
"border-2 border-gray-900 bg-gray-900 text-white hover:bg-gray-900/90", "bg-[color:var(--accent)] text-white shadow-sm hover:bg-[color:var(--accent-strong)]",
secondary: secondary:
"border-2 border-gray-200 bg-white text-gray-900 hover:border-gray-900 hover:bg-gray-900 hover:text-white", "border border-[color:var(--border)] bg-[color:var(--surface)] text-strong hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]",
ghost: "bg-transparent text-gray-900 hover:bg-black/5", outline:
"border border-[color:var(--border-strong)] bg-transparent text-strong hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]",
ghost: "bg-transparent text-strong hover:bg-[color:var(--surface-strong)]",
}, },
size: { size: {
sm: "h-9 px-4", sm: "h-9 px-4",

View File

@@ -6,10 +6,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn( className={cn("rounded-2xl surface-card", className)}
"rounded-xl border-2 border-gray-200 bg-white soft-shadow-sm",
className
)}
{...props} {...props}
/> />
) )

View File

@@ -17,7 +17,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/40 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-slate-950/40 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className
)} )}
{...props} {...props}
@@ -34,7 +34,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 w-[90vw] max-w-2xl translate-x-[-50%] translate-y-[-50%] rounded-2xl border-2 border-gray-200 bg-white p-6 shadow-lush focus:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "fixed left-[50%] top-[50%] z-50 w-[90vw] max-w-2xl translate-x-[-50%] translate-y-[-50%] rounded-3xl border border-[color:var(--border)] bg-[color:var(--surface)] p-6 shadow-lush focus:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
className className
)} )}
{...props} {...props}
@@ -70,7 +70,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn("text-lg font-semibold text-gray-900", className)} className={cn("text-lg font-semibold text-strong", className)}
{...props} {...props}
/> />
)); ));
@@ -82,7 +82,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref} ref={ref}
className={cn("text-sm text-gray-600", className)} className={cn("text-sm text-muted", className)}
{...props} {...props}
/> />
)); ));

View File

@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLI
ref={ref} ref={ref}
type={type} type={type}
className={cn( className={cn(
"flex h-11 w-full rounded-lg border-2 border-gray-200 bg-white px-4 text-sm text-gray-900 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black", "flex h-11 w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 text-sm text-strong shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]",
className className
)} )}
{...props} {...props}

View File

@@ -18,14 +18,14 @@ const SelectTrigger = React.forwardRef<
ref={ref} ref={ref}
type="button" type="button"
className={cn( className={cn(
"flex h-11 w-full cursor-pointer items-center justify-between rounded-lg border-2 border-gray-200 bg-white px-4 text-sm text-gray-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2", "flex h-11 w-full cursor-pointer items-center justify-between rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 text-sm text-strong shadow-sm focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)] focus:ring-offset-2",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 text-gray-500" /> <ChevronDown className="h-4 w-4 text-[color:var(--text-quiet)]" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)); ));
@@ -67,7 +67,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-xl border-2 border-gray-200 bg-white shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className, className,
@@ -97,7 +97,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold text-strong", className)}
{...props} {...props}
/> />
)); ));
@@ -110,7 +110,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm text-gray-800 outline-none focus:bg-black/5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-pointer select-none items-center rounded-lg py-2 pl-8 pr-2 text-sm text-strong outline-none focus:bg-[color:var(--surface-strong)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className, className,
)} )}
{...props} {...props}
@@ -131,7 +131,7 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-gray-200", className)} className={cn("-mx-1 my-1 h-px bg-[color:var(--border)]", className)}
{...props} {...props}
/> />
)); ));

View File

@@ -14,7 +14,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center gap-2 rounded-full border-2 border-gray-200 bg-white p-1", "inline-flex items-center gap-2 rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] p-1",
className className
)} )}
{...props} {...props}
@@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-full px-4 py-2 text-xs font-semibold text-gray-600 transition data-[state=active]:bg-gray-900 data-[state=active]:text-white", "rounded-full px-4 py-2 text-xs font-semibold text-muted transition data-[state=active]:bg-[color:var(--accent)] data-[state=active]:text-white",
className className
)} )}
{...props} {...props}

View File

@@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
<textarea <textarea
ref={ref} ref={ref}
className={cn( className={cn(
"min-h-[120px] w-full rounded-lg border-2 border-gray-200 bg-white px-4 py-3 text-sm text-gray-900 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black", "min-h-[120px] w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 py-3 text-sm text-strong shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]",
className className
)} )}
{...props} {...props}

View File

@@ -17,7 +17,7 @@ const TooltipContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"rounded-lg bg-gray-900 px-3 py-2 text-xs font-semibold text-white soft-shadow-sm", "rounded-lg bg-slate-900 px-3 py-2 text-xs font-semibold text-white shadow-lg",
className className
)} )}
{...props} {...props}

View File

@@ -1,6 +1,6 @@
# BOOT.md # BOOT.md
On startup: On startup:
1) Verify API reachability (GET {{BASE_URL}}/api/v1/gateway/status). 1) Verify API reachability (GET {{ base_url }}/api/v1/gateway/status).
2) If you send a boot message, end with NO_REPLY. 2) If you send a boot message, end with NO_REPLY.
3) If BOOTSTRAP.md exists in this workspace, the agent should run it once and delete it. 3) If BOOTSTRAP.md exists in this workspace, the agent should run it once and delete it.

View File

@@ -1,8 +1,8 @@
# IDENTITY.md # IDENTITY.md
Name: {{AGENT_NAME}} Name: {{ agent_name }}
Agent ID: {{AGENT_ID}} Agent ID: {{ agent_id }}
Creature: AI Creature: AI
Vibe: calm, precise, helpful Vibe: calm, precise, helpful
Emoji: :gear: Emoji: :gear:
Avatar: avatars/{{AGENT_ID}}.png Avatar: avatars/{{ agent_id }}.png

View File

@@ -1,9 +1,13 @@
# TOOLS.md # TOOLS.md
BASE_URL={{BASE_URL}} BASE_URL={{ base_url }}
AUTH_TOKEN={{AUTH_TOKEN}} AUTH_TOKEN={{ auth_token }}
MAIN_SESSION_KEY=agent:main:main MAIN_SESSION_KEY={{ main_session_key }}
WORKSPACE_ROOT=~/.openclaw/workspaces WORKSPACE_ROOT={{ workspace_root }}
AGENT_NAME={{ agent_name }}
AGENT_ID={{ agent_id }}
SESSION_KEY={{ session_key }}
WORKSPACE_PATH={{ workspace_path }}
Notes: Notes:
- Use curl for API calls. - Use curl for API calls.

View File

@@ -1,7 +1,7 @@
# USER.md # USER.md
Name: {{USER_NAME}} Name: {{ user_name }}
Preferred name: {{USER_PREFERRED_NAME}} Preferred name: {{ user_preferred_name }}
Timezone: {{USER_TIMEZONE}} Timezone: {{ user_timezone }}
Notes: Notes:
- {{USER_NOTES}} - {{ user_notes }}