Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions docs/stories/US-002-recurring-alarms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# User Story: Recurring (Daily-Repeat) Alarms

**Story ID:** US-002
**Epic:** Alarms
**Status:** In Progress

---

## 1. Description

**As a** visitor who keeps the clock site open at their desk,
**I want to** set an alarm that rings every day at the same time,
**So that** I don't have to re-create my daily alarms (break start, lunch, wrap-up) every morning.

---

## 2. Context & Scope

- **Source:** the MVP cut recorded in US-001 ("Out of Scope: daily-repeat
alarms — v2 story if users ask"). Owner prioritised this ahead of US-004
and deferred US-004 to individual follow-up work (2026-09-14).
- **In Scope:**
- A **Repeat daily** option on alarm creation (checkbox in the create form)
- A repeat alarm rings at its configured time **every day** while enabled,
including across page reloads — it is never consumed by ringing
- Dismissing or snoozing a repeat alarm never turns it off; the only way
to stop it is the enable/disable toggle
- A visible daily-repeat indicator on the alarm list rows
- One-shot alarms (US-001 behaviour) remain available and unchanged
- Legacy persisted alarms (created before this story) load unchanged as
one-shot alarms — no data loss, no manual migration
- **Out of Scope:**
- **Weekday scheduling** (e.g. "weekdays only", custom day-of-week sets) —
deliberately deferred. The core stores `repeat` as a simple daily flag
today; a future story can widen it to a day-set without a breaking
schema change (see §6)
- Interval-based recurrence (e.g. "every 2 hours"), calendar-date alarms
- Configurable snooze duration; custom sounds
- Ringing when the tab is closed; notifications; sync/backends

---

## 3. Acceptance Criteria

_These criteria define "Done." Every criterion must be verified by QA._

- [ ] **AC-1:** Given the create form, When the user checks "Repeat daily" and adds a 09:30 alarm, Then the alarm appears in the list enabled, marked with the daily-repeat indicator.
- [ ] **AC-2:** Given an enabled daily-repeat alarm for 09:30, When the clock reaches 09:30, Then the alarm rings, and afterwards it remains enabled — the next day at 09:30 it rings again.
- [ ] **AC-3:** Given a daily-repeat alarm that just rang, When the user dismisses it, Then the alarm stays enabled and rings again the following day (including across a page reload).
- [ ] **AC-4:** Given a daily-repeat alarm, When the user disables its toggle, Then it never rings again until re-enabled (toggling also clears any pending snooze).
- [ ] **AC-5:** Given an alarm created before this story (persisted without a repeat flag), When the page loads, Then it behaves exactly as a one-shot alarm (rings once, auto-disables).
- [ ] **AC-6:** Snooze works identically for daily-repeat alarms: the snoozed ring fires at the snooze time, and afterwards the alarm is still armed for the next day.
- [ ] **AC-7:** One-shot alarms created after this story keep US-001 semantics exactly: ring once, auto-disable.
- [ ] **AC-8:** While a daily-repeat alarm is ringing and a second one becomes due, the second stays armed and rings on a later tick (one overlay at a time, as in US-001).

---

## 4. Design Notes

- The `Alarm` record gains a persisted `repeat: boolean` flag (default
`false`). Persistence stays schema-additive: `parseAlarms` treats a
missing flag as `false`, so old saved JSON needs no migration step.
- The core's ring action branches: one-shot alarms set `enabled: false`
(US-001); repeat alarms only clear a pending snooze, so `todayAt()` re-arms
them naturally on the next day's tick window.
- The create form follows US-005's control language (label + checkbox on the
shared control scale); the row indicator is a monochrome inline SVG in
`currentColor`, matching the existing icon set.
- Pure-core changes live entirely in `alarm.core.ts`; the DOM layer only
reads/writes the new flag.

## 5. Verification Plan

- Unit tests mirrored under `tests/unit/features/alarm/`: ring-stays-armed,
next-day re-ring (tick with a next-day `Date`), dismiss/reload survival,
disable clears snooze, legacy-JSON default, snooze interplay, second-due
queuing.
- Full `make check` + `format:check`; Playwright screenshot pass of the
create form and list rows (before/after) using the existing `/tmp/ux-shots`
tooling.

## 6. Future Work

- **Weekday scheduling:** widen the persisted `repeat: boolean` to a day
set (e.g. `repeatDays: number[]` or a bitmask). Because this story's flag
is schema-additive and normalised at parse time, that change is backward
compatible: `repeat: true` can be read as "all seven days" by the future
implementation, and old one-shot records (flag absent) keep working.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 30 additions & 26 deletions memory-bank/activeContext.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
# Active Context

Updated: 2026-09-14
Updated: 2026-09-15

## Current focus

