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,
|
taskLists, addTaskList, renameTaskList, deleteTaskList,
|
||||||
settings, saveSettings,
|
settings, saveSettings,
|
||||||
} from "$lib/stores/page.svelte.js";
|
} from "$lib/stores/page.svelte.js";
|
||||||
|
import { recentCompletions, todayCount } from "$lib/stores/completionStats.svelte";
|
||||||
import WipTaskList from '$lib/components/WipTaskList.svelte';
|
import WipTaskList from '$lib/components/WipTaskList.svelte';
|
||||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||||
|
import CompletionSparkline from "$lib/components/CompletionSparkline.svelte";
|
||||||
import EnergyChip from '$lib/components/EnergyChip.svelte';
|
import EnergyChip from '$lib/components/EnergyChip.svelte';
|
||||||
import { SquareCheck, Search, ExternalLink, ChevronLeft, ArrowUpDown, Plus, X, ChevronRight, Zap } from 'lucide-svelte';
|
import { SquareCheck, Search, ExternalLink, ChevronLeft, ArrowUpDown, Plus, X, ChevronRight, Zap } from 'lucide-svelte';
|
||||||
import Card from "$lib/components/Card.svelte";
|
import Card from "$lib/components/Card.svelte";
|
||||||
@@ -284,7 +286,31 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center px-7 pt-6 pb-2">
|
<div class="flex items-center px-7 pt-6 pb-2">
|
||||||
<div>
|
<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>
|
<p class="text-[11px] text-text-tertiary mt-1">Add tasks manually. Automatic extraction from your transcripts is coming.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ const DAYS = 7;
|
|||||||
|
|
||||||
export const recentCompletions = $state<DailyCompletionCount[]>([]);
|
export const recentCompletions = $state<DailyCompletionCount[]>([]);
|
||||||
|
|
||||||
// Reactive derived value. Svelte 5 template consumers read it as a
|
// Reactive getter. Svelte 5 does not allow exporting $derived at module
|
||||||
// plain property (no parens) and it recomputes when recentCompletions
|
// level ("derived_invalid_export"), so we expose a function instead.
|
||||||
// changes.
|
// Template consumers call todayCount() in reactive contexts and it
|
||||||
export const todayCount = $derived(recentCompletions.at(-1)?.count ?? 0);
|
// recomputes when recentCompletions changes.
|
||||||
|
export function todayCount(): number {
|
||||||
|
return recentCompletions.at(-1)?.count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function refresh(): Promise<void> {
|
export async function refresh(): Promise<void> {
|
||||||
if (!hasTauriRuntime()) return;
|
if (!hasTauriRuntime()) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user