From ea673a22c23df0f4e5edf916c8d0989d719fbf4c Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 20 Apr 2026 21:16:16 +0200 Subject: [PATCH] =?UTF-8?q?style(workbench):=20polish=20timeline=20?= =?UTF-8?q?=E2=80=94=20time-range=20filter,=20event=20count,=20prominent?= =?UTF-8?q?=20revert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timeline is now the primary AI-review surface post function- calling migration — a handful of ergonomics tweaks so scanning a day's AI activity is less friction. - Time-range toggle (24h / 7T / alle) right-aligned in the filter row. Default stays `alle` so nothing changes for users who want everything. Client-side filter — over-fetch already caps at 500. - Each bucket shows an event-count pill next to the mission title ("8 Änderungen in dieser Iteration"), so the reader sees the weight of an iteration before expanding. - Revert button: slightly bigger, label reads "Rückgängig" instead of "Revert" (matches the rest of the German UI), bold icon, hover highlights with a softer red tuned to the theme-token palette. No logic changes to the revert or bucketing code. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/modules/ai-workbench/ListView.svelte | 105 +++++++++++++++--- 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte b/apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte index 97c608149..5453683ec 100644 --- a/apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte @@ -15,6 +15,7 @@ let moduleFilter = $state(null); let missionFilter = $state(null); let agentFilter = $state(null); + let timeRangeFilter = $state<'24h' | '7d' | 'all'>('all'); const events = $derived( useAiTimeline({ @@ -24,12 +25,19 @@ }) ); const allBuckets = $derived(bucketByIteration(events.value)); - // Agent filter is applied client-side after bucketing because the - // useAiTimeline query is keyed by module/mission only today. If the - // volume ever grows large enough for this to matter, push it into - // the query. + // Agent + time-range filters are applied client-side after bucketing + // because the useAiTimeline query is keyed by module/mission only + // today. If the volume ever grows large enough for this to matter, + // push them into the query. const buckets = $derived( - agentFilter ? allBuckets.filter((b) => b.agentId === agentFilter) : allBuckets + allBuckets + .filter((b) => (agentFilter ? b.agentId === agentFilter : true)) + .filter((b) => { + if (timeRangeFilter === 'all') return true; + const ageMs = Date.now() - new Date(b.firstTimestamp).getTime(); + const cutoff = timeRangeFilter === '24h' ? 86_400_000 : 7 * 86_400_000; + return ageMs <= cutoff; + }) ); const missions = $derived(useMissions()); const missionTitleById = $derived(new Map(missions.value.map((m) => [m.id, m.title]))); @@ -167,6 +175,21 @@ {/each} + {#if tab === 'timeline'} +
+ {#each [{ id: '24h', label: '24h' }, { id: '7d', label: '7T' }, { id: 'all', label: 'alle' }] as const as opt} + + {/each} +
+ {/if} {#if tab === 'audit'} @@ -228,6 +251,9 @@ {bucketAgent?.name ?? b.agentDisplayName} · {missionTitleById.get(b.missionId) ?? b.missionId} + {b.events.length} {#if b.rationale}

{b.rationale}

@@ -238,9 +264,10 @@ class="revert" disabled={revertingKey !== null} onclick={() => handleRevert(b.key, b.missionId, b.iterationId)} + title="Alle Änderungen dieser Iteration zurücknehmen" > - - {revertingKey === b.key ? 'Läuft…' : 'Revert'} + + {revertingKey === b.key ? 'Läuft…' : 'Rückgängig'}
    @@ -362,6 +389,43 @@ font: inherit; font-size: 0.8125rem; } + .range-group { + display: inline-flex; + gap: 0.125rem; + margin-left: auto; + background: hsl(var(--color-muted)); + border-radius: 0.375rem; + padding: 0.125rem; + } + .range-btn { + padding: 0.25rem 0.5rem; + border: none; + background: transparent; + color: hsl(var(--color-muted-foreground)); + font: inherit; + font-size: 0.75rem; + cursor: pointer; + border-radius: 0.25rem; + } + .range-btn:hover { + background: hsl(var(--color-surface)); + } + .range-active { + background: hsl(var(--color-surface)); + color: hsl(var(--color-foreground)); + font-weight: 600; + } + .event-count { + display: inline-block; + margin-left: 0.5rem; + padding: 0 0.375rem; + font-size: 0.6875rem; + font-weight: 600; + color: hsl(var(--color-muted-foreground)); + background: hsl(var(--color-muted)); + border-radius: 999px; + font-variant-numeric: tabular-nums; + } .empty { color: hsl(var(--color-muted-foreground)); font-size: 0.875rem; @@ -423,20 +487,31 @@ .revert { display: inline-flex; align-items: center; - gap: 0.25rem; - padding: 0.25rem 0.5rem; + gap: 0.375rem; + padding: 0.4rem 0.75rem; border: 1px solid hsl(var(--color-border)); - border-radius: 0.25rem; + border-radius: 0.375rem; background: hsl(var(--color-surface)); - color: hsl(var(--color-muted-foreground)); + color: hsl(var(--color-foreground)); font: inherit; - font-size: 0.6875rem; + font-size: 0.75rem; + font-weight: 500; cursor: pointer; + transition: + background 0.15s, + border-color 0.15s, + color 0.15s; + align-self: start; + white-space: nowrap; } .revert:hover:not(:disabled) { - color: #8a1b1b; - border-color: #e99; - background: #fff0f0; + color: hsl(0 72% 45%); + border-color: hsl(0 72% 60%); + background: hsl(0 72% 95%); + } + .revert:disabled { + opacity: 0.5; + cursor: not-allowed; } .events { list-style: none;