-
Active sessions
- {sorted.length === 0 && (
+
+
Active sessions
+
+ {active.length} active · {archived.length} archived
+
+
+ {active.length === 0 && (
No active sessions. Spawn one from the sidebar.
)}
- {sorted.map((s) => (
-
(
+ open(s.id)}
- onKeyDown={(e) => {
- if (e.key === "Enter" || e.key === " ") open(s.id);
- }}
- >
-
-
-
- {s.name ?? s.repo ?? s.cwd}
-
-
- {s.repo !== null && {s.repo}}
- {s.model}
- {relativeTime(s.lastEventAt)}
-
-
- {s.agent && agent}
- {s.agent && (
-
- )}
-
+ s={s}
+ onOpen={open}
+ onStop={(e, ss) => void stop(e, ss)}
+ />
))}
+ {archived.length > 0 && (
+ <>
+
+ Archive
+
+ {archived.map((s) => (
+
void stop(e, ss)}
+ />
+ ))}
+ >
+ )}
);
}
diff --git a/web/src/SpawnView.test.tsx b/web/src/SpawnView.test.tsx
index 3b8e6ac..e805c41 100644
--- a/web/src/SpawnView.test.tsx
+++ b/web/src/SpawnView.test.tsx
@@ -420,3 +420,29 @@ describe("SpawnView spawn+poll", () => {
expect(screen.getByText("g/p: cloning")).toBeInTheDocument();
});
});
+
+describe("SpawnSteps", () => {
+ it("renders progress steps matching job state", async () => {
+ mockFetchJson((url, init) => {
+ if (init?.method === "POST" && url.endsWith("/api/spawn"))
+ return { sessionId: "sp1", containerId: "" };
+ if (url.endsWith("/api/gitlab/status"))
+ return { connected: true, baseUrl: "https://gl", username: "a" };
+ if (url.endsWith("/api/gitlab/repos")) return [repo("g/p")];
+ return [];
+ });
+ const store = makeStore({
+ spawnJobs: [{ sessionId: "sp1", repo: "g/p", state: "building", containerId: "" }],
+ });
+ render(tree(store));
+ await flush();
+ fireEvent.click(screen.getByText("g/p"));
+ fireEvent.click(screen.getByLabelText("Spawn container"));
+ await flush();
+ const steps = document.querySelectorAll(".spawn-progress .step");
+ expect(steps).toHaveLength(4);
+ expect(steps[0]?.className).toContain("done");
+ expect(steps[1]?.className).toContain("current");
+ expect(steps[2]?.className).toBe("step");
+ });
+});
diff --git a/web/src/SpawnView.tsx b/web/src/SpawnView.tsx
index 2b953ca..600dec1 100644
--- a/web/src/SpawnView.tsx
+++ b/web/src/SpawnView.tsx
@@ -19,6 +19,27 @@ interface Props {
pushToast: (text: string) => void;
}
+
+const SPAWN_STEPS: string[] = ["cloning", "building", "creating", "running"];
+
+function SpawnSteps({ state }: { state: string }): React.ReactNode {
+ const idx = SPAWN_STEPS.indexOf(state);
+ if (state === "error") return null;
+ return (
+
Spawning…
+
{jobLine()}
container {spawning.containerId.slice(0, 12)}
diff --git a/web/src/derive.ts b/web/src/derive.ts
index 739a48c..a77dab6 100644
--- a/web/src/derive.ts
+++ b/web/src/derive.ts
@@ -46,6 +46,7 @@ export interface ChatDerivation {
messages: ChatMessage[];
tools: Map;
busy: boolean;
+ usage: { inputTokens: number; outputTokens: number; totalCost: number };
}
// The plugin mirrors pi extension events verbatim: tool args arrive as a raw
@@ -66,6 +67,7 @@ export function deriveChat(events: EventFrame[]): ChatDerivation {
const tools = new Map();
let stream: { id: string; text: string } | null = null;
let busy = false;
+ const usage = { inputTokens: 0, outputTokens: 0, totalCost: 0 };
for (const e of events) {
switch (e.type) {
@@ -95,6 +97,15 @@ export function deriveChat(events: EventFrame[]): ChatDerivation {
case "agent_start":
busy = true;
break;
+ case "agent_end": {
+ const u = e.usage;
+ if (u !== undefined) {
+ usage.inputTokens += u.inputTokens ?? 0;
+ usage.outputTokens += u.outputTokens ?? 0;
+ usage.totalCost += u.totalCost ?? 0;
+ }
+ break;
+ }
case "agent_settled":
busy = false;
break;
@@ -138,7 +149,7 @@ export function deriveChat(events: EventFrame[]): ChatDerivation {
});
}
- return { messages, tools, busy: busy || stream !== null };
+ return { messages, tools, busy: busy || stream !== null, usage };
}
// ---------- todo / subagent derivation (client-side, per PROTOCOL.md) ----------
diff --git a/web/src/index.css b/web/src/index.css
index 24c6169..b95f9c3 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -1,468 +1,1145 @@
:root {
- color-scheme: dark;
- --bg: #212121;
- --bg-raised: #2f2f2f;
- --bg-hover: #383838;
- --border: #3d3d3d;
- --text: #ececec;
- --text-dim: #a6a6a6;
- --text-faint: #7c7c7c;
- --accent: #10a37f;
- --accent-dim: #0d8a6c;
- --danger: #ef4444;
- --user-bubble: #2f2f2f;
- --assistant-bubble: #212121;
- --radius: 12px;
- font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+ color-scheme: dark;
+ /* ink / slate design system */
+ --bg: #0d1017;
+ --bg-veil: #11151d;
+ --bg-raised: #161b26;
+ --bg-hover: #1c2230;
+ --bg-active: #202739;
+ --border: #232a3a;
+ --border-strong: #2e3750;
+ --text: #e8ebf2;
+ --text-dim: #9aa3b8;
+ --text-faint: #6b7488;
+ --accent: #6c8cff;
+ --accent-strong: #8aa2ff;
+ --accent-soft: rgba(108, 140, 255, 0.14);
+ --accent-line: rgba(108, 140, 255, 0.35);
+ --ok: #34d399;
+ --warn: #fbbf24;
+ --danger: #f87171;
+ --danger-soft: rgba(248, 113, 113, 0.12);
+ --mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
+ --radius: 10px;
+ --radius-lg: 14px;
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35);
+ --shadow-2: 0 8px 28px rgba(0, 0, 0, 0.45);
+ font-family:
+ ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter,
+ sans-serif;
}
-* { box-sizing: border-box; }
+* {
+ box-sizing: border-box;
+}
-html, body, #root { height: 100%; margin: 0; }
+html,
+body,
+#root {
+ height: 100%;
+ margin: 0;
+}
body {
- background: var(--bg);
- color: var(--text);
- font-size: 15px;
- line-height: 1.5;
- -webkit-font-smoothing: antialiased;
+ background: var(--bg);
+ color: var(--text);
+ font-size: 14.5px;
+ line-height: 1.55;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
}
button {
- font: inherit;
- color: inherit;
- background: none;
- border: none;
- cursor: pointer;
+ font: inherit;
+ color: inherit;
+ background: none;
+ border: none;
+ cursor: pointer;
}
-button:disabled { cursor: not-allowed; opacity: 0.5; }
-
-input, textarea, select {
- font: inherit;
- color: var(--text);
- background: var(--bg-raised);
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 8px 10px;
+button:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
}
-input:focus, textarea:focus, select:focus { outline: 1px solid var(--accent); }
-a { color: var(--text-dim); text-decoration: none; }
-a:hover { color: var(--text); }
+input,
+textarea,
+select {
+ font: inherit;
+ color: var(--text);
+ background: var(--bg-veil);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 8px 12px;
+ transition:
+ border-color 0.15s ease,
+ box-shadow 0.15s ease;
+}
+input:focus,
+textarea:focus,
+select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-soft);
+}
+
+a {
+ color: var(--text-dim);
+ text-decoration: none;
+}
+a:hover {
+ color: var(--text);
+}
+
+::selection {
+ background: var(--accent-soft);
+}
+
+*::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+*::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 6px;
+ border: 2px solid var(--bg);
+}
+*::-webkit-scrollbar-track {
+ background: transparent;
+}
/* ---------- app shell ---------- */
-.app-shell { display: flex; height: 100%; overflow: hidden; }
+.app-shell {
+ display: flex;
+ height: 100%;
+ overflow: hidden;
+}
.sidebar {
- width: 264px;
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- background: var(--bg-raised);
- border-right: 1px solid var(--border);
+ width: 272px;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-veil);
+ border-right: 1px solid var(--border);
}
.sidebar-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 14px;
- font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 14px 16px;
+}
+.sidebar-header .brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 650;
+ letter-spacing: 0.01em;
}
-.sidebar-header .brand { display: flex; align-items: center; gap: 8px; }
.sidebar-header .brand .logo {
- width: 24px; height: 24px;
- border-radius: 6px;
- background: var(--accent);
- color: #fff;
- display: flex; align-items: center; justify-content: center;
- font-size: 13px; font-weight: 700;
+ width: 26px;
+ height: 26px;
+ border-radius: 8px;
+ background: linear-gradient(135deg, var(--accent) 0%, #9d6cff 100%);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 13px;
+ font-weight: 800;
+ box-shadow: var(--shadow-1);
}
-.conn-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
-.conn-dot.open { background: #22c55e; }
-.conn-dot.connecting { background: #eab308; }
-.conn-dot.closed { background: var(--danger); }
+.conn-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ color: var(--text-faint);
+ padding: 3px 8px;
+ border: 1px solid var(--border);
+ border-radius: 999px;
+}
+.conn-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.conn-dot.open {
+ background: var(--ok);
+ box-shadow: 0 0 6px rgba(52, 211, 153, 0.6);
+}
+.conn-dot.connecting {
+ background: var(--warn);
+}
+.conn-dot.closed {
+ background: var(--danger);
+}
+
.new-chat-btn {
- display: block;
- width: calc(100% - 20px);
- margin: 0 10px 8px;
- padding: 8px;
- border: 1px solid var(--border);
- border-radius: 10px;
- text-align: center;
- color: var(--text);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: calc(100% - 24px);
+ margin: 0 12px 10px;
+ padding: 9px;
+ border: 1px solid var(--accent-line);
+ border-radius: var(--radius);
+ text-align: center;
+ color: var(--accent-strong);
+ font-weight: 550;
+ background: var(--accent-soft);
+ transition: background 0.15s ease;
+}
+.new-chat-btn:hover {
+ background: rgba(108, 140, 255, 0.22);
+}
+.new-chat-btn.active {
+ background: rgba(108, 140, 255, 0.22);
}
-.new-chat-btn:hover { background: var(--bg-hover); }
-.sidebar-sessions { flex: 1; overflow-y: auto; padding: 4px 8px 12px; }
+.sidebar-sessions {
+ flex: 1;
+ overflow-y: auto;
+ padding: 2px 12px 12px;
+}
+.sidebar-section-label {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-faint);
+ padding: 12px 6px 6px;
+}
.session-link {
- display: block;
- padding: 8px 10px;
- border-radius: 8px;
- color: var(--text-dim);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 10px;
+ border-radius: 8px;
+ color: var(--text-dim);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ border: 1px solid transparent;
}
-.session-link:hover { background: var(--bg-hover); color: var(--text); }
-.session-link.active { background: var(--bg-hover); color: var(--text); }
-.sidebar-footer {
- padding: 10px;
- border-top: 1px solid var(--border);
- display: flex;
- gap: 8px;
- align-items: center;
+.session-link:hover {
+ background: var(--bg-hover);
+ color: var(--text);
}
-.sidebar-footer .spacer { flex: 1; }
-.icon-btn {
- padding: 6px;
- border-radius: 8px;
- color: var(--text-dim);
- font-size: 13px;
+.session-link.active {
+ background: var(--bg-active);
+ color: var(--text);
+ border-color: var(--border);
+}
+.session-link.archived {
+ opacity: 0.75;
+}
+.session-link .link-label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ flex: 1;
+}
+.sidebar-empty {
+ color: var(--text-faint);
+ font-size: 12.5px;
+ padding: 4px 8px;
}
-.icon-btn:hover { background: var(--bg-hover); color: var(--text); }
-.icon-btn.danger:hover { color: var(--danger); }
-.main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
+.sidebar-footer {
+ padding: 10px 12px;
+ border-top: 1px solid var(--border);
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+.sidebar-footer .spacer {
+ flex: 1;
+}
+.icon-btn {
+ padding: 6px 10px;
+ border-radius: 8px;
+ color: var(--text-dim);
+ font-size: 13px;
+ transition:
+ background 0.12s ease,
+ color 0.12s ease;
+}
+.icon-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text);
+}
+.icon-btn.danger:hover {
+ color: var(--danger);
+ background: var(--danger-soft);
+}
+
+.main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ overflow: hidden;
+ position: relative;
+}
+
+.conn-banner {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 6px 16px;
+ background: rgba(251, 191, 36, 0.09);
+ border-bottom: 1px solid rgba(251, 191, 36, 0.25);
+ color: var(--warn);
+ font-size: 12.5px;
+}
/* ---------- sessions view ---------- */
-.page { flex: 1; overflow-y: auto; padding: 24px; max-width: 860px; margin: 0 auto; width: 100%; }
-.page h1 { font-size: 20px; margin: 0 0 16px; }
-.page .empty { color: var(--text-faint); padding: 40px 0; text-align: center; }
+.page {
+ flex: 1;
+ overflow-y: auto;
+ padding: 28px 24px 48px;
+ max-width: 880px;
+ margin: 0 auto;
+ width: 100%;
+}
+.page-head {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 18px;
+}
+.page h1 {
+ font-size: 19px;
+ margin: 0;
+ font-weight: 650;
+ letter-spacing: -0.01em;
+}
+.page-head .count {
+ color: var(--text-faint);
+ font-size: 13px;
+}
+.page .empty {
+ color: var(--text-faint);
+ padding: 56px 0;
+ text-align: center;
+}
.session-card {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 14px;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- margin-bottom: 10px;
- cursor: pointer;
- background: var(--bg);
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 14px 16px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ margin-bottom: 10px;
+ cursor: pointer;
+ background: var(--bg-raised);
+ box-shadow: var(--shadow-1);
+ transition:
+ border-color 0.15s ease,
+ transform 0.1s ease;
+}
+.session-card:hover {
+ border-color: var(--border-strong);
+ transform: translateY(-1px);
}
-.session-card:hover { background: var(--bg-raised); }
.session-card .online-dot {
- width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
- background: #525252;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ background: var(--text-faint);
+}
+.session-card .online-dot.on {
+ background: var(--ok);
+ box-shadow: 0 0 8px rgba(52, 211, 153, 0.55);
+}
+.session-card .card-body {
+ flex: 1;
+ min-width: 0;
}
-.session-card .online-dot.on { background: #22c55e; }
-.session-card .card-body { flex: 1; min-width: 0; }
.session-card .card-title {
- font-weight: 600;
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 14.5px;
}
.session-card .card-meta {
- font-size: 12px; color: var(--text-faint);
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
- display: flex; gap: 8px; flex-wrap: wrap;
+ font-size: 12px;
+ color: var(--text-faint);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-top: 2px;
}
.badge {
- font-size: 11px;
- padding: 2px 8px;
- border-radius: 999px;
- border: 1px solid var(--border);
- color: var(--text-dim);
- flex-shrink: 0;
+ font-size: 10.5px;
+ font-weight: 600;
+ padding: 2.5px 8px;
+ border-radius: 999px;
+ border: 1px solid var(--accent-line);
+ color: var(--accent-strong);
+ background: var(--accent-soft);
+ flex-shrink: 0;
+ letter-spacing: 0.02em;
+}
+.badge.neutral {
+ border-color: var(--border);
+ color: var(--text-dim);
+ background: transparent;
+}
+
+.skeleton {
+ border-radius: var(--radius-lg);
+ background: linear-gradient(
+ 100deg,
+ var(--bg-raised) 40%,
+ var(--bg-hover) 50%,
+ var(--bg-raised) 60%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.4s infinite linear;
+ height: 74px;
+ margin-bottom: 10px;
+ border: 1px solid var(--border);
+}
+@keyframes shimmer {
+ to {
+ background-position: -200% 0;
+ }
}
/* ---------- chat view ---------- */
-.chat-layout { flex: 1; display: flex; min-height: 0; }
-.chat-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
+.chat-layout {
+ flex: 1;
+ display: flex;
+ min-height: 0;
+}
+.chat-col {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+}
.chat-header {
- display: flex; align-items: center; gap: 10px;
- padding: 10px 16px;
- border-bottom: 1px solid var(--border);
- background: var(--bg);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 18px;
+ border-bottom: 1px solid var(--border);
+ background: var(--bg-veil);
+ min-height: 54px;
+}
+.chat-header .title {
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 14.5px;
+}
+.chat-header .sub {
+ font-size: 12px;
+ color: var(--text-faint);
+ white-space: nowrap;
+}
+.chat-header .spacer,
+.task-col .spacer {
+ flex: 1;
+}
+.usage-chip {
+ font-size: 11.5px;
+ color: var(--text-faint);
+ font-family: var(--mono);
+ border: 1px solid var(--border);
+ padding: 2.5px 8px;
+ border-radius: 999px;
+ white-space: nowrap;
}
-.chat-header .title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.chat-header .sub { font-size: 12px; color: var(--text-faint); }
-.chat-header .spacer, .task-col .spacer { flex: 1; }
-.chat-scroll { flex: 1; overflow-y: auto; padding: 20px 16px; scroll-behavior: smooth; }
-.chat-inner { max-width: 760px; margin: 0 auto; }
+.chat-scroll {
+ flex: 1;
+ overflow-y: auto;
+ padding: 22px 18px 30px;
+ scroll-behavior: smooth;
+}
+.chat-inner {
+ max-width: 780px;
+ margin: 0 auto;
+}
-.bubble-row { display: flex; margin-bottom: 14px; }
-.bubble-row.user { justify-content: flex-end; }
+.bubble-row {
+ display: flex;
+ margin-bottom: 16px;
+ position: relative;
+}
+.bubble-row.user {
+ justify-content: flex-end;
+}
.bubble {
- max-width: 86%;
- padding: 10px 14px;
- border-radius: var(--radius);
- white-space: pre-wrap;
- word-break: break-word;
+ max-width: 86%;
+ padding: 10px 14px;
+ border-radius: var(--radius-lg);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+.bubble-row.user .bubble {
+ background: var(--bg-active);
+ border: 1px solid var(--border-strong);
+ border-bottom-right-radius: 4px;
+}
+.bubble-row.assistant .bubble {
+ background: transparent;
+ padding: 0;
+ max-width: 100%;
+}
+.bubble-row.system .bubble {
+ color: var(--text-faint);
+ font-size: 13px;
+ font-style: italic;
+}
+
+.msg-copy {
+ position: absolute;
+ top: -6px;
+ right: -4px;
+ opacity: 0;
+ font-size: 11px;
+ color: var(--text-faint);
+ border: 1px solid var(--border);
+ background: var(--bg-raised);
+ border-radius: 6px;
+ padding: 2px 7px;
+ transition: opacity 0.12s ease;
+}
+.bubble-row:hover .msg-copy {
+ opacity: 1;
+}
+.bubble-row.user .msg-copy {
+ right: auto;
+ left: -4px;
+}
+.msg-copy:hover {
+ color: var(--text);
+ border-color: var(--border-strong);
}
-.bubble-row.user .bubble { background: var(--user-bubble); }
-.bubble-row.assistant .bubble { background: transparent; padding: 0; max-width: 100%; }
-.bubble-row.system .bubble { color: var(--text-faint); font-size: 13px; font-style: italic; }
.bubble .tool-card {
- border: 1px solid var(--border);
- border-radius: 10px;
- background: var(--bg-raised);
- margin: 8px 0;
- overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg-raised);
+ margin: 10px 0;
+ overflow: hidden;
}
.bubble .tool-card summary {
- cursor: pointer;
- padding: 8px 12px;
- font-size: 13px;
- color: var(--text-dim);
- display: flex; align-items: center; gap: 8px;
- list-style: none;
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ cursor: pointer;
+ padding: 9px 13px;
+ font-size: 12.5px;
+ color: var(--text-dim);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ list-style: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.bubble .tool-card summary::-webkit-details-marker {
+ display: none;
+}
+.bubble .tool-card summary .tool-name {
+ font-family: var(--mono);
+ font-size: 12px;
+ color: var(--text);
}
-.bubble .tool-card summary::-webkit-details-marker { display: none; }
.bubble .tool-card .tool-body {
- border-top: 1px solid var(--border);
- padding: 10px 12px;
- font-size: 13px;
- color: var(--text-dim);
- white-space: pre-wrap;
- word-break: break-word;
- max-height: 300px;
- overflow-y: auto;
+ border-top: 1px solid var(--border);
+ padding: 11px 13px;
+ font-size: 12.5px;
+ color: var(--text-dim);
+ white-space: pre-wrap;
+ word-break: break-word;
+ max-height: 320px;
+ overflow-y: auto;
+ font-family: var(--mono);
+ background: var(--bg-veil);
+}
+.tool-status-ok {
+ color: var(--ok);
+ font-weight: 600;
+}
+.tool-status-err {
+ color: var(--danger);
+ font-weight: 600;
}
-.tool-status-ok { color: var(--accent); }
-.tool-status-err { color: var(--danger); }
-details.thinking { margin-bottom: 8px; }
+details.thinking {
+ margin-bottom: 10px;
+}
details.thinking summary {
- cursor: pointer;
- font-size: 12px;
- color: var(--text-faint);
- list-style: none;
+ cursor: pointer;
+ font-size: 12px;
+ color: var(--text-faint);
+ list-style: none;
+ user-select: none;
+}
+details.thinking summary::-webkit-details-marker {
+ display: none;
+}
+details.thinking summary::before {
+ content: "◆ ";
+ font-size: 10px;
+}
+details.thinking summary:hover {
+ color: var(--text-dim);
}
-details.thinking summary::-webkit-details-marker { display: none; }
-details.thinking summary::before { content: "◆ "; }
details.thinking .thinking-body {
- font-size: 13px;
- color: var(--text-faint);
- border-left: 2px solid var(--border);
- padding-left: 10px;
- margin-top: 6px;
- white-space: pre-wrap;
- max-height: 260px;
- overflow-y: auto;
+ font-size: 12.5px;
+ color: var(--text-faint);
+ border-left: 2px solid var(--border-strong);
+ padding-left: 12px;
+ margin-top: 8px;
+ white-space: pre-wrap;
+ max-height: 280px;
+ overflow-y: auto;
}
.typing {
- display: inline-flex; gap: 4px; align-items: center;
- padding: 8px 0;
+ display: inline-flex;
+ gap: 4px;
+ align-items: center;
+ padding: 10px 0 4px;
}
.typing .dot {
- width: 6px; height: 6px; border-radius: 50%;
- background: var(--text-faint);
- animation: typing-bounce 1.2s infinite;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--text-faint);
+ animation: typing-bounce 1.2s infinite;
+}
+.typing .dot:nth-child(2) {
+ animation-delay: 0.15s;
+}
+.typing .dot:nth-child(3) {
+ animation-delay: 0.3s;
}
-.typing .dot:nth-child(2) { animation-delay: 0.15s; }
-.typing .dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes typing-bounce {
- 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
- 30% { transform: translateY(-4px); opacity: 1; }
+ 0%,
+ 60%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.45;
+ }
+ 30% {
+ transform: translateY(-4px);
+ opacity: 1;
+ }
}
.composer {
- border-top: 1px solid var(--border);
- padding: 12px 16px;
- background: var(--bg);
+ border-top: 1px solid var(--border);
+ padding: 14px 18px 16px;
+ background: var(--bg-veil);
+}
+.composer-inner {
+ max-width: 780px;
+ margin: 0 auto;
+ display: flex;
+ gap: 10px;
+ align-items: flex-end;
}
-.composer-inner { max-width: 760px; margin: 0 auto; display: flex; gap: 8px; align-items: flex-end; }
.composer textarea {
- flex: 1;
- resize: none;
- max-height: 200px;
- min-height: 42px;
- border-radius: var(--radius);
+ flex: 1;
+ resize: none;
+ max-height: 200px;
+ min-height: 44px;
+ border-radius: var(--radius-lg);
+ background: var(--bg-raised);
}
.composer .send-btn {
- background: var(--accent);
- color: #fff;
- border-radius: 10px;
- padding: 10px 14px;
+ background: var(--accent);
+ color: #fff;
+ border-radius: var(--radius-lg);
+ padding: 11px 16px;
+ font-weight: 600;
+ transition: background 0.15s ease;
+}
+.composer .send-btn:hover {
+ background: var(--accent-strong);
+}
+.composer .send-btn:disabled {
+ background: var(--bg-hover);
+ color: var(--text-faint);
}
-.composer .send-btn:hover { background: var(--accent-dim); }
-.composer .send-btn:disabled { background: var(--bg-hover); color: var(--text-faint); }
.composer .abort-btn {
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 10px 14px;
- color: var(--danger);
+ border: 1px solid rgba(248, 113, 113, 0.35);
+ border-radius: var(--radius-lg);
+ padding: 11px 16px;
+ color: var(--danger);
+}
+.composer .abort-btn:hover {
+ background: var(--danger-soft);
}
/* ---------- task panel ---------- */
.task-col {
- width: 300px;
- flex-shrink: 0;
- border-left: 1px solid var(--border);
- overflow-y: auto;
- padding: 14px;
- background: var(--bg);
+ width: 312px;
+ flex-shrink: 0;
+ border-left: 1px solid var(--border);
+ overflow-y: auto;
+ padding: 16px;
+ background: var(--bg-veil);
+}
+.task-col h2 {
+ font-size: 11px;
+ margin: 0 0 10px;
+ color: var(--text-faint);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-weight: 600;
+}
+.task-col .empty {
+ color: var(--text-faint);
+ font-size: 12.5px;
+ padding: 8px 0;
+}
+.task-section {
+ margin-bottom: 20px;
+}
+.task-card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg-raised);
+ padding: 10px 12px;
+}
+.task-card + .task-card {
+ margin-top: 8px;
}
-.task-col h2 { font-size: 13px; margin: 0 0 8px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
-.task-col .empty { color: var(--text-faint); font-size: 13px; }
-.task-section { margin-bottom: 18px; }
-.todo-item { display: flex; gap: 8px; align-items: baseline; font-size: 13px; padding: 3px 0; }
-.todo-item .todo-icon { flex-shrink: 0; width: 14px; text-align: center; }
-.todo-item .todo-icon.pending { color: var(--text-faint); }
-.todo-item .todo-icon.in-progress { color: #eab308; }
-.todo-item .todo-icon.completed { color: var(--accent); }
-.todo-item.done .todo-text { text-decoration: line-through; color: var(--text-faint); }
-.todo-item .todo-text { color: var(--text); }
+.todo-item {
+ display: flex;
+ gap: 8px;
+ align-items: baseline;
+ font-size: 13px;
+ padding: 3.5px 0;
+}
+.todo-item .todo-icon {
+ flex-shrink: 0;
+ width: 14px;
+ text-align: center;
+}
+.todo-item .todo-icon.pending {
+ color: var(--text-faint);
+}
+.todo-item .todo-icon.in-progress {
+ color: var(--warn);
+}
+.todo-item .todo-icon.completed {
+ color: var(--ok);
+}
+.todo-item.done .todo-text {
+ text-decoration: line-through;
+ color: var(--text-faint);
+}
+.todo-item .todo-text {
+ color: var(--text);
+}
-.subagent-item { font-size: 13px; padding: 4px 0; display: flex; gap: 8px; align-items: center; }
+.subagent-item {
+ font-size: 13px;
+ padding: 6px 10px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--bg-raised);
+ margin-bottom: 6px;
+}
.subagent-item .spinner {
- width: 12px; height: 12px;
- border: 2px solid var(--border);
- border-top-color: var(--accent);
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
- flex-shrink: 0;
+ width: 12px;
+ height: 12px;
+ border: 2px solid var(--border);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ flex-shrink: 0;
+}
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+.subagent-item .done-icon {
+ color: var(--ok);
+ flex-shrink: 0;
+}
+.subagent-item .err-icon {
+ color: var(--danger);
+ flex-shrink: 0;
+}
+.working-line {
+ font-size: 12px;
+ color: var(--text-faint);
+ padding: 3px 0;
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ font-family: var(--mono);
}
-@keyframes spin { to { transform: rotate(360deg); } }
-.subagent-item .done-icon { color: var(--accent); flex-shrink: 0; }
-.working-line { font-size: 12px; color: var(--text-faint); padding: 3px 0; display: flex; gap: 6px; align-items: center; }
-.task-toggle { display: none; }
+.task-toggle {
+ display: none;
+}
/* ---------- settings gate ---------- */
.gate {
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 20px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ background:
+ radial-gradient(
+ 60% 50% at 50% 0%,
+ rgba(108, 140, 255, 0.07) 0%,
+ transparent 70%
+ ),
+ var(--bg);
}
.gate-card {
- width: 100%;
- max-width: 380px;
- border: 1px solid var(--border);
- border-radius: 16px;
- background: var(--bg-raised);
- padding: 28px;
+ width: 100%;
+ max-width: 400px;
+ border: 1px solid var(--border-strong);
+ border-radius: 18px;
+ background: var(--bg-raised);
+ padding: 30px;
+ box-shadow: var(--shadow-2);
+}
+.gate-card h1 {
+ font-size: 19px;
+ margin: 0 0 4px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 650;
+}
+.gate-card h1 .logo {
+ width: 30px;
+ height: 30px;
+ border-radius: 9px;
+ background: linear-gradient(135deg, var(--accent) 0%, #9d6cff 100%);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: 800;
+}
+.gate-card .sub {
+ color: var(--text-faint);
+ font-size: 13px;
+ margin-bottom: 22px;
+}
+.gate-card label {
+ display: block;
+ font-size: 12.5px;
+ font-weight: 600;
+ color: var(--text-dim);
+ margin: 14px 0 5px;
+}
+.gate-card input {
+ width: 100%;
+}
+.gate-card .gate-error {
+ color: var(--danger);
+ font-size: 13px;
+ margin-top: 12px;
+ min-height: 18px;
}
-.gate-card h1 { font-size: 18px; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; }
-.gate-card .sub { color: var(--text-faint); font-size: 13px; margin-bottom: 20px; }
-.gate-card label { display: block; font-size: 13px; color: var(--text-dim); margin: 12px 0 4px; }
-.gate-card input { width: 100%; }
-.gate-card .gate-error { color: var(--danger); font-size: 13px; margin-top: 12px; min-height: 18px; }
.gate-card .gate-submit {
- width: 100%;
- margin-top: 20px;
- padding: 10px;
- background: var(--accent);
- color: #fff;
- border-radius: 10px;
+ width: 100%;
+ margin-top: 22px;
+ padding: 11px;
+ background: var(--accent);
+ color: #fff;
+ border-radius: var(--radius);
+ font-weight: 600;
+}
+.gate-card .gate-submit:hover {
+ background: var(--accent-strong);
}
/* ---------- spawn view ---------- */
.spawn-card {
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 18px;
- margin-bottom: 16px;
- background: var(--bg-raised);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ padding: 20px;
+ margin-bottom: 14px;
+ background: var(--bg-raised);
+ box-shadow: var(--shadow-1);
+}
+.spawn-card h2 {
+ font-size: 14px;
+ margin: 0 0 12px;
+ font-weight: 650;
+}
+.spawn-card input[type="text"],
+.spawn-card input[type="password"],
+.spawn-card select {
+ width: 100%;
+}
+.spawn-card .row {
+ display: flex;
+ gap: 10px;
+}
+.spawn-card .row > * {
+ flex: 1;
+}
+.spawn-card .actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 14px;
}
-.spawn-card h2 { font-size: 15px; margin: 0 0 10px; }
-.spawn-card input[type="text"], .spawn-card input[type="password"], .spawn-card select { width: 100%; }
-.spawn-card .row { display: flex; gap: 10px; }
-.spawn-card .row > * { flex: 1; }
-.spawn-card .actions { display: flex; gap: 8px; margin-top: 14px; }
.btn-primary {
- background: var(--accent);
- color: #fff;
- border-radius: 10px;
- padding: 8px 16px;
+ background: var(--accent);
+ color: #fff;
+ border-radius: var(--radius);
+ padding: 9px 18px;
+ font-weight: 600;
+}
+.btn-primary:hover {
+ background: var(--accent-strong);
+}
+.btn-primary:disabled {
+ background: var(--bg-hover);
+ color: var(--text-faint);
+}
+.btn-secondary {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 9px 18px;
+}
+.btn-secondary:hover {
+ background: var(--bg-hover);
+}
+.error-text {
+ color: var(--danger);
+ font-size: 13px;
+ margin-top: 10px;
}
-.btn-primary:hover { background: var(--accent-dim); }
-.btn-primary:disabled { background: var(--bg-hover); color: var(--text-faint); }
-.btn-secondary { border: 1px solid var(--border); border-radius: 10px; padding: 8px 16px; }
-.btn-secondary:hover { background: var(--bg-hover); }
-.error-text { color: var(--danger); font-size: 13px; margin-top: 10px; }
-.repo-list { max-height: 420px; overflow-y: auto; }
+.repo-list {
+ max-height: 440px;
+ overflow-y: auto;
+ margin: 0 -6px;
+ padding: 0 6px;
+}
.repo-item {
- display: flex; align-items: center; gap: 10px;
- padding: 10px;
- border-radius: 8px;
- cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 11px 12px;
+ border-radius: var(--radius);
+ cursor: pointer;
+ border: 1px solid transparent;
+}
+.repo-item:hover {
+ background: var(--bg-hover);
+}
+.repo-item.selected {
+ background: var(--accent-soft);
+ border-color: var(--accent-line);
+}
+.repo-item .repo-path {
+ font-weight: 600;
+ font-size: 13.5px;
+}
+.repo-item .repo-meta {
+ font-size: 12px;
+ color: var(--text-faint);
+ margin-top: 1px;
}
-.repo-item:hover { background: var(--bg-hover); }
-.repo-item.selected { background: var(--bg-hover); outline: 1px solid var(--accent); }
-.repo-item .repo-path { font-weight: 600; }
-.repo-item .repo-meta { font-size: 12px; color: var(--text-faint); }
-.spawn-job-line { font-size: 13px; color: var(--text-dim); padding: 4px 0; }
+.spawn-job-line {
+ font-size: 13px;
+ color: var(--text-dim);
+ padding: 4px 0;
+ font-family: var(--mono);
+}
+.spawn-progress {
+ display: flex;
+ gap: 6px;
+ margin: 10px 0 4px;
+}
+.spawn-progress .step {
+ height: 4px;
+ flex: 1;
+ border-radius: 2px;
+ background: var(--border);
+ transition: background 0.3s ease;
+}
+.spawn-progress .step.done {
+ background: var(--accent);
+}
+.spawn-progress .step.current {
+ background: var(--accent);
+ opacity: 0.45;
+ animation: pulse-step 1.2s infinite;
+}
+@keyframes pulse-step {
+ 50% {
+ opacity: 0.2;
+ }
+}
+
+/* ---------- error boundary ---------- */
+
+.err-boundary {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 10px;
+ color: var(--text-dim);
+ padding: 40px;
+ text-align: center;
+}
+.err-boundary h2 {
+ color: var(--text);
+ margin: 0;
+}
+.err-boundary pre {
+ font-size: 12px;
+ color: var(--text-faint);
+ background: var(--bg-raised);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 12px;
+ max-width: 560px;
+ max-height: 200px;
+ overflow: auto;
+ text-align: left;
+}
/* ---------- toasts ---------- */
.toasts {
- position: fixed;
- bottom: 16px;
- left: 50%;
- transform: translateX(-50%);
- display: flex;
- flex-direction: column;
- gap: 8px;
- z-index: 100;
+ position: fixed;
+ top: 14px;
+ right: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ z-index: 100;
+ max-width: min(360px, calc(100vw - 28px));
}
.toast {
- background: var(--bg-raised);
- border: 1px solid var(--border);
- color: var(--text);
- padding: 10px 16px;
- border-radius: 10px;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
- font-size: 14px;
+ background: var(--bg-raised);
+ border: 1px solid var(--border-strong);
+ color: var(--text);
+ padding: 10px 14px;
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-2);
+ font-size: 13.5px;
+ animation: toast-in 0.18s ease;
+}
+@keyframes toast-in {
+ from {
+ opacity: 0;
+ transform: translateY(-6px);
+ }
}
-/* ---------- mobile ≤400px ---------- */
+/* ---------- mobile ---------- */
@media (max-width: 860px) {
- .task-col { display: none; }
- .task-toggle {
- display: inline-flex;
- align-items: center;
- }
- .mobile-tasks {
- border-bottom: 1px solid var(--border);
- padding: 12px 16px;
- background: var(--bg-raised);
- }
+ .task-col {
+ display: none;
+ }
+ .task-toggle {
+ display: inline-flex;
+ align-items: center;
+ }
+ .mobile-tasks {
+ border-bottom: 1px solid var(--border);
+ padding: 12px 16px;
+ background: var(--bg-raised);
+ }
}
@media (max-width: 700px) {
- .sidebar {
- position: fixed;
- inset: 0 auto 0 0;
- z-index: 50;
- transform: translateX(-100%);
- transition: transform 0.2s ease;
- width: 280px;
- }
- .sidebar.open { transform: translateX(0); }
- .sidebar-backdrop {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 40;
- }
- .menu-btn { display: inline-flex !important; }
- .page { padding: 14px; }
- .chat-scroll { padding: 14px 10px; }
- .bubble { max-width: 94%; }
+ .sidebar {
+ position: fixed;
+ inset: 0 auto 0 0;
+ z-index: 50;
+ transform: translateX(-100%);
+ transition: transform 0.22s ease;
+ width: 288px;
+ box-shadow: var(--shadow-2);
+ }
+ .sidebar.open {
+ transform: translateX(0);
+ }
+ .sidebar-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.55);
+ z-index: 40;
+ backdrop-filter: blur(2px);
+ }
+ .menu-btn {
+ display: inline-flex !important;
+ }
+ .page {
+ padding: 16px 14px 40px;
+ }
+ .chat-scroll {
+ padding: 16px 12px 24px;
+ }
+ .bubble {
+ max-width: 94%;
+ }
+ .usage-chip {
+ display: none;
+ }
+ .toasts {
+ top: auto;
+ bottom: 14px;
+ right: 14px;
+ }
}
@media (max-width: 400px) {
- body { font-size: 14px; }
- .sidebar { width: 100%; }
- .gate-card { padding: 20px; }
- .session-card { padding: 10px; gap: 8px; }
- .composer textarea { font-size: 16px; } /* prevent iOS zoom */
- .composer .send-btn { padding: 10px 12px; }
+ body {
+ font-size: 14px;
+ }
+ .sidebar {
+ width: 100%;
+ }
+ .gate-card {
+ padding: 22px;
+ }
+ .session-card {
+ padding: 12px;
+ gap: 10px;
+ }
+ .composer textarea {
+ font-size: 16px;
+ } /* prevent iOS zoom */
+ .composer .send-btn {
+ padding: 11px 13px;
+ }
}