feat(ui): interactive graph, strip context menus, days by activity, message times, limit status words

This commit is contained in:
hh
2026-09-02 04:24:15 +02:00
parent 025f2dbc3f
commit c6401f1114
27 changed files with 1619 additions and 381 deletions
+60 -20
View File
@@ -14,7 +14,7 @@
import type { ConversationIndex } from "$lib/panel/index.svelte";
import Strip from "$lib/shell/strip.svelte";
import { cn } from "$lib/utils";
import { type DayGroup, groupByDay } from "./days";
import { type DayGroup, daySummary, groupByDay } from "./days";
import NewConversation from "./new-conversation.svelte";
let {
@@ -64,10 +64,9 @@
}
return index.conversations.filter(
(row) =>
row.kind !== "job" &&
((row.title ?? "").toLowerCase().includes(needle) ||
row.id.startsWith(needle) ||
(row.last_item?.text ?? "").toLowerCase().includes(needle))
(row.title ?? "").toLowerCase().includes(needle) ||
row.id.startsWith(needle) ||
(row.last_item?.text ?? "").toLowerCase().includes(needle)
);
});
@@ -120,22 +119,20 @@
expanded = next;
}
function summary(group: DayGroup): string {
const parts: string[] = [];
if (group.branches.length) {
parts.push(
`${group.branches.length} ${group.branches.length === 1 ? "branch" : "branches"}`
);
let jobsOpen = $state<Set<string>>(new Set());
function toggleJobs(day: string) {
const next = new Set(jobsOpen);
if (next.has(day)) {
next.delete(day);
} else {
next.add(day);
}
if (group.deep.length) {
parts.push(`${group.deep.length} deep`);
}
if (group.others.length) {
parts.push(`${group.others.length} more`);
}
return parts.join(" · ");
jobsOpen = next;
}
const refresh = () => index.load().catch(() => undefined);
const linkOf = (id: string) => href?.(id);
</script>
@@ -188,10 +185,12 @@
<div class="bay-rack">
{#each found as row (row.id)}
<Strip
{client}
current={row.id === selected}
detail={row.last_item?.text ?? ""}
href={linkOf(row.id)}
{now}
onChanged={refresh}
onclick={onOpen}
{row}
/>
@@ -254,17 +253,19 @@
></span>
{/if}
<span class="truncate text-muted-foreground text-xs">
{summary(group)}
{daySummary(group, position === 0)}
</span>
</button>
{#if shown}
<div class="bay-rack">
{#if group.master}
<Strip
{client}
current={group.master.id === selected}
detail={group.master.last_item?.text ?? ""}
href={linkOf(group.master.id)}
{now}
onChanged={refresh}
onclick={onOpen}
row={group.master}
/>
@@ -272,34 +273,73 @@
{#each group.branches as row (row.id)}
<Strip
class="strip-child"
{client}
current={row.id === selected}
detail={row.last_item?.text ?? ""}
href={linkOf(row.id)}
{now}
onChanged={refresh}
onclick={onOpen}
{row}
/>
{/each}
{#each group.deep as row (row.id)}
<Strip
{client}
current={row.id === selected}
detail={row.last_item?.text ?? ""}
href={linkOf(row.id)}
{now}
onChanged={refresh}
onclick={onOpen}
{row}
/>
{/each}
{#each group.others as row (row.id)}
{#each [...group.forks, ...group.others] as row (row.id)}
<Strip
class="strip-child"
{client}
current={row.id === selected}
detail="{row.kind} · {row.status}"
href={linkOf(row.id)}
{now}
onChanged={refresh}
onclick={onOpen}
{row}
/>
{/each}
{#if group.jobs.length > 0}
{@const shownJobs = jobsOpen.has(group.day)}
<button
aria-expanded={shownJobs}
class="strip w-full text-left text-muted-foreground text-xs"
onclick={() => toggleJobs(group.day)}
type="button"
>
<span class="size-5"></span>
<span>
{group.jobs.length}
{group.jobs.length === 1 ? "job run" : "job runs"}
· {shownJobs ? "hide" : "show"}
</span>
<span></span>
</button>
{#if shownJobs}
{#each group.jobs as row (row.id)}
<Strip
class="strip-child"
{client}
current={row.id === selected}
detail={row.last_item?.text ?? ""}
href={linkOf(row.id)}
{now}
onChanged={refresh}
onclick={onOpen}
{row}
/>
{/each}
{/if}
{/if}
</div>
{/if}
</section>