summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-21 00:07:03 +0900
committerAdam Malczewski <[email protected]>2026-05-21 00:07:03 +0900
commit41f681d23491f0ba090afae053f0232743703619 (patch)
treeaf2fdbf8cd0f9d6de9784f1ffb74c2f0522e9d28 /packages/frontend/src/lib
parent1f4776e6891348d2dbdcbbf704c0a5901b008ecf (diff)
downloaddispatch-41f681d23491f0ba090afae053f0232743703619.tar.gz
dispatch-41f681d23491f0ba090afae053f0232743703619.zip
fix: backend wake scheduler with atomic toggle API, American time display
- Replaced POST /wake-schedule (full-replace) with POST /wake-schedule/toggle (atomic single-hour toggle) to eliminate race conditions between frontend and scheduler - Recursive setTimeout prevents overlapping wake executions - HMR-safe via global timer reference - Frontend now uses toggle endpoint instead of full schedule POST - Display shows reset time (wake hour + 5h) in American 12h format e.g. '8:15 → Reset at 1:00 PM' instead of European 24h
Diffstat (limited to 'packages/frontend/src/lib')
-rw-r--r--packages/frontend/src/lib/components/ClaudeReset.svelte143
1 files changed, 57 insertions, 86 deletions
diff --git a/packages/frontend/src/lib/components/ClaudeReset.svelte b/packages/frontend/src/lib/components/ClaudeReset.svelte
index 58150fe..bcefbcf 100644
--- a/packages/frontend/src/lib/components/ClaudeReset.svelte
+++ b/packages/frontend/src/lib/components/ClaudeReset.svelte
@@ -4,119 +4,84 @@
// Map of hour (0-23) → scheduled wake timestamp (ms)
let schedule = $state<Record<number, number>>({});
- // Active timeout IDs keyed by hour
- const timeoutIds: Record<number, ReturnType<typeof setTimeout>> = {};
-
function formatHour(h: number): string {
const display = h % 12;
return display === 0 ? "12" : String(display);
}
- function loadSchedule(): Record<number, number> {
- try {
- const raw = localStorage.getItem("claude-reset-schedule");
- if (!raw) return {};
- return JSON.parse(raw) as Record<number, number>;
- } catch {
- return {};
- }
- }
-
- function saveSchedule(s: Record<number, number>): void {
- try {
- localStorage.setItem("claude-reset-schedule", JSON.stringify(s));
- } catch {
- // localStorage unavailable — ignore
+ function nextOccurrenceAt15(hour: number): number {
+ const now = new Date();
+ const target = new Date(now);
+ target.setHours(hour, 15, 0, 0);
+ if (target.getTime() <= Date.now()) {
+ target.setDate(target.getDate() + 1);
}
+ return target.getTime();
}
- async function triggerWake(hour: number): Promise<void> {
+ async function loadFromServer(): Promise<void> {
try {
- await fetch(`${apiBase}/models/wake`, { method: "POST" });
+ const res = await fetch(`${apiBase}/models/wake-schedule`);
+ if (!res.ok) return;
+ const data = (await res.json()) as { schedule: Record<string, number> };
+ const parsed: Record<number, number> = {};
+ for (const [k, v] of Object.entries(data.schedule)) {
+ parsed[Number(k)] = v;
+ }
+ schedule = parsed;
} catch {
- // Ignore network errors
+ // Network error — leave schedule empty
}
- // Remove this hour from the schedule
- const updated = { ...schedule };
- delete updated[hour];
- schedule = updated;
- saveSchedule(schedule);
}
- function scheduleTimeout(hour: number, ts: number): void {
- const delay = ts - Date.now();
- if (delay <= 0) {
- // Already past — fire immediately
- void triggerWake(hour);
- return;
+ async function parseScheduleResponse(res: Response): Promise<void> {
+ const data = (await res.json()) as { schedule: Record<string, number> };
+ const parsed: Record<number, number> = {};
+ for (const [k, v] of Object.entries(data.schedule)) {
+ parsed[Number(k)] = v;
}
- const id = setTimeout(() => {
- void triggerWake(hour);
- }, delay);
- timeoutIds[hour] = id;
+ schedule = parsed;
}
- function clearHourTimeout(hour: number): void {
- if (timeoutIds[hour] !== undefined) {
- clearTimeout(timeoutIds[hour]);
- delete timeoutIds[hour];
+ async function toggleOnServer(hour: number, ts: number): Promise<void> {
+ try {
+ const res = await fetch(`${apiBase}/models/wake-schedule/toggle`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ hour, timestamp: ts }),
+ });
+ if (!res.ok) return;
+ await parseScheduleResponse(res);
+ } catch {
+ // Network error — keep local state
}
}
- function nextOccurrenceAt15(hour: number): number {
- const now = new Date();
- const target = new Date(now);
- target.setHours(hour, 15, 0, 0);
- if (target.getTime() <= Date.now()) {
- target.setDate(target.getDate() + 1);
+ async function removeFromServer(hour: number): Promise<void> {
+ try {
+ const res = await fetch(`${apiBase}/models/wake-schedule/toggle`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ hour }),
+ });
+ if (!res.ok) return;
+ await parseScheduleResponse(res);
+ } catch {
+ // Network error — keep local state
}
- return target.getTime();
}
function toggleHour(hour: number): void {
if (schedule[hour] !== undefined) {
- // Deschedule
- clearHourTimeout(hour);
- const updated = { ...schedule };
- delete updated[hour];
- schedule = updated;
- saveSchedule(schedule);
+ void removeFromServer(hour);
} else {
- // Schedule
const ts = nextOccurrenceAt15(hour);
- schedule = { ...schedule, [hour]: ts };
- saveSchedule(schedule);
- scheduleTimeout(hour, ts);
+ void toggleOnServer(hour, ts);
}
}
$effect(() => {
- // Load persisted schedule on mount
- const loaded = loadSchedule();
- const now = Date.now();
- const cleaned: Record<number, number> = {};
-
- for (const [k, ts] of Object.entries(loaded)) {
- const hour = Number(k);
- if (ts >= now) {
- cleaned[hour] = ts;
- }
- }
-
- schedule = cleaned;
- saveSchedule(cleaned);
-
- // Register timeouts for all future entries
- for (const [k, ts] of Object.entries(cleaned)) {
- scheduleTimeout(Number(k), ts);
- }
-
- // Cleanup on destroy
- return () => {
- for (const id of Object.values(timeoutIds)) {
- clearTimeout(id);
- }
- };
+ void loadFromServer();
});
// Compute "faded" hours: the 4 hours after each scheduled block
@@ -161,8 +126,14 @@
return base;
}
- function formatWakeTime(ts: number): string {
- return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
+ function formatAmPm(hour24: number): string {
+ const h = hour24 % 12;
+ const ampm = hour24 < 12 ? "AM" : "PM";
+ return `${h === 0 ? "12" : String(h)}:00 ${ampm}`;
+ }
+
+ function resetHour(wakeHour: number): number {
+ return (wakeHour + 5) % 24;
}
const scheduledHours = $derived(
@@ -228,7 +199,7 @@
{#each scheduledHours as hour}
<div class="flex items-center gap-1.5 text-xs text-base-content/70">
<span class="badge badge-xs badge-primary">{formatHour(hour)}:15</span>
- <span>Wake scheduled for {formatWakeTime(schedule[hour] ?? 0)}</span>
+ <span>Reset at {formatAmPm(resetHour(hour))}</span>
</div>
{/each}
</div>