**HK-6 — implemented on `fix/hk6-asset-retention`** (off `main`): the
stale-HTML/dead-asset window after deploys is closed via previous-release
asset retention (ADR-0007). deploy.sh step 3b streams the old web
container's assets into `.prev-assets/` (tar contents-form — the `docker
cp src/. dst/` form NESTS, do not use); compose bind-mounts it ro at
`/usr/share/nginx/html-prev`; `web/nginx.conf` `try_files $uri
@prev_assets` fallback serves old hashes with immutable headers.

- Verified locally: `sh -n`, both `compose config -q`, `nginx -t` on both
configs, functional docker test (old hash 200 via fallback, unknown 404).
- Sandbox gotcha: this environment's docker bind mounts can show STALE
container views of host dirs (caused a long nesting-wild-goose-chase —
the data was polluted by an earlier root-owned `docker cp`, and the
container saw content the host didn't have). Trust host-side `find` over
container `ls` here.
- First deploy after merge runs the old in-memory deploy.sh; fallback is
fully populated from the second deploy onward (in ADR-0007).
**US-002 — merged (PR #18, rebase-merged into `main`).** Daily-repeat
alarms: persisted `repeat` + `lastRungDay` on `Alarm` (schema-additive —
legacy JSON normalizes to one-shot, no migration). Ring consumes the local
calendar day (`lastRungDay`), fixing a design hole found during planning:
without it, a dismiss mid-grace-window or a reload would re-ring a repeat
alarm. Toggling clears both markers (re-arms from the configured time).
UI: "Daily" checkbox in the create form, monochrome ↻ row indicator
(`role="img"` + aria-label). Weekday scheduling is recorded as future work
in the story (§6) — `repeat: true` reads as "all days" for that future
widening.

- Verified: 152/152 unit tests (12 new), `make check` green, 7/7 Playwright
assertions, 2 screenshots in the story folder.

## Repo cleanup & workflow change (2026-09-15)

- All merged feature branches and `dev` deleted (local + origin) once PR
#18 merged; only `main` remains.
- **`dev` is retired.** Since PR #14 practice has been feature branch →
`main` via PR (rebase-merge, linear history). Work now branches from
`main` and PRs into `main`; merging to `main` fires the automatic
release deploy (GHCR → webhook).

## Environment state

- Branch `fix/hk6-asset-retention` = main + 3 commits (code, ADRs, tasks).
`dev` has sprint-tracking commit `54edefa` that main lacks — this branch's
sprint.md rewrite reconciles them on merge.
- `make check` green (140 tests); pure cores untouched by HK-6.
- Single long-lived branch: `main`; no open PRs.
- Playwright tooling still in `/tmp/ux-shots/` (not a repo dependency).

## Next steps

- Owner: review + merge HK-6 PR → `main`; next deploy needs NO manual CF
purge from the second deploy on.
- Owner: verify the repeat alarm rings live after this deploy (`/healthz`,
then a cache-busted homepage).
- Owner: audible alarm-tone check (US-001) and visual pass on prod (US-005)
— both still open.
- Pick next epic: US-004 (needs PO story + likely ADR) vs US-002 (needs PO
story). HK-5 (nginx re-pin) is the remaining housekeeping item.
- Then: US-004 multi-clock epic, run individually per owner decision —
needs a PO story defining "a clock" + likely an ADR before starting.

38 changes: 19 additions & 19 deletions memory-bank/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,32 @@

## Status

US-001 (Multiple Alarms) is **deployed to production** (PR #13 merged
2026-09-13 21:17Z; Release run #5 succeeded; `healthz` verified 200).
US-005 (UX polish & a11y hardening) is **implemented on
`feat/us-005-ux-polish`** — all ACs verified, awaiting owner QA/review.
Site is live at `clock.taylormadetech.net`.
US-002 (recurring alarms) is **merged and deployed** (PR #18, rebase-merged
into `main` on 2026-09-15; merging fires the automatic release deploy).
Repo cleanup, same day: merged feature branches and `dev` deleted (local +
origin); `dev` is retired — work now branches from `main` and PRs into
`main` (rebase-merge, linear history). Site is live at
`clock.taylormadetech.net`. 152 unit tests green.

## What works

- Clock (tick, title mirror, timezone), timer (presets, persistence, beep),
alarms (multi, snooze, persistence, ring overlay) — 140 unit tests green.
alarms (multi, snooze, persistence, ring overlay, **daily-repeat
(US-002)**).
- **Design system (US-005):** semantic text-tier tokens (secondary 7.0:1,
muted 5.2:1, placeholder 4.7:1 — never raw opacity for text), AA-safe
danger `#ff6b6b`, global `:focus-visible` ring, monochrome inline-SVG
icons, shared FLIP helper (`shared/dom/flip.ts`, reduced-motion aware),
focus management + `aria-modal` ring overlay with Tab trap,
`prefers-reduced-motion` global kill switch, `panelHadFocus` focus-restore
pattern in both UI features.
- **Deploy resilience (HK-6, ADR-0007):** previous-release assets retained
on origin (`.prev-assets/` bind mount + `@prev_assets` fallback), closing
the stale-HTML/dead-asset window; no manual CF purges needed from the
second post-merge deploy on.
- Full CI gate: prettier, eslint, typecheck, vitest+coverage, build,
`nginx -t` × 2, compose config × 2, hadolint × 4.
`nginx -t` × 2, compose config × 2, hadolint × 4; on modern action
majors (HK-2).
- Automated deploys: `main` → GHCR → webhook → swap with auto-rollback.
- Cloudflare edge caching of `index.html` (2 h Cache Rule; `/healthz`
uncacheable) per ADR 0006.
Expand All @@ -29,22 +36,15 @@ Site is live at `clock.taylormadetech.net`.

- Production audio QA: alarm tone not yet verified by a human (jsdom can't
test audio) — last open US-001 DoD item.
- **Stale-HTML → dead-asset window after every deploy** (HK-6): a rebuild can
change asset hashes; the 2 h edge-cached homepage then references assets
that 404 on origin, and the 404 gets edge-cached too (observed 2026-09-13).
Fix candidates: CF API purge post-deploy, retain old assets in `deploy.sh`,
or shorter HTML TTL.
- US-005 visual sign-off on prod still pending (owner eyeball).
- Edge-cached homepage can lag up to 2 h behind a deploy (expected; judge
freshness via `/healthz` or a query-string cache-bust, which works).
- CI actions pinned to Node-20-runtime majors (HK-2 — fix in progress).
- `nginxinc/nginx-unprivileged:1.30-alpine` is an EOL mainline branch
(HK-5 candidate: re-pin to current stable + digest).
- npm minor drift: eslint/prettier/lint-staged/TS patch bumps available;
vite 7+/vitest 5 majors deliberately deferred (not housekeeping).
- Multi-clock scaffold (`clock-registry.ts`) unwired (backlog US-004).
- FLIP helper duplicated in timer/alarm UI (backlog US-003).
- Multi-clock scaffold (`clock-registry.ts`) unwired — **US-004, owner will
run it individually**; needs a PO story defining "a clock" + likely an ADR.

## Next epic decision (owner)

US-002 recurring alarms vs US-004 multi-clock (US-004 wants US-003 item 1
first). See `tasks/backlog.md` + `tasks/sprint.md`.
US-004 multi-clock (deferred, owner-scheduled). See `tasks/backlog.md` +
`tasks/sprint.md`.
29 changes: 3 additions & 26 deletions tasks/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,16 @@ Prioritized candidate work. Items here are NOT committed to a sprint until moved
to `tasks/sprint.md`. New items enter via the Product Owner (user story) and are
sized/sequenced by the SDM.

## Epic: Alarms

### US-002 — Recurring (daily-repeat) alarms

- Status: Candidate — needs a user story from the Product Owner before work.
- Source: MVP cut recorded in US-001 ("Out of Scope": daily-repeat alarms).
- Priority: **Deprioritized behind US-005** (owner decision 2026-09-13) —
still the most requested natural follow-up to US-001.
- Dependencies: none (builds directly on the US-001 alarm core).

## Epic: Multi-clock

### US-004 — Wire up the multi-clock registry

- Status: Candidate — `clock-registry.ts` scaffold exists but is not wired.
- Status: Candidate — **owner will schedule and run this individually**;
deliberately deferred from the 2026-09-14 cleanup sprint as too heavy to
batch.
- Source: README ("Multi-clock scaffold — not wired up yet"); recommended as
a major epic after the Alarms epic.
- Dependencies: the shared FLIP helper (formerly US-003 item 1) is DONE via
US-005 (`shared/dom/flip.ts`) — the sequencing blocker is cleared.
- Note: likely requires an ADR if the page layout/composition root changes
meaningfully, plus a Product Owner story defining what a "clock" is.

## Housekeeping (backlog, non-story)

### HK-5 — Re-pin the nginx base image off the EOL 1.27 branch

- Status: Candidate (found 2026-09-13 during the repo date/staleness audit).
- Finding: `nginxinc/nginx-unprivileged:1.27-alpine` was last built
2025-06-23; the 1.27 mainline stopped shipping when 1.29 released, so the
edge and prod images run an unmaintained branch. The digest pin itself is
current for the tag — the staleness is at the version level.
- Requires: choose the current nginx stable branch, bump tag + digest
together (ADR 0005) in `web/Dockerfile.prod` and `nginx/Dockerfile`, update
the image tag in `.github/workflows/ci.yml` (nginx-config-check), and run
`nginx -t` locally on both configs.
- Priority: Medium — security updates are accruing on the EOL branch.
Loading