feat(gamification): today count + sparkline on Tasks header
Badge renders when today's count > 0. Sparkline renders when the setting is enabled and any of the last 7 days has a completion. Wrapped in a narrow aria-live region so increments announce without re-reading the rest of the header. Fix: converted todayCount from $derived module export to a getter function (Svelte 5 derived_invalid_export constraint). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,10 @@
|
||||
taskLists, addTaskList, renameTaskList, deleteTaskList,
|
||||
settings, saveSettings,
|
||||
} from "$lib/stores/page.svelte.js";
|
||||
import { recentCompletions, todayCount } from "$lib/stores/completionStats.svelte";
|
||||
import WipTaskList from '$lib/components/WipTaskList.svelte';
|
||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||
import CompletionSparkline from "$lib/components/CompletionSparkline.svelte";
|
||||
import EnergyChip from '$lib/components/EnergyChip.svelte';
|
||||
import { SquareCheck, Search, ExternalLink, ChevronLeft, ArrowUpDown, Plus, X, ChevronRight, Zap } from 'lucide-svelte';
|
||||
import Card from "$lib/components/Card.svelte";
|
||||
@@ -284,7 +286,31 @@
|
||||
<!-- Header -->
|
||||
<div class="flex items-center px-7 pt-6 pb-2">
|
||||
<div>
|
||||
<h2 class="text-xl font-display text-text italic">Tasks</h2>
|
||||
<div class="flex items-baseline gap-3">
|
||||
<h2 class="text-xl font-display text-text italic">Tasks</h2>
|
||||
|
||||
<!-- Phase 8 badge + sparkline. aria-live scoped to just this
|
||||
wrapper so screen readers announce completions without
|
||||
re-reading the rest of the header. -->
|
||||
<div
|
||||
class="flex items-center gap-2"
|
||||
aria-live="polite"
|
||||
>
|
||||
{#if todayCount() > 0}
|
||||
<span
|
||||
class="text-[11px] text-text-tertiary"
|
||||
aria-label={`${todayCount()} ${todayCount() === 1 ? "task" : "tasks"} completed today`}
|
||||
>
|
||||
{todayCount()} today
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if settings.showMomentumSparkline}
|
||||
<CompletionSparkline data={recentCompletions} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-[11px] text-text-tertiary mt-1">Add tasks manually. Automatic extraction from your transcripts is coming.</p>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
|
||||
@@ -21,10 +21,13 @@ const DAYS = 7;
|
||||
|
||||
export const recentCompletions = $state<DailyCompletionCount[]>([]);
|
||||
|
||||
// Reactive derived value. Svelte 5 template consumers read it as a
|
||||
// plain property (no parens) and it recomputes when recentCompletions
|
||||
// changes.
|
||||
export const todayCount = $derived(recentCompletions.at(-1)?.count ?? 0);
|
||||
// Reactive getter. Svelte 5 does not allow exporting $derived at module
|
||||
// level ("derived_invalid_export"), so we expose a function instead.
|
||||
// Template consumers call todayCount() in reactive contexts and it
|
||||
// recomputes when recentCompletions changes.
|
||||
export function todayCount(): number {
|
||||
return recentCompletions.at(-1)?.count ?? 0;
|
||||
}
|
||||
|
||||
export async function refresh(): Promise<void> {
|
||||
if (!hasTauriRuntime()) return;
|
||||
|
||||
Reference in New Issue
Block a user