feat(ux): B3.6 — editable energy labels (global + per-profile) + first-tap prompt

Settings → Energy: edit global energy labels (label + description per
level) with reset-to-defaults. Per-profile override toggle in the
Profiles section ("Customise for this profile" / "Use global").
EnergyChip and Tasks-page energy controls now resolve labels via
resolveEnergyLabels(profile, settings) — global by default, profile
override when present.

First-time prompt: on first energy-chip tap of the day (per profile)
or first-ever tap to Zero, an inline non-modal prompt asks "What does
Zero mean for you today?" with example chips. Per-day gate via
energyPromptLastShownDate; one-shot first-Zero gate via
energyPromptZeroSeen; Settings escape resets both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 19:30:06 +01:00
parent 7daf5677c9
commit fc3a56c0dc
6 changed files with 375 additions and 19 deletions

View File

@@ -19,9 +19,16 @@
//
// Callers pass the task's current energy and a setter. This component
// owns no state — the task store is the source of truth.
//
// B3.6: the displayed string per level now comes from
// resolveEnergyLabels(activeProfile, settings) rather than being
// hardcoded, so users can rename "Zero" to "Battery dead" without
// touching code.
import type { EnergyLevel } from "$lib/types/app";
import { Zap } from "lucide-svelte";
import { settings, profiles, page } from "$lib/stores/page.svelte.js";
import { resolveEnergyLabels } from "$lib/utils/energyLabels";
let {
energy = null as EnergyLevel | null,
@@ -45,11 +52,21 @@
return CYCLE[(idx + 1) % CYCLE.length];
}
// Active profile lookup against the localStorage `profiles` array.
// page.activeProfile is "None" or a profile name; we resolve to the
// Profile entry (or null) so the resolver can read its override.
let activeProfile = $derived(
page.activeProfile === "None"
? null
: profiles.find((p) => p.name === page.activeProfile) ?? null,
);
let labels = $derived(resolveEnergyLabels(activeProfile, settings));
function labelFor(level: EnergyLevel | null): string {
switch (level) {
case "high": return "High";
case "medium": return "Medium";
case "brain_dead": return "Zero";
case "high": return labels.high.label;
case "medium": return labels.medium.label;
case "brain_dead": return labels.brain_dead.label;
default: return "No energy set";
}
}