collet 0.1.0

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
<script lang="ts">
	import { onMount } from 'svelte';
	import { sessionsStore } from '$lib/stores/sessions.svelte.js';
	import { appStore } from '$lib/store.svelte.js';
	import { deleteSession } from '$lib/api.js';

	onMount(() => {
		sessionsStore.load(appStore.authToken ?? undefined);
	});

	function formatTs(ts: string): string {
		const d = new Date(ts);
		const now = new Date();
		const diffMs = now.getTime() - d.getTime();
		const diffMin = Math.floor(diffMs / 60000);
		if (diffMin < 1) return 'just now';
		if (diffMin < 60) return `${diffMin}m ago`;
		const diffH = Math.floor(diffMin / 60);
		if (diffH < 24) return `${diffH}h ago`;
		const diffD = Math.floor(diffH / 24);
		if (diffD < 7) return `${diffD}d ago`;
		return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
	}

	function truncate(s: string | null, max: number): string {
		if (!s) return 'New conversation';
		return s.length > max ? s.slice(0, max) + '...' : s;
	}

	async function handleDelete(e: MouseEvent, id: string) {
		e.stopPropagation();
		await deleteSession(id, appStore.authToken ?? undefined);
		await sessionsStore.load(appStore.authToken ?? undefined);
	}
</script>

<div class="session-list">
	{#if sessionsStore.loading}
		<div class="empty">Loading...</div>
	{:else if sessionsStore.sessions.length === 0}
		<div class="empty">No previous sessions</div>
	{:else}
		{#each sessionsStore.sessions as session (session.id)}
			<!-- svelte-ignore a11y_no_static_element_interactions -->
			<div
				class="session-item"
				class:selected={sessionsStore.selectedId === session.id}
				onclick={() => sessionsStore.select(session.id, appStore.authToken ?? undefined)}
				onkeydown={(e) => { if (e.key === 'Enter') sessionsStore.select(session.id, appStore.authToken ?? undefined); }}
				tabindex="0"
				role="option"
				aria-selected={sessionsStore.selectedId === session.id}
			>
				<div class="session-row">
					<span class="session-indicator">&mdash;</span>
					<span class="session-preview">{truncate(session.task_preview, 50)}</span>
				</div>
				<div class="session-meta">
					<span class="session-time">{formatTs(session.timestamp)}</span>
					<button
						class="session-del"
						onclick={(e) => handleDelete(e, session.id)}
						aria-label="Delete session"
					>&times;</button>
				</div>
			</div>
		{/each}
	{/if}
</div>

<style>
	.session-list {
		padding: 0.25rem 0;
	}

	.empty {
		padding: 1.5rem 1rem;
		color: var(--fg-dim);
		font-size: 0.875rem;
		text-align: center;
	}

	.session-item {
		display: flex;
		flex-direction: column;
		gap: 0.125rem;
		padding: 0.625rem 1rem;
		cursor: pointer;
		border-left: 2px solid transparent;
		transition: background 0.1s;
	}

	.session-item:hover {
		background: var(--bg-elevated);
	}

	.session-item:hover .session-del {
		opacity: 1;
	}

	.session-item.selected {
		background: var(--bg-elevated);
		border-left-color: var(--accent);
	}

	.session-item:focus-visible {
		outline: 2px solid var(--accent);
		outline-offset: -2px;
	}

	.session-row {
		display: flex;
		align-items: center;
		gap: 0.5rem;
	}

	.session-indicator {
		color: var(--fg-dim);
		flex-shrink: 0;
	}

	.session-preview {
		font-size: 0.875rem;
		color: var(--fg);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.session-meta {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding-left: 1.25rem;
	}

	.session-time {
		font-size: 0.75rem;
		color: var(--fg-dim);
	}

	.session-del {
		all: unset;
		cursor: pointer;
		color: var(--fg-dim);
		opacity: 0;
		font-size: 0.75rem;
		padding: 0.125rem 0.25rem;
		border-radius: 3px;
		transition: opacity 0.1s, color 0.1s;
	}

	.session-del:hover {
		color: var(--red);
	}

	.session-del:focus-visible {
		opacity: 1;
		outline: 1px solid var(--red);
	}
</style>