feat: stuffy feature bundle (#159)
CI / Lint & Test (push) Has started running
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled

## Summary

This PR bundles a collection of new features and quality-of-life improvements identified during a Claude CLI 2.1.50 audit.

- **Tab status indicator** — Tab stays yellow until the greeting is responded to, then turns green. Fixed disconnect not resetting to grey. Closes #157
- **Auth status display** — New "Account" section in settings sidebar showing login status, email, org, API key source, and Hikari override indicator. Includes login/logout buttons. Closes #153
- **CLI version badge** — New "Supported" badge showing the highest audited CLI version, colour-coded green/amber/red based on installed vs supported version. Closes #154 (bump to 2.1.50)
- **Rate limit events** — `rate_limit_event` messages from the stream are now parsed and shown as amber `[rate-limit]` lines in the terminal instead of being silently dropped. Closes #155
- **"Prompt is too long" handling** — Detects this error in assistant messages and shows a  Compact Conversation button to send `/compact` directly. Closes #158
- **`last_assistant_message` in Agent Monitor** — Extracts the agent's final output from the `ToolResult` content block in the JSON stream and displays it as a snippet on completed agent cards. Closes #156
- **`--worktree` flag** — New "Worktree isolation" toggle in session settings passes `--worktree` to Claude Code. Hook events (`WorktreeCreate`/`WorktreeRemove`) are displayed as green `[worktree]` lines. Closes #152, Closes #150
- **ConfigChange hook events** — `[ConfigChange Hook]` stderr events are now displayed as cyan `[config]` lines instead of errors. Closes #151
- **`CLAUDE_CODE_DISABLE_1M_CONTEXT` toggle** — New "Disable 1M context" setting in session configuration injects this env var into the Claude process. Closes #154

## Test plan

- [ ] Tab status indicator: start a new session and verify the tab stays yellow until Claude responds to the greeting, then turns green
- [ ] Auth status: open settings and verify the Account section shows correct login info
- [ ] CLI version badge: verify the "Supported 2.1.50" badge shows green when CLI matches
- [ ] Rate limit events: unit tests cover parsing; amber `[rate-limit]` lines display correctly
- [ ] Compact button: unit tests cover detection; button renders correctly in terminal
- [ ] Agent Monitor: use the Task tool and verify completed agent cards show a message snippet
- [ ] Worktree: enable toggle, start session, verify `--worktree` flag appears in process args
- [ ] ConfigChange: hook events display as `[config]` lines rather than errors
- [ ] Disable 1M context: enable toggle, start session, verify `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` in `/proc/<pid>/environ`

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #159
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #159.
This commit is contained in:
2026-02-24 20:48:49 -08:00
committed by Naomi Carrigan
parent d2e0915a75
commit a4e6788573
22 changed files with 1396 additions and 56 deletions
+118 -1
View File
@@ -1,6 +1,8 @@
<script lang="ts">
import { claudeStore, type TerminalLine } from "$lib/stores/claude";
import { afterUpdate, tick, onMount, onDestroy } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import ConversationTabs from "./ConversationTabs.svelte";
import Markdown from "./Markdown.svelte";
import HighlightedText from "./HighlightedText.svelte";
@@ -92,6 +94,14 @@
return "terminal-error";
case "thinking":
return "terminal-thinking";
case "rate-limit":
return "terminal-rate-limit";
case "compact-prompt":
return "terminal-compact-prompt";
case "worktree":
return "terminal-worktree";
case "config-change":
return "terminal-config-change";
default:
return "terminal-default";
}
@@ -109,6 +119,12 @@
return "[tool]";
case "error":
return "[error]";
case "rate-limit":
return "[rate-limit]";
case "worktree":
return "[worktree]";
case "config-change":
return "[config]";
default:
return "";
}
@@ -187,6 +203,27 @@
copiedMessageId = null;
}, 2000);
}
async function handleCompact() {
if (!currentConversationId) return;
await invoke("send_prompt", { conversationId: currentConversationId, message: "/compact" });
}
// Collapsible tool lines
const TOOL_COLLAPSE_THRESHOLD = 60;
let expandedToolLines: Record<string, boolean> = {};
function isToolContentLong(content: string): boolean {
return content.length > TOOL_COLLAPSE_THRESHOLD;
}
function truncateToolContent(content: string): string {
return content.slice(0, TOOL_COLLAPSE_THRESHOLD) + "…";
}
function toggleToolLine(id: string) {
expandedToolLines = { ...expandedToolLines, [id]: !expandedToolLines[id] };
}
</script>
<div
@@ -262,7 +299,11 @@
{#if line.toolName}
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
{/if}
{#if line.type === "assistant" || line.type === "user"}
{#if line.type === "compact-prompt"}
<button class="compact-action-btn" onclick={handleCompact}>
⚡ Compact Conversation
</button>
{:else if line.type === "assistant" || line.type === "user"}
<div class="message-content-wrapper">
<Markdown
content={maskPaths(line.content, hidePaths)}
@@ -289,6 +330,22 @@
<span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
</button>
</div>
{:else if line.type === "tool" && isToolContentLong(maskPaths(line.content, hidePaths))}
<span class="tool-collapsible">
<HighlightedText
content={expandedToolLines[line.id]
? maskPaths(line.content, hidePaths)
: truncateToolContent(maskPaths(line.content, hidePaths))}
searchQuery={currentSearchQuery}
/>
<button
class="tool-toggle-btn"
onclick={() => toggleToolLine(line.id)}
title={expandedToolLines[line.id] ? "Collapse" : "Expand to see full content"}
>
{expandedToolLines[line.id] ? "▲" : "▼"}
</button>
</span>
{:else}
<HighlightedText
content={maskPaths(line.content, hidePaths)}
@@ -329,6 +386,42 @@
color: var(--terminal-error, #f87171);
}
.terminal-rate-limit {
color: var(--terminal-rate-limit, #fb923c);
}
.terminal-compact-prompt {
color: var(--text-secondary);
}
.terminal-worktree {
color: var(--terminal-worktree, #34d399);
}
.terminal-config-change {
color: var(--terminal-config-change, #a78bfa);
}
.compact-action-btn {
display: inline-flex;
align-items: center;
gap: 0.4em;
background: var(--bg-secondary);
border: 1px solid var(--terminal-error, #f87171);
color: var(--terminal-error, #f87171);
padding: 0.3em 0.8em;
cursor: pointer;
border-radius: 4px;
font-size: 0.9em;
font-family: inherit;
transition: all 0.15s ease;
}
.compact-action-btn:hover {
background: color-mix(in srgb, var(--terminal-error, #f87171) 15%, transparent);
color: var(--terminal-error, #f87171);
}
.terminal-default {
color: var(--text-primary);
}
@@ -408,4 +501,28 @@
.terminal-line {
position: relative;
}
.tool-collapsible {
display: inline-flex;
align-items: baseline;
gap: 0.4em;
}
.tool-toggle-btn {
background: none;
border: none;
color: var(--text-tertiary, #6b7280);
cursor: pointer;
font-size: 0.7em;
padding: 0;
line-height: 1;
opacity: 0.7;
transition: opacity 0.15s ease;
font-family: inherit;
}
.tool-toggle-btn:hover {
opacity: 1;
color: var(--terminal-tool, #c084fc);
}
</style>