feat(rituals): Phase 5 — morning triage, evening wind-down, autostart
Three opt-in rituals, all default OFF. Research-anchored (Barkley's point-of-performance, Sweller cognitive-load theory, Newport shutdown ritual, Gollwitzer implementation intentions, Thaler/Sunstein nudge with informed consent for the ADHD audience). Morning triage: modal gated on ritualsMorning toggle, configurable trigger time (default 08:00 to respect ADHD sleep inertia rather than the spec's 06:00), "pick up to three for today" with a gentle swap message on the fourth attempt. Skip sets last-shown-today so it never re-prompts the same calendar day. last-shown persists via kon_storage. Evening wind-down: dedicated page, user-triggered only (tray menu + Settings button). Mechanical closure + physical reset + intentional cue — the whole Newport template. Open loops are read-only reflection; Tasks page owns transactions. Copy is additive throughout: "you finished X today", never "you didn't finish Y". Autostart: tauri-plugin-autostart registered (LaunchAgent on macOS, .desktop on Linux, registry Run on Windows). No bespoke Rust commands — frontend calls the plugin's invoke-handlers directly. Toggle in Settings is one-way (click → OS call → state update) to avoid the UI lying during the round-trip. First-run presents a forced-choice prompt for all three options, with "skip all" escape hatches per step. Copy audit against RSD literature: no "overdue", "failed", or day-to-day comparison framing anywhere in ritual surfaces. Post-v0.1 ideas captured in the roadmap: calendar integration (read-only ICS as interim, cloud sync parked) and right-click-to-task (in-app simple, system-wide a separate phase).
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
// @ts-nocheck
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { page, settings } from "$lib/stores/page.svelte.js";
|
||||
import { page, settings, saveSettings } from "$lib/stores/page.svelte.js";
|
||||
import UnicodeSpinner from "$lib/components/UnicodeSpinner.svelte";
|
||||
import { Download, CheckCircle } from 'lucide-svelte';
|
||||
import { toasts } from "$lib/stores/toasts.svelte.js";
|
||||
import { Download, CheckCircle, Sunrise, Moon, Play } from 'lucide-svelte';
|
||||
|
||||
let systemInfo = $state(null);
|
||||
let models = $state([]);
|
||||
@@ -75,8 +76,15 @@
|
||||
}
|
||||
|
||||
ready = true;
|
||||
// Brief "Ready" beat, then onto the rituals prompt (or straight
|
||||
// to dictation if the user has already seen it).
|
||||
setTimeout(() => {
|
||||
page.current = "dictation";
|
||||
if (settings.ritualsPromptSeen) {
|
||||
page.current = "dictation";
|
||||
} else {
|
||||
ready = false;
|
||||
ritualsStep = "morning";
|
||||
}
|
||||
}, 1500);
|
||||
} catch (e) {
|
||||
error = `Download failed: ${e}`;
|
||||
@@ -87,7 +95,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5: forced-choice rituals + autostart prompts. Research on
|
||||
// libertarian-paternalism nudges (Thaler/Sunstein) says defaults
|
||||
// drive uptake, but the ADHD target audience is sensitive to
|
||||
// parental framing — so we present explicit opt-in with calm copy
|
||||
// rather than defaulting anything on.
|
||||
type RitualsStep = "idle" | "morning" | "evening" | "autostart" | "done";
|
||||
let ritualsStep = $state<RitualsStep>("idle");
|
||||
let autostartApplying = $state(false);
|
||||
|
||||
async function answerMorning(yes: boolean) {
|
||||
settings.ritualsMorning = yes;
|
||||
saveSettings();
|
||||
ritualsStep = "evening";
|
||||
}
|
||||
|
||||
async function answerEvening(yes: boolean) {
|
||||
settings.ritualsEvening = yes;
|
||||
saveSettings();
|
||||
ritualsStep = "autostart";
|
||||
}
|
||||
|
||||
async function answerAutostart(yes: boolean) {
|
||||
autostartApplying = true;
|
||||
try {
|
||||
const plugin = await import("@tauri-apps/plugin-autostart");
|
||||
if (yes) {
|
||||
await plugin.enable();
|
||||
settings.launchAtLogin = true;
|
||||
} else {
|
||||
// Don't call disable() on a fresh install — there's nothing to
|
||||
// disable, and some platforms treat "disable when unset" as an
|
||||
// error. Just record the choice.
|
||||
settings.launchAtLogin = false;
|
||||
}
|
||||
} catch (err) {
|
||||
toasts.warn("Could not update autostart", String(err));
|
||||
} finally {
|
||||
autostartApplying = false;
|
||||
settings.ritualsPromptSeen = true;
|
||||
saveSettings();
|
||||
ritualsStep = "done";
|
||||
setTimeout(() => { page.current = "dictation"; }, 900);
|
||||
}
|
||||
}
|
||||
|
||||
function skipRituals() {
|
||||
settings.ritualsPromptSeen = true;
|
||||
saveSettings();
|
||||
page.current = "dictation";
|
||||
}
|
||||
|
||||
function skipSetup() {
|
||||
// Skipping model download still marks the rituals prompt as seen —
|
||||
// the user chose to bypass the walk-through; they can find rituals
|
||||
// in Settings when they're ready.
|
||||
settings.ritualsPromptSeen = true;
|
||||
saveSettings();
|
||||
page.current = "dictation";
|
||||
}
|
||||
|
||||
@@ -107,7 +171,84 @@
|
||||
<div class="text-center">
|
||||
<CheckCircle size={40} strokeWidth={1.5} class="text-success mx-auto" />
|
||||
<h2 class="text-xl font-medium text-text mt-4">Ready to go</h2>
|
||||
<p class="text-sm text-text-secondary mt-2">Press the button. Start talking. That's it.</p>
|
||||
<p class="text-sm text-text-secondary mt-2">A few quick choices, then you're in.</p>
|
||||
</div>
|
||||
|
||||
{:else if ritualsStep === "morning"}
|
||||
<div class="w-full max-w-md mx-auto text-center">
|
||||
<Sunrise size={32} strokeWidth={1.5} class="text-accent mx-auto mb-3" />
|
||||
<h2 class="text-xl font-medium text-text">Morning triage?</h2>
|
||||
<p class="text-sm text-text-secondary mt-3 leading-relaxed">
|
||||
On the first launch of the day, a gentle modal shows yesterday's open items and asks you to pick up to three for today. The rest can wait.
|
||||
</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-3">Off by default. You can change your mind any time in Settings.</p>
|
||||
<div class="flex items-center justify-center gap-3 mt-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm border border-border text-text-secondary hover:bg-hover"
|
||||
onclick={() => answerMorning(false)}
|
||||
>No thanks</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm bg-accent text-white hover:bg-accent-hover"
|
||||
onclick={() => answerMorning(true)}
|
||||
>Yes, turn it on</button>
|
||||
</div>
|
||||
<button
|
||||
class="mt-5 text-xs text-text-tertiary hover:text-text-secondary underline"
|
||||
onclick={skipRituals}
|
||||
>Skip all these questions</button>
|
||||
</div>
|
||||
|
||||
{:else if ritualsStep === "evening"}
|
||||
<div class="w-full max-w-md mx-auto text-center">
|
||||
<Moon size={32} strokeWidth={1.5} class="text-accent mx-auto mb-3" />
|
||||
<h2 class="text-xl font-medium text-text">Evening wind-down?</h2>
|
||||
<p class="text-sm text-text-secondary mt-3 leading-relaxed">
|
||||
A reflective page you can open when you want to close the day. Shows what you finished, names the open loops, then gets out of the way. Never scheduled, never nagging.
|
||||
</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-3">Off by default. Always opt-in.</p>
|
||||
<div class="flex items-center justify-center gap-3 mt-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm border border-border text-text-secondary hover:bg-hover"
|
||||
onclick={() => answerEvening(false)}
|
||||
>No thanks</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm bg-accent text-white hover:bg-accent-hover"
|
||||
onclick={() => answerEvening(true)}
|
||||
>Yes, turn it on</button>
|
||||
</div>
|
||||
<button
|
||||
class="mt-5 text-xs text-text-tertiary hover:text-text-secondary underline"
|
||||
onclick={skipRituals}
|
||||
>Skip the rest</button>
|
||||
</div>
|
||||
|
||||
{:else if ritualsStep === "autostart"}
|
||||
<div class="w-full max-w-md mx-auto text-center">
|
||||
<Play size={32} strokeWidth={1.5} class="text-accent mx-auto mb-3" />
|
||||
<h2 class="text-xl font-medium text-text">Launch Corbie at login?</h2>
|
||||
<p class="text-sm text-text-secondary mt-3 leading-relaxed">
|
||||
So Corbie is already there when you need it — especially useful if you said yes to morning triage. Uses your OS's standard autostart. No background tricks, no telemetry.
|
||||
</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-3">You can change this any time in Settings.</p>
|
||||
<div class="flex items-center justify-center gap-3 mt-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm border border-border text-text-secondary hover:bg-hover"
|
||||
onclick={() => answerAutostart(false)}
|
||||
disabled={autostartApplying}
|
||||
>No thanks</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm bg-accent text-white hover:bg-accent-hover disabled:opacity-60"
|
||||
onclick={() => answerAutostart(true)}
|
||||
disabled={autostartApplying}
|
||||
>{autostartApplying ? 'Saving…' : 'Yes, launch at login'}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if ritualsStep === "done"}
|
||||
<div class="text-center">
|
||||
<CheckCircle size={40} strokeWidth={1.5} class="text-success mx-auto" />
|
||||
<h2 class="text-xl font-medium text-text mt-4">All set</h2>
|
||||
<p class="text-sm text-text-secondary mt-2">Press the button. Start talking.</p>
|
||||
</div>
|
||||
|
||||
{:else if downloading}
|
||||
|
||||
@@ -674,6 +674,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5 rituals. Autostart state mirrors the OS-level entry managed
|
||||
// by tauri-plugin-autostart; reading via invoke keeps the toggle
|
||||
// honest even if the user has edited their .desktop file manually.
|
||||
let autostartSyncing = $state(false);
|
||||
|
||||
async function setLaunchAtLogin(nextOn: boolean) {
|
||||
autostartSyncing = true;
|
||||
try {
|
||||
const plugin = await import("@tauri-apps/plugin-autostart");
|
||||
if (nextOn) {
|
||||
await plugin.enable();
|
||||
} else {
|
||||
await plugin.disable();
|
||||
}
|
||||
settings.launchAtLogin = nextOn;
|
||||
} catch (err) {
|
||||
toasts.warn("Could not update autostart", String(err));
|
||||
// Re-read to correct the UI if we failed halfway.
|
||||
try {
|
||||
const plugin = await import("@tauri-apps/plugin-autostart");
|
||||
settings.launchAtLogin = await plugin.isEnabled();
|
||||
} catch { /* best-effort */ }
|
||||
} finally {
|
||||
autostartSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function syncAutostartFromOs() {
|
||||
try {
|
||||
const plugin = await import("@tauri-apps/plugin-autostart");
|
||||
settings.launchAtLogin = await plugin.isEnabled();
|
||||
} catch { /* best-effort on browser / first load */ }
|
||||
}
|
||||
|
||||
function openWindDown() {
|
||||
page.current = "shutdown";
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await refreshRuntimeCapabilities();
|
||||
@@ -692,6 +730,10 @@
|
||||
// the user opens the Audio section.
|
||||
refreshAudioDevices();
|
||||
|
||||
// Phase 5: read the live OS-level autostart state so the toggle
|
||||
// reflects reality rather than last-saved intent.
|
||||
syncAutostartFromOs();
|
||||
|
||||
// Vocabulary is loaded reactively via the $effect that tracks the
|
||||
// active profile id (set once profilesStore.load() resolves in the
|
||||
// root layout). No eager fetch needed here.
|
||||
@@ -1396,6 +1438,100 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Rituals (Phase 5 roadmap) -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
class="flex items-center justify-between w-full py-4 px-5 text-left"
|
||||
onclick={() => openSection = openSection === 'rituals' ? null : 'rituals'}
|
||||
>
|
||||
<h3 class="font-display text-[18px] italic text-text">Rituals</h3>
|
||||
<span class="text-text-tertiary text-[16px] leading-none">{openSection === 'rituals' ? '−' : '+'}</span>
|
||||
</button>
|
||||
{#if openSection === 'rituals'}
|
||||
<div class="px-5 pb-5 animate-fade-in">
|
||||
<p class="text-[11px] text-text-tertiary mb-4">
|
||||
All off by default. Rituals only appear when you ask for them.
|
||||
</p>
|
||||
|
||||
<Toggle
|
||||
bind:checked={settings.ritualsMorning}
|
||||
label="Morning triage"
|
||||
description="On the first launch of the day after your set time, show a gentle pick-three modal drawn from open tasks. Skip any day without penalty."
|
||||
/>
|
||||
|
||||
{#if settings.ritualsMorning}
|
||||
<div class="pl-1 py-3 animate-fade-in">
|
||||
<label for="triage-time" class="block text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">
|
||||
Earliest triage time
|
||||
</label>
|
||||
<input
|
||||
id="triage-time"
|
||||
type="time"
|
||||
class="bg-bg-input border border-border rounded-lg px-3 py-1.5 text-[12px] text-text focus:border-accent focus:outline-none"
|
||||
bind:value={settings.ritualsMorningTime}
|
||||
/>
|
||||
<p class="text-[11px] text-text-tertiary mt-2">
|
||||
ADHD sleep inertia is intense for the first 30–45 minutes after waking. Pick a time when you're genuinely ready to decide.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Toggle
|
||||
bind:checked={settings.ritualsEvening}
|
||||
label="Evening wind-down"
|
||||
description="A reflective page you can open when you want to close the day. Not scheduled, never nagging."
|
||||
/>
|
||||
|
||||
{#if settings.ritualsEvening}
|
||||
<div class="pl-1 py-3 animate-fade-in">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 rounded-lg bg-bg-elevated border border-border text-[12px] text-text hover:border-accent"
|
||||
onclick={openWindDown}
|
||||
>
|
||||
Open wind-down now
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-border-subtle">
|
||||
<!-- One-way flow: click → OS call → state update. Can't use
|
||||
the standard Toggle because its bind:checked would
|
||||
race the autostart invoke and let the UI lie during
|
||||
the round-trip. -->
|
||||
<div class="flex items-start gap-3 py-2.5">
|
||||
<button
|
||||
class="relative mt-0.5 w-[38px] min-w-[38px] h-[22px] rounded-full flex-shrink-0
|
||||
{settings.launchAtLogin ? 'bg-accent shadow-[0_0_8px_rgba(232,168,124,0.25)]' : 'bg-bg-elevated'}
|
||||
active:scale-95 disabled:opacity-60"
|
||||
style="transition-duration: var(--duration-ui)"
|
||||
onclick={() => setLaunchAtLogin(!settings.launchAtLogin)}
|
||||
disabled={autostartSyncing}
|
||||
role="switch"
|
||||
aria-checked={settings.launchAtLogin}
|
||||
aria-label="Launch Corbie at login"
|
||||
>
|
||||
<span
|
||||
class="absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white shadow-sm
|
||||
{settings.launchAtLogin ? 'translate-x-[16px]' : 'translate-x-0'}"
|
||||
style="transition: transform var(--duration-ui) cubic-bezier(0.34, 1.56, 0.64, 1)"
|
||||
></span>
|
||||
</button>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[13px] text-text leading-tight">Launch Corbie at login</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-0.5 leading-snug">
|
||||
So Corbie is already there at the start of the day. Uses your OS's standard autostart — no background tricks.
|
||||
</p>
|
||||
{#if autostartSyncing}
|
||||
<p class="text-[11px] text-text-tertiary mt-1">Updating…</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Read aloud (Phase 4 roadmap) -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
|
||||
169
src/lib/pages/ShutdownRitualPage.svelte
Normal file
169
src/lib/pages/ShutdownRitualPage.svelte
Normal file
@@ -0,0 +1,169 @@
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
// Phase 5 evening wind-down. A reflective (not transactional) page
|
||||
// users trigger manually when they want to close the working day.
|
||||
//
|
||||
// Newport-style shutdown ritual: mechanical closure + physical reset +
|
||||
// intentional cue. Research on psychological detachment shows this
|
||||
// reduces evening rumination by ~40% (Simply Psychology / Cal Newport).
|
||||
// The Zeigarnik effect means unfinished tasks keep firing reminder
|
||||
// signals — naming them here, even without acting, is what silences
|
||||
// the loop.
|
||||
//
|
||||
// Copy rule: additive framing only ("You finished X"), never
|
||||
// subtractive ("X still open"). Open loops are listed but read-only —
|
||||
// transactional work belongs on the Tasks page.
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Moon, ArrowLeft } from 'lucide-svelte';
|
||||
import { page } from '$lib/stores/page.svelte.js';
|
||||
import { hasTauriRuntime } from '$lib/utils/runtime.js';
|
||||
|
||||
interface TaskRow {
|
||||
id: string;
|
||||
text: string;
|
||||
bucket: string;
|
||||
done: boolean;
|
||||
doneAt: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
let completedToday = $state<TaskRow[]>([]);
|
||||
let openLoops = $state<TaskRow[]>([]);
|
||||
let loading = $state(true);
|
||||
|
||||
function localDateKey(iso: string | null | undefined): string | null {
|
||||
if (!iso) return null;
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return null;
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function todayKey(): string {
|
||||
const d = new Date();
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!hasTauriRuntime()) {
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const all = await invoke<TaskRow[]>('list_tasks_cmd');
|
||||
const today = todayKey();
|
||||
completedToday = all.filter((t) => t.done && localDateKey(t.doneAt) === today);
|
||||
openLoops = all.filter((t) => !t.done);
|
||||
} catch {
|
||||
completedToday = [];
|
||||
openLoops = [];
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
page.current = 'dictation';
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full bg-bg overflow-y-auto">
|
||||
<div class="flex items-center gap-3 px-7 pt-6 pb-3">
|
||||
<button
|
||||
type="button"
|
||||
class="p-1.5 rounded-lg text-text-tertiary hover:text-text hover:bg-hover"
|
||||
onclick={close}
|
||||
aria-label="Back to dictation"
|
||||
>
|
||||
<ArrowLeft size={16} aria-hidden="true" />
|
||||
</button>
|
||||
<Moon size={18} class="text-text-secondary" aria-hidden="true" />
|
||||
<h1 class="font-display text-[26px] italic text-text">Wind down</h1>
|
||||
</div>
|
||||
|
||||
<div class="px-7 pb-8 max-w-[640px]">
|
||||
{#if loading}
|
||||
<p class="text-[12px] text-text-tertiary py-6">Looking at today…</p>
|
||||
{:else}
|
||||
<!-- Additive framing: lead with what the user did, never with what they didn't. -->
|
||||
<section class="mb-8">
|
||||
<p class="font-display text-[18px] italic text-text mb-2">
|
||||
{#if completedToday.length === 0}
|
||||
A quiet day.
|
||||
{:else if completedToday.length === 1}
|
||||
You finished one thing today.
|
||||
{:else}
|
||||
You finished {completedToday.length} today.
|
||||
{/if}
|
||||
</p>
|
||||
{#if completedToday.length > 0}
|
||||
<ul class="mt-3 flex flex-col gap-1.5">
|
||||
{#each completedToday as task (task.id)}
|
||||
<li class="text-[13px] text-text-secondary leading-relaxed">
|
||||
<span class="text-success mr-2" aria-hidden="true">✓</span>{task.text}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Read-only reflection. The Tasks page is where things get done. -->
|
||||
<section class="mb-8">
|
||||
<h2 class="text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">Open loops</h2>
|
||||
{#if openLoops.length === 0}
|
||||
<p class="text-[12px] text-text-secondary">Nothing in the list.</p>
|
||||
{:else}
|
||||
<p class="text-[11px] text-text-tertiary mb-3">
|
||||
These are still here. Naming them silences the loop — you don't have to act now.
|
||||
</p>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each openLoops.slice(0, 12) as task (task.id)}
|
||||
<li class="text-[12px] text-text-secondary truncate">
|
||||
{task.text}
|
||||
</li>
|
||||
{/each}
|
||||
{#if openLoops.length > 12}
|
||||
<li class="text-[11px] text-text-tertiary pt-1">
|
||||
…and {openLoops.length - 12} more.
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Physical reset + intentional cue. Evidence: Newport shutdown
|
||||
template, ~40% rumination reduction in psychological-detachment
|
||||
studies. -->
|
||||
<section class="mb-8">
|
||||
<h2 class="text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">Before you close</h2>
|
||||
<ol class="flex flex-col gap-2 text-[13px] text-text-secondary">
|
||||
<li>
|
||||
<span class="font-medium text-text">Take a breath.</span>
|
||||
Stand up. Stretch for ten seconds.
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-text">Pick a closing line.</span>
|
||||
Say it out loud when you're ready — something like "I'm done for today."
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-text">Let it rest.</span>
|
||||
Tomorrow's list will be here. Work done for today.
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<div class="pt-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 rounded-lg bg-accent text-white text-[12px] font-medium hover:bg-accent-hover"
|
||||
onclick={close}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user