From 73e5b01577aed782fd1303d40780e945b57129ce Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 30 Apr 2026 19:01:47 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20update=20PLAN.md=20=E2=80=94=20merge=20?= =?UTF-8?q?Today+Week=20view=20stages=20M7,=20add=20decisions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLAN.md | 76 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/PLAN.md b/PLAN.md index 631fc7a..015d0b5 100644 --- a/PLAN.md +++ b/PLAN.md @@ -35,7 +35,7 @@ internal/ service/ # business rules (close day/week, merge, overtime) migrations/ # SQL migration files web/ # SvelteKit app (embedded in binary at build time) -Makefile +mise.toml # task/tool runner (replaces Makefile) PLAN.md ``` @@ -104,6 +104,7 @@ CREATE TABLE sync_log ( POST /api/entries/start { note? } -> Entry POST /api/entries/{id}/stop -> Entry GET /api/entries?from=YYYY-MM-DD&to=YYYY-MM-DD +POST /api/entries { start_ms, end_ms, note? } -> Entry (completed interval) PUT /api/entries/{id} { start_time?, end_time?, note? } DELETE /api/entries/{id} (soft delete) @@ -137,8 +138,9 @@ GET /healthz (unauthenticated) - **Midnight enforcement**: `end_time` must be on the same calendar day as `start_time`. Server auto-stops any running entry at 23:59:59 local time via a background goroutine. Client warns the user. - **Close day**: requires all entries for that day to have `end_time != NULL` (409 otherwise). Computes `worked_ms = sum(end_time - start_time)` across non-deleted entries. Entries are preserved but the day is now locked. - **Mark day**: writes a `closed_days` row with `kind != 'work'`; `worked_ms` = expected daily ms (hours_per_week * 3600000 / popcount(workdays_mask)) for workdays, 0 for non-workdays. -- **Close week**: all workdays in the week must have a `closed_days` row. Sums `worked_ms`, snapshots `expected_ms` from settings effective at the week start, stores signed `delta_ms`. +- **Close week**: all workdays in the week must have a `closed_days` row. Sums `worked_ms`, snapshots `expected_ms` from settings effective at close time, stores signed `delta_ms`. - **Settings change**: new row in `settings_history` with `effective_from`. Past closed weeks are unaffected because they store a frozen snapshot. +- **Future intervals**: server rejects `CreateInterval` and `Update` if the resulting day key is in the future (400 Bad Request). ### Auth @@ -155,16 +157,40 @@ GET /healthz (unauthenticated) - TypeScript - `vite-plugin-pwa` + Workbox for service worker and manifest - `dexie` for typed IndexedDB access +- `vitest` for unit tests ### Screens | Screen | Purpose | |--------|---------| -| **Today** | Start/Stop button with live timer, list of today's entries, quick-mark actions | -| **Week** | 7-day grid: worked vs expected per day, running totals, "Close week" CTA | +| **Week** | Week picker, 7-day chip strip, day detail panel for selected day. Default: today selected (if in week) else Monday. Today tab in bottom nav is a shortcut that selects today. | | **History** | Month/week list of closed days and weeks with overtime indicators | | **Settings** | hours/week, workdays mask, timezone, effective-from, token setup | +The old dedicated **Today** route is merged into the Week view (see § Merged Week+Today View below). + +### Merged Week+Today View (`/week`) + +Three regions: + +1. **Week header** — week picker, week totals, Close Week button, closed-week banner. +2. **Day strip** — 7 chips (Mon–Sun), each showing: weekday label + date, mini progress bar (worked/expected), kind icon, closed badge, today highlight. Horizontal scroll-snap on mobile. Selected chip scrolls into view. `role="tablist"` with keyboard arrow navigation. +3. **Day detail panel** — for the selected day, capabilities gated per table: + +| Condition | canStartStop | canAddInterval | canEditEntries | canMarkKind | canCloseDay | +|---|---|---|---|---|---| +| Future | false | false | false | true | false | +| Today, open | true | true | true | true | true* | +| Today, closed | false | false | false | true | reopen | +| Past, open | false | true | true | true | true | +| Past, closed | false | false | false | true | reopen | + +*Close Day button disabled with tooltip while a running entry exists. + +**URL state**: `?week=2026-W18&day=2026-04-30`. Bare `/week` canonicalizes via `replaceState`. Chip clicks use `replaceState` (no history push). Week navigation (prev/next) uses `goto` (history push). Default day selection on week change: today if in week, else Monday. + +**Bottom nav**: "Today" tab is a shortcut link to `/week?week=&day=`. Active when route is `/week` and `?day === todayKey()`. + ### Offline Strategy 1. App shell precached by Workbox. @@ -181,11 +207,11 @@ GET /healthz (unauthenticated) ## 5. Build & Deployment -```makefile -# Single-binary production build -make build -# 1. pnpm build (inside web/) -# 2. go build -tags production ./cmd/wotra +```sh +# Single-binary production build (via mise) +mise run build +# 1. npm run build (inside web/) +# 2. go build -tags production -o wotra ./cmd/wotra # -> embeds web/build via go:embed ``` @@ -201,23 +227,24 @@ Environment variables: ## 6. Milestones -### M1 — Backend MVP -Repo scaffold, Go module, SQLite with migrations, entries CRUD, start/stop with same-day validation (midnight guard), auth middleware, service-layer tests. +### M1–M6 — Done +Full backend + frontend implemented, PWA offline sync, single-binary build, `mise.toml` task runner. -### M2 — Day Management -Settings with effective-from history, close day (running-entry guard, entry merging), holiday/vacation/sick marking. +### M7 — Merge Today+Week views ✅ in progress -### M3 — Week Close & Overtime -Close week, expected/worked/delta computation with frozen settings snapshot, reopen day/week. +Staged implementation: -### M4 — Svelte UI (online-only) -Today view, Week view, History view, Settings screen, all consuming the API directly. +| Stage | Goal | Status | +|---|---|---| +| 0 | Add Vitest setup | pending | +| 1 | Day strip on week view (read-only) | pending | +| 2 | Extract DayDetail component; render today in week view | pending | +| 3 | Day selection + URL-driven state | pending | +| 4 | Backend guard: reject future-day intervals | pending | +| 5 | Bottom nav Today shortcut; delete /today route | pending | -### M5 — PWA + Offline -Service worker, manifest, Dexie store, outbox pattern, `/api/sync/pull` and `/api/sync/push` endpoints, last-write-wins reconciliation. - -### M6 — Polish -CSV/JSON export, monthly summary view, single-binary build with embedded assets, Makefile, README. +### M8 — Future +CSV/JSON export, monthly summary view. ## 7. Decisions & Rationale @@ -230,3 +257,8 @@ CSV/JSON export, monthly summary view, single-binary build with embedded assets, | Deployment | Single Go binary embeds SPA | Simplest self-hosting story | | SQLite driver | modernc.org/sqlite (pure Go) | No CGO; easy cross-compilation | | Breaks in closed day | Not included in worked_ms | Sum of entry durations only; gaps between entries are breaks | +| Week close settings | Use close-time date, not week Monday | Settings changed mid-week apply to that week's close | +| weekDayKeys formula | `(weekday+6)%7` for days-since-Monday | Avoids sign bug when Jan 4 is Sunday (affects year 2026) | +| Merged Today+Week | Single `/week` route with day selection | Reduces duplication; week context always visible | +| Future intervals | Rejected by server (400) | Nonsensical to track time that hasn't happened | +| Test framework | Vitest (frontend) + Go testing (backend) | Automated coverage for capability logic and key utilities |