# Negotium: Fix and Harden — Design **Date:** 2026-08-15 **Status:** Approved, not yet implemented ## Context Negotium is a minimalist Svelte to-do app scoped to exactly two days, Today and Tomorrow. Tasks are stored client-side in `localStorage`, one key per date. It ships as a static nginx image published to GHCR. The app has not been updated in roughly ten months. An audit found that its headline feature — automatic carry-over of tasks across the day boundary — has never worked, alongside several correctness, accessibility, and payload problems. This design covers correctness and foundations only. No new user-facing features, no changes to the two-day model, and no visual redesign. ## Findings this design addresses ### Correctness 1. **Carry-over is dead code.** `checkAndMigrateTasks()` (`src/App.svelte:118`) guards on `currentDate !== today`. `currentDate` is initialized from `new Date().toDateString()` at `src/App.svelte:14` and compared against a fresh `new Date().toDateString()` milliseconds later in `onMount`. The condition is never true. "Tomorrow becomes Today" does work, but incidentally — tomorrow's tasks are written under tomorrow's date key, which is simply what gets read as today after midnight. What is genuinely broken is carry-over of *unfinished* work: those tasks remain under the previous day's key, are never displayed again, and are never cleaned up. 2. **No midnight rollover while open.** There is no timer or `visibilitychange` listener. A tab left open overnight keeps `selectedDate` pinned to the previous day, still labels it "Today" (`buttonText` at `src/App.svelte:153` only recomputes when `selectedDate` changes; the `new Date()` inside it is not a reactive dependency), and writes all subsequent edits into the previous day's key. 3. **Only one day back.** Even had the guard been correct, the logic inspects yesterday alone. A three-day absence would still drop work. 4. **Unbounded storage growth.** Every date key ever written persists indefinitely. 5. **Unguarded `JSON.parse`.** `src/App.svelte:108` will throw on any corrupt value and white-screen the app. `localStorage` access is also unguarded against Safari private mode and quota exhaustion. ### Accessibility 6. **Delete button invisible to keyboard users.** `.delete-btn` is `opacity: 0`, revealed only by `.task-item:hover` (`src/style.css:360`). The `:focus-visible` outline (`src/style.css:509`) renders on a zero-opacity element and is therefore also invisible. 7. **Invalid nested interactives.** The task row is `role="button"` with `tabindex="0"` (`src/App.svelte:336`) and contains two real `