new TurnState { Idle, Thinking, Compacting } on hive_ag3nt::events::Bus
with set_state + state_snapshot. the turn loops in hive-ag3nt and
hive-m1nd flip Thinking before drive_turn and Idle after; the
web_ui's /api/compact handler flips Compacting around compact_session.
per-agent /api/state grows turn_state + turn_state_since (unix
seconds). frontend prefers the server-reported state over the
client-derived one — setStateAbs takes the absolute since-time so
the 'last turn' chip reads the actual server-side duration instead
of the client's perceived gap between SSE events. SSE turn_start /
turn_end still drive state instantly between renders; /api/state
re-anchors on each turn_end refresh.
new compacting state gets its own purple badge with pulse
animation (mirrors thinking's amber). napping will slot in the
same way once the nap tool lands.
456 lines
13 KiB
CSS
456 lines
13 KiB
CSS
:root {
|
|
/* Catppuccin Mocha — mirrors the dashboard palette. */
|
|
--bg: #1e1e2e; /* base */
|
|
--bg-elev: #181825; /* mantle */
|
|
--fg: #cdd6f4; /* text */
|
|
--muted: #7f849c; /* overlay1 */
|
|
--purple: #cba6f7; /* mauve */
|
|
--purple-dim: #45475a; /* surface1 */
|
|
--cyan: #89dceb; /* sky */
|
|
--amber: #fab387; /* peach */
|
|
--green: #a6e3a1; /* green */
|
|
--red: #f38ba8; /* red */
|
|
}
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace;
|
|
max-width: 110em;
|
|
margin: 1.5em auto;
|
|
padding: 0 1.5em;
|
|
line-height: 1.6;
|
|
}
|
|
.banner {
|
|
text-align: center;
|
|
margin: 0 0 1em 0;
|
|
font-size: 0.95em;
|
|
overflow-x: auto;
|
|
background: linear-gradient(
|
|
90deg,
|
|
var(--purple-dim) 0%,
|
|
var(--purple) 50%,
|
|
var(--purple-dim) 100%
|
|
);
|
|
background-size: 200% 100%;
|
|
background-position: 50% 0;
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
filter: drop-shadow(0 0 6px rgba(203, 166, 247, 0.45));
|
|
}
|
|
.banner.active {
|
|
animation: banner-shimmer 1.8s linear infinite;
|
|
}
|
|
@keyframes banner-shimmer {
|
|
from { background-position: 200% 0; }
|
|
to { background-position: -100% 0; }
|
|
}
|
|
h2, h3 {
|
|
color: var(--purple);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.15em;
|
|
text-shadow: 0 0 8px rgba(203, 166, 247, 0.4);
|
|
}
|
|
.meta { color: var(--muted); font-size: 0.85em; }
|
|
.status-online { color: var(--green); text-shadow: 0 0 6px rgba(166, 227, 161, 0.55); }
|
|
.status-needs-login { color: var(--amber); text-shadow: 0 0 6px rgba(250, 179, 135, 0.55); }
|
|
code { background: rgba(203, 166, 247, 0.12); padding: 0.05em 0.3em; border-radius: 2px; }
|
|
a {
|
|
color: var(--cyan);
|
|
text-shadow: 0 0 4px rgba(137, 220, 235, 0.5);
|
|
}
|
|
a:hover { color: var(--fg); text-shadow: 0 0 12px rgba(137, 220, 235, 0.9); }
|
|
.btn {
|
|
font-family: inherit;
|
|
font-size: 1em;
|
|
background: var(--bg);
|
|
border: 1px solid var(--purple);
|
|
color: var(--purple);
|
|
padding: 0.25em 0.8em;
|
|
cursor: pointer;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
.btn {
|
|
text-shadow: 0 0 4px currentColor;
|
|
transition: box-shadow 0.15s ease, text-shadow 0.15s ease;
|
|
}
|
|
.btn:hover {
|
|
background: rgba(205, 214, 244, 0.06);
|
|
text-shadow: 0 0 10px currentColor;
|
|
box-shadow: 0 0 10px -2px currentColor;
|
|
}
|
|
.btn-login { color: var(--amber); border-color: var(--amber); }
|
|
.btn-cancel { color: var(--red); border-color: var(--red); font-size: 0.85em; padding: 0.15em 0.6em; }
|
|
.btn-rebuild {
|
|
color: var(--amber);
|
|
border: 1px solid var(--amber);
|
|
padding: 0.15em 0.6em;
|
|
font-size: 0.55em;
|
|
font-family: inherit;
|
|
text-decoration: none;
|
|
letter-spacing: 0.1em;
|
|
margin-left: 0.6em;
|
|
vertical-align: middle;
|
|
cursor: pointer;
|
|
}
|
|
.btn-rebuild:hover { background: rgba(250, 179, 135, 0.1); }
|
|
.btn-send { color: var(--green); border-color: var(--green); }
|
|
.sendform { display: flex; gap: 0.6em; margin-top: 0.5em; }
|
|
.sendform input {
|
|
font-family: inherit; font-size: 1em;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
color: var(--fg);
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.4em 0.6em;
|
|
flex: 1;
|
|
}
|
|
.sendform input:focus { outline: 1px solid var(--purple); }
|
|
.loginform { display: flex; gap: 0.6em; margin-top: 0.5em; }
|
|
.loginform input {
|
|
font-family: inherit; font-size: 1em;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
color: var(--fg);
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.4em 0.6em;
|
|
flex: 1;
|
|
}
|
|
.loginform input:focus { outline: 1px solid var(--purple); }
|
|
pre.diff {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.6em 0.8em;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
max-height: 30em;
|
|
}
|
|
#state-row {
|
|
margin: 0.4em 0 0.2em;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6em;
|
|
}
|
|
/* Per-agent inbox section — collapsible, dim, lives between the
|
|
state row and the terminal so the operator can peek at what
|
|
landed without scrolling through the live tail. */
|
|
.agent-inbox {
|
|
margin: 0.4em 0;
|
|
font-size: 0.85em;
|
|
color: var(--muted);
|
|
}
|
|
.agent-inbox > summary {
|
|
cursor: pointer;
|
|
letter-spacing: 0.05em;
|
|
list-style: none;
|
|
}
|
|
.agent-inbox > summary::marker { content: ''; }
|
|
.agent-inbox[open] > summary > span::before { content: ''; }
|
|
.agent-inbox ul {
|
|
list-style: none;
|
|
padding: 0.4em 0.8em;
|
|
margin: 0.3em 0 0;
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border-left: 2px solid var(--purple-dim);
|
|
max-height: 16em;
|
|
overflow-y: auto;
|
|
}
|
|
.agent-inbox li {
|
|
padding: 0.15em 0;
|
|
display: grid;
|
|
grid-template-columns: auto auto auto 1fr;
|
|
gap: 0.5em;
|
|
align-items: baseline;
|
|
}
|
|
.agent-inbox .inbox-ts { color: var(--muted); font-size: 0.9em; }
|
|
.agent-inbox .inbox-from { color: var(--amber); }
|
|
.agent-inbox .inbox-sep { color: var(--muted); }
|
|
.agent-inbox .inbox-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
|
|
|
|
.last-turn {
|
|
color: var(--muted);
|
|
font-size: 0.8em;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.btn-dashlink {
|
|
color: var(--cyan);
|
|
border: 1px solid var(--cyan);
|
|
padding: 0.15em 0.6em;
|
|
font-size: 0.55em;
|
|
font-family: inherit;
|
|
text-decoration: none;
|
|
letter-spacing: 0.1em;
|
|
margin-left: 0.6em;
|
|
vertical-align: middle;
|
|
}
|
|
.btn-dashlink:hover {
|
|
background: rgba(137, 220, 235, 0.1);
|
|
box-shadow: 0 0 10px -2px currentColor;
|
|
}
|
|
.btn-cancel-turn {
|
|
font-family: inherit;
|
|
font-size: 0.8em;
|
|
letter-spacing: 0.08em;
|
|
background: transparent;
|
|
color: var(--red);
|
|
border: 1px solid var(--red);
|
|
border-radius: 999px;
|
|
padding: 0.2em 0.8em;
|
|
cursor: pointer;
|
|
text-shadow: 0 0 4px currentColor;
|
|
transition: box-shadow 0.15s ease, background 0.15s ease;
|
|
}
|
|
.btn-cancel-turn:hover {
|
|
background: rgba(243, 139, 168, 0.1);
|
|
box-shadow: 0 0 10px -2px currentColor;
|
|
}
|
|
.state-badge {
|
|
display: inline-block;
|
|
padding: 0.25em 0.8em;
|
|
border: 1px solid;
|
|
border-radius: 999px;
|
|
font-size: 0.85em;
|
|
letter-spacing: 0.05em;
|
|
transition: color 280ms ease, border-color 280ms ease,
|
|
box-shadow 280ms ease, background 280ms ease;
|
|
}
|
|
.state-badge.state-loading {
|
|
color: var(--muted); border-color: var(--purple-dim);
|
|
}
|
|
.state-badge.state-offline {
|
|
color: var(--muted); border-color: var(--muted);
|
|
}
|
|
.state-badge.state-idle {
|
|
color: var(--cyan); border-color: var(--cyan);
|
|
text-shadow: 0 0 6px rgba(137, 220, 235, 0.55);
|
|
}
|
|
.state-badge.state-thinking {
|
|
color: var(--amber); border-color: var(--amber);
|
|
text-shadow: 0 0 6px rgba(250, 179, 135, 0.65);
|
|
animation: badge-pulse 1.8s ease-in-out infinite;
|
|
}
|
|
.state-badge.state-compacting {
|
|
color: var(--purple); border-color: var(--purple);
|
|
text-shadow: 0 0 6px rgba(203, 166, 247, 0.65);
|
|
animation: badge-pulse 1.8s ease-in-out infinite;
|
|
}
|
|
.state-badge.state-just-changed {
|
|
animation: state-flash 600ms ease-out;
|
|
}
|
|
@keyframes state-flash {
|
|
0% { box-shadow: 0 0 0 0 currentColor, 0 0 0 0 currentColor; }
|
|
60% { box-shadow: 0 0 18px -4px currentColor, 0 0 4px 0 currentColor; }
|
|
100% { box-shadow: 0 0 0 0 currentColor, 0 0 0 0 currentColor; }
|
|
}
|
|
/* Terminal-ish wrapper holding the live output + prompt input as one
|
|
unit. Crust as bg (almost-black), slightly inset, mauve phosphor glow.
|
|
Frosted-glass backdrop blur: the page bg behind the wrap gets softened,
|
|
so anything that bleeds through (page banner glow, scroll position)
|
|
reads as out-of-focus depth instead of sharp competing detail. */
|
|
.terminal-wrap {
|
|
position: relative;
|
|
background: rgba(17, 17, 27, 0.78);
|
|
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
|
backdrop-filter: blur(8px) saturate(120%);
|
|
border: 1px solid var(--purple-dim);
|
|
box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.7);
|
|
border-radius: 4px;
|
|
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace;
|
|
font-size: 0.92em;
|
|
color: #cdd6f4;
|
|
margin-top: 0.6em;
|
|
}
|
|
.live.terminal {
|
|
background: transparent;
|
|
border: 0;
|
|
box-shadow: none;
|
|
border-radius: 0;
|
|
padding: 0.8em 1em 0.4em;
|
|
overflow-y: auto;
|
|
/* Make the terminal the page's main visual element on tall screens
|
|
while staying inside the page chrome on short ones. */
|
|
height: min(72vh, 60em);
|
|
max-height: none;
|
|
font-family: inherit;
|
|
font-size: inherit;
|
|
color: inherit;
|
|
}
|
|
.term-input { padding: 0.4em 1em 0.8em; }
|
|
.term-input .sendform-term {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 0.5em;
|
|
border-top: 1px dashed var(--purple-dim);
|
|
padding-top: 0.5em;
|
|
}
|
|
.term-input .prompt, .term-input .submit-hint {
|
|
padding-top: 0.25em;
|
|
}
|
|
.term-input .prompt {
|
|
color: var(--green);
|
|
text-shadow: 0 0 6px rgba(166, 227, 161, 0.6);
|
|
user-select: none;
|
|
flex: 0 0 auto;
|
|
}
|
|
.term-input textarea {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: 0;
|
|
outline: 0;
|
|
color: var(--fg);
|
|
font-family: inherit;
|
|
font-size: 1em;
|
|
padding: 0.2em 0;
|
|
caret-color: var(--green);
|
|
resize: none;
|
|
overflow-y: auto;
|
|
line-height: 1.4;
|
|
min-height: 1.4em;
|
|
}
|
|
.term-input textarea::placeholder { color: var(--muted); }
|
|
.term-input .submit-hint { color: var(--muted); font-size: 0.8em; flex: 0 0 auto; }
|
|
.term-input.disabled .prompt { color: var(--muted); text-shadow: none; }
|
|
.term-input.disabled textarea { color: var(--muted); }
|
|
.live {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border: 1px solid var(--purple-dim);
|
|
padding: 0.4em 0.6em;
|
|
overflow-y: auto;
|
|
max-height: 32em;
|
|
font-family: inherit;
|
|
}
|
|
.live .unread-badge {
|
|
color: var(--amber);
|
|
font-weight: normal;
|
|
margin-left: 0.6em;
|
|
font-size: 0.85em;
|
|
text-shadow: 0 0 6px rgba(250, 179, 135, 0.55);
|
|
animation: badge-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
@keyframes badge-pulse {
|
|
0%, 100% { opacity: 1; text-shadow: 0 0 6px rgba(250, 179, 135, 0.55); }
|
|
50% { opacity: 0.7; text-shadow: 0 0 14px rgba(250, 179, 135, 0.95); }
|
|
}
|
|
/* Per-event fade-in slide-up. Applied to every row the live panel
|
|
appends; the `.no-anim` modifier lets history-backfill skip the
|
|
animation (we don't want 100 rows fading in at once on page load). */
|
|
.live .row,
|
|
.live details.row {
|
|
animation: row-fade-in 220ms ease-out both;
|
|
}
|
|
.live .row.no-anim,
|
|
.live details.row.no-anim {
|
|
animation: none;
|
|
}
|
|
@keyframes row-fade-in {
|
|
from { opacity: 0; transform: translateY(4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
/* "↓ N new" pill: shown when new rows arrive while the operator is
|
|
scrolled up; click to jump to bottom. */
|
|
.tail-pill {
|
|
position: absolute;
|
|
right: 1em;
|
|
bottom: 4.2em;
|
|
background: var(--amber);
|
|
color: #11111b;
|
|
font-family: inherit;
|
|
font-size: 0.8em;
|
|
font-weight: bold;
|
|
letter-spacing: 0.08em;
|
|
border: 0;
|
|
border-radius: 999px;
|
|
padding: 0.35em 0.9em;
|
|
cursor: pointer;
|
|
box-shadow: 0 0 14px -2px rgba(250, 179, 135, 0.85);
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
pointer-events: none;
|
|
transition: opacity 160ms ease, transform 160ms ease;
|
|
}
|
|
.tail-pill.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
.tail-pill:hover {
|
|
filter: brightness(1.1);
|
|
}
|
|
details.row {
|
|
white-space: normal;
|
|
padding-left: 0.5em;
|
|
}
|
|
details.row > summary {
|
|
cursor: pointer;
|
|
color: var(--muted);
|
|
list-style: none;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
details.row > summary::before {
|
|
content: '▸ ';
|
|
color: var(--muted);
|
|
display: inline-block;
|
|
width: 1em;
|
|
}
|
|
details.row[open] > summary::before { content: '▾ '; }
|
|
details.row.tool-result-block > summary { color: var(--muted); }
|
|
/* Inline diff body for Write / Edit tool_use rows: same shape as
|
|
tool-body but each line is wrapped in a span with diff-add /
|
|
diff-del / diff-ctx so + / - lines are colored. */
|
|
details.row > pre.diff-body {
|
|
margin: 0.3em 0 0.4em 1.2em;
|
|
padding: 0.4em 0.6em;
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border-left: 2px solid var(--purple-dim);
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
max-height: 22em;
|
|
overflow-y: auto;
|
|
}
|
|
details.row > pre.diff-body .diff-add { color: var(--green); }
|
|
details.row > pre.diff-body .diff-del { color: var(--red); }
|
|
details.row > pre.diff-body .diff-ctx { color: var(--fg); }
|
|
details.row > pre.tool-body {
|
|
margin: 0.3em 0 0.4em 1.2em;
|
|
padding: 0.4em 0.6em;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-left: 2px solid var(--purple-dim);
|
|
color: var(--fg);
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
max-height: 22em;
|
|
overflow-y: auto;
|
|
}
|
|
.live .row {
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
padding: 0.05em 0;
|
|
line-height: 1.45;
|
|
border-left: 2px solid transparent;
|
|
padding-left: 0.5em;
|
|
margin: 0.1em 0;
|
|
}
|
|
.live .row + .row { border-top: 0; }
|
|
.live .turn-start {
|
|
color: var(--amber);
|
|
font-weight: bold;
|
|
margin-top: 1em;
|
|
border-left-color: var(--amber);
|
|
padding-top: 0.3em;
|
|
}
|
|
.live .turn-start:first-child { margin-top: 0; }
|
|
.live .turn-body {
|
|
color: var(--fg);
|
|
font-weight: normal;
|
|
margin-top: 0.15em;
|
|
padding-left: 1.2em;
|
|
opacity: 0.85;
|
|
}
|
|
.live .turn-end-ok { color: var(--green); border-left-color: var(--green); margin-bottom: 0.4em; }
|
|
.live .turn-end-fail { color: var(--red); border-left-color: var(--red); margin-bottom: 0.4em; }
|
|
.live .text { color: var(--fg); padding-left: 1.2em; }
|
|
.live .thinking { color: var(--muted); font-style: italic; padding-left: 1.2em; }
|
|
.live .tool-use { color: var(--cyan); padding-left: 1.2em; }
|
|
.live .tool-result { color: var(--muted); padding-left: 1.2em; }
|
|
.live .result { color: var(--green); padding-left: 0.5em; }
|
|
.live .sys, .live .note { color: var(--muted); }
|