collet 0.1.0

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
<script lang="ts">
	import { onMount } from 'svelte';
	import { layoutStore } from '$lib/stores/layout.svelte.js';
	import { appStore } from '$lib/store.svelte.js';
	import SwarmGraph from '$lib/components/swarm/SwarmGraph.svelte';
	import AgentDetailPanel from '$lib/components/swarm/AgentDetailPanel.svelte';

	let { isMobile = false }: { isMobile?: boolean } = $props();

	let mcpServers = $state<string[]>([]);

	onMount(() => {
		mcpServers = deriveMcpServers();
	});

	// Watch for tool updates
	$effect(() => {
		mcpServers = deriveMcpServers();
	});

	function deriveMcpServers(): string[] {
		const seen = new Set<string>();
		for (const tool of appStore.tools) {
			if (tool.name.startsWith('mcp__')) {
				const server = tool.name.slice(5).split('__')[0];
				if (server) seen.add(server);
			}
		}
		return [...seen].sort();
	}

	function formatTokens(n: number): string {
		if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
		return String(n);
	}

	function contextPercent(): string {
		const s = appStore.status;
		if (!s || !s.context_tokens) return '0%';
		return Math.round((s.context_tokens / 128000) * 100) + '%';
	}

	function contextTokens(): string {
		const s = appStore.status;
		if (!s || !s.context_tokens) return '0';
		return formatTokens(s.context_tokens);
	}

	function lspServers(): { name: string; available: boolean }[] {
		// TODO: Derive from appStore.tools when LSP tools are available
		return [];
	}
</script>

{#if layoutStore.rightSidebarOpen}
	<aside class="right-sidebar" class:mobile={isMobile}>
		{#if isMobile}
			<div class="mobile-header">
				<span class="mobile-title">Panels</span>
				<button class="close-btn" onclick={() => layoutStore.rightSidebarOpen = false} aria-label="Close panels">x</button>
			</div>
		{/if}

		<div class="sections">
			<!-- Swarm Graph -->
			{#if appStore.showSwarmGraph}
				<div class="section swarm-section">
					<SwarmGraph />
					<AgentDetailPanel />
				</div>
			{/if}

			<!-- Context -->
			<div class="section">
				<div class="section-header">Context</div>
				<div class="section-body">
					<span class="item dim">{contextTokens()} tokens</span>
					<span class="item dim">{contextPercent()} used</span>
				</div>
			</div>

			<!-- MCP -->
			<div class="section">
				<div class="section-header">MCP</div>
				<div class="section-body">
					{#each mcpServers as server}
						<span class="item">
							<span class="dot cyan">&#x2022;</span> {server}
						</span>
					{:else}
						<span class="item dim">No servers</span>
					{/each}
				</div>
			</div>

			<!-- LSP -->
			<div class="section">
				<div class="section-header">LSP</div>
				<div class="section-body">
					{#each lspServers() as server}
						<span class="item" class:dim={!server.available}>
							<span class="dot magenta">&#x25aa;</span> {server.name}
						</span>
					{:else}
						<span class="item dim">None detected</span>
					{/each}
				</div>
			</div>

			<!-- Changed Files -->
			<div class="section">
				<div class="section-header">Changed Files</div>
				<div class="section-body">
					{#each appStore.modifiedFiles as file}
						<span class="item"><span class="dot green">&#x2022;</span> {file.split('/').pop()}</span>
					{:else}
						<span class="item dim">No changes yet</span>
					{/each}
				</div>
			</div>
		</div>
	</aside>
{/if}

<style>
	.right-sidebar {
		width: var(--right-w, 240px);
		flex-shrink: 0;
		background: var(--bg-surface);
		border-left: 1px solid var(--border);
		display: flex;
		flex-direction: column;
		overflow: hidden;
		font-family: var(--font-mono);
	}

	.right-sidebar.mobile {
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0;
		z-index: 100;
		width: 280px;
		box-shadow: -4px 0 24px rgba(0, 0, 0, 0.4);
	}

	.mobile-header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 0.375rem 0.5rem;
		border-bottom: 1px solid var(--border);
		flex-shrink: 0;
	}

	.mobile-title {
		font-size: 0.8125rem;
		font-weight: 700;
		color: var(--fg);
	}

	.close-btn {
		all: unset;
		cursor: pointer;
		color: var(--fg-dim);
		padding: 0.125rem 0.25rem;
	}

	.close-btn:hover {
		color: var(--fg);
	}

	.sections {
		flex: 1;
		overflow-y: auto;
		padding: 0.25rem 0;
	}

	.section {
		padding: 0.375rem 0.5rem;
	}

	.section-header {
		font-size: 0.75rem;
		font-weight: 700;
		color: var(--fg-bright);
		text-transform: uppercase;
		letter-spacing: 0.04em;
		margin-bottom: 0.25rem;
	}

	.section-body {
		display: flex;
		flex-direction: column;
		gap: 0.0625rem;
	}

	.item {
		font-size: 0.8125rem;
		color: var(--fg);
		line-height: 1.4;
		padding-left: 0.25rem;
	}

	.item.dim {
		color: var(--fg-dim);
	}

	.dot {
		margin-right: 0.25rem;
	}

	.dot.cyan {
		color: var(--cyan);
	}

	.dot.magenta {
		color: var(--magenta);
	}

	.dot.green {
		color: var(--green);
	}

	.swarm-section {
		padding: 0.375rem !important;
	}
</style>