feat: major feature additions and improvements (#135)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m2s
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled
CI / Lint & Test (push) Has been cancelled

## Summary

This PR includes major feature additions, bug fixes, comprehensive testing improvements, and responsive design enhancements!

## New Features 

### Plugin & MCP Management (#133, #134)
- **Plugin Management Panel**: Install, uninstall, enable/disable, and update plugins
- **MCP Server Management Panel**: Add/remove MCP servers, view detailed configuration
- **Marketplace Management**: Add/remove plugin marketplaces from GitHub
- Backend commands for full CLI integration (`list_plugins`, `install_plugin`, `add_mcp_server`, etc.)
- Beautiful UI with proper loading states, error handling, and theme support

### Visual Todo List Panel (#132)
- Real-time todo list display when Hikari uses the `TodoWrite` tool
- Shows pending/in-progress/completed status with visual indicators
- Progress bar and completion count
- Automatically clears on disconnect
- Theme-aware styling

### Clear Session History Button (#130)
- "Clear All Sessions" button in Session History panel
- Confirmation dialog with session count
- Keyboard support and accessibility features
- Gives users control over disk usage

### CLI Version Display (#131)
- Displays Claude CLI version in status bar
- Auto-polls every 30 seconds for updates
- Useful for debugging and feature compatibility

## Bug Fixes 🐛

### Stats Panel Scrolling (#136)
- **Fixed stats panel overflow**: Added scrollable container with `max-height` constraint
- Stats panel now scrolls when content (Tools Used, Historical Costs, Budget sections) gets too long
- Prevents content from overflowing off screen

### Agent Monitor Fixes (#122)
- **Fixed agents stuck in "running" state**: Added `SubagentStop` hook parsing
- **Fixed agents persisting after disconnect**: Call `clearConversation()` on disconnect
- **Fixed "Kill All" button**: Now properly marks all agents as errored
- **Fixed badge persisting after tab close**: Cleanup agents when conversation is deleted
- Comprehensive tests for agent lifecycle management

### Discord RPC Cleanup (#129)
- Removed file-based logging for Discord RPC
- Replaced with proper `tracing` framework usage
- Reduces disk usage and eliminates maintenance burden

### Close Modal Bug Fix (#128)
- Fixed close confirmation modal not triggering after Discord RPC refactor
- Removed frontend calls to deleted `log_discord_rpc` command
- Modal now works correctly after all operations

### Responsive Design Fixes (#118)
- Fixed top navigation icons getting cut off at small screen widths
- Fixed Connect button disappearing on narrow screens
- Fixed bottom status info (clock, CLI version) getting cut off
- Added flex-wrap and mobile-optimised layouts
- Icons-only mode on screens < 640px
- Vertical stacking on screens < 768px

## Testing Improvements 🧪

### Comprehensive Test Coverage (#114)
- **417 backend tests** (up from 408)
- **387 frontend tests** (up from 363)
- **61%+ backend code coverage**
- Added E2E integration tests for cross-platform notification commands
- New test files: `agents.test.ts`, comprehensive CLI parsing tests
- Tests for `debug_logger.rs`, `bridge_manager.rs`, `notifications.rs`
- Console mocking for cleaner test output
- Fixed flaky frontend tests

### Testing Documentation
- Updated CLAUDE.md with comprehensive testing guidelines
- Documented mocking approaches (console mocking, E2E command structure testing)
- Added step-by-step guide for adding tests to new features
- Goal to maintain ~100% test coverage documented

## Closes

Closes #114
Closes #118
Closes #122
Closes #128
Closes #129
Closes #130
Closes #131
Closes #132
Closes #133
Closes #134
Closes #136

## Technical Details

- All new backend commands properly registered in `lib.rs`
- CLI output parsing with comprehensive test coverage
- Cross-platform compatibility verified through E2E tests (Linux CI can test Windows commands)
- Theme-aware UI components using CSS variables throughout
- Proper TypeScript types for all new stores and components
- ESLint and Prettier compliant
- All Clippy warnings addressed

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #135
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #135.
This commit is contained in:
2026-02-07 21:15:41 -08:00
committed by Naomi Carrigan
parent 34e9af57f0
commit f173892aaa
35 changed files with 4701 additions and 224 deletions
+84 -70
View File
@@ -4,9 +4,10 @@
import ConversationTabs from "./ConversationTabs.svelte";
import Markdown from "./Markdown.svelte";
import HighlightedText from "./HighlightedText.svelte";
import ThinkingBlock from "./ThinkingBlock.svelte";
import { searchState, searchQuery } from "$lib/stores/search";
import { clipboardStore } from "$lib/stores/clipboard";
import { shouldHidePaths, maskPaths } from "$lib/stores/config";
import { shouldHidePaths, maskPaths, showThinkingBlocks } from "$lib/stores/config";
let terminalElement: HTMLDivElement;
let shouldAutoScroll = true;
@@ -24,6 +25,11 @@
hidePaths = value;
});
let showThinking = true;
showThinkingBlocks.subscribe((value) => {
showThinking = value;
});
claudeStore.terminalLines.subscribe((value) => {
lines = value;
});
@@ -84,6 +90,8 @@
return "terminal-tool";
case "error":
return "terminal-error";
case "thinking":
return "terminal-thinking";
default:
return "terminal-default";
}
@@ -209,80 +217,86 @@
</div>
{:else}
{#each lines as line (line.id)}
<div
class="terminal-line mb-2 {getLineClass(line.type)} relative group"
style={line.parentToolUseId
? "margin-left: 16px; padding-left: 8px; border-left: 2px solid var(--accent-primary);"
: ""}
>
<span class="terminal-timestamp text-xs mr-2">{formatTime(line.timestamp)}</span>
{#if line.parentToolUseId}
<span class="text-xs mr-2 opacity-60" title="Message from subagent">
<svg
class="inline-block w-3 h-3 -mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
{#if line.type === "thinking"}
{#if showThinking}
<ThinkingBlock content={line.content} timestamp={line.timestamp} />
{/if}
{:else}
<div
class="terminal-line mb-2 {getLineClass(line.type)} relative group"
style={line.parentToolUseId
? "margin-left: 16px; padding-left: 8px; border-left: 2px solid var(--accent-primary);"
: ""}
>
<span class="terminal-timestamp text-xs mr-2">{formatTime(line.timestamp)}</span>
{#if line.parentToolUseId}
<span class="text-xs mr-2 opacity-60" title="Message from subagent">
<svg
class="inline-block w-3 h-3 -mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</span>
{/if}
{#if line.cost && line.cost.costUsd > 0}
<span
class="terminal-cost text-xs mr-2"
title="Input: {line.cost.inputTokens} | Output: {line.cost.outputTokens}"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
${line.cost.costUsd < 0.01
? line.cost.costUsd.toFixed(4)
: line.cost.costUsd.toFixed(3)}
</span>
{/if}
{#if getLinePrefix(line.type)}
<span class="terminal-prefix mr-2">{getLinePrefix(line.type)}</span>
{/if}
{#if line.toolName}
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
{/if}
{#if line.type === "assistant" || line.type === "user"}
<div class="message-content-wrapper">
<Markdown
content={maskPaths(line.content, hidePaths)}
searchQuery={currentSearchQuery}
/>
</svg>
</span>
{/if}
{#if line.cost && line.cost.costUsd > 0}
<span
class="terminal-cost text-xs mr-2"
title="Input: {line.cost.inputTokens} | Output: {line.cost.outputTokens}"
>
${line.cost.costUsd < 0.01
? line.cost.costUsd.toFixed(4)
: line.cost.costUsd.toFixed(3)}
</span>
{/if}
{#if getLinePrefix(line.type)}
<span class="terminal-prefix mr-2">{getLinePrefix(line.type)}</span>
{/if}
{#if line.toolName}
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
{/if}
{#if line.type === "assistant" || line.type === "user"}
<div class="message-content-wrapper">
<Markdown
<button
class="copy-message-btn opacity-0 group-hover:opacity-100 transition-opacity"
onclick={() => handleCopyMessage(line.id, line.content)}
title="Copy message"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
</button>
</div>
{:else}
<HighlightedText
content={maskPaths(line.content, hidePaths)}
searchQuery={currentSearchQuery}
/>
<button
class="copy-message-btn opacity-0 group-hover:opacity-100 transition-opacity"
onclick={() => handleCopyMessage(line.id, line.content)}
title="Copy message"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
</button>
</div>
{:else}
<HighlightedText
content={maskPaths(line.content, hidePaths)}
searchQuery={currentSearchQuery}
/>
{/if}
</div>
{/if}
</div>
{/if}
{/each}
{/if}
</div>