From 5f9cbbf58701ab04ad80e73d26c7c9cadd4c41bf Mon Sep 17 00:00:00 2001 From: marcosgilf Date: Tue, 11 Aug 2026 14:57:05 +0200 Subject: [PATCH 1/2] chore: migrate open work to GitHub issues --- .github/workflows/ci.yml | 109 +++++++++++-- .github/workflows/deploy-dev.yml | 43 ------ .github/workflows/deploy-prod.yml | 30 ---- .github/workflows/deploy-production.yml | 95 +++++++++++- .github/workflows/qa.yml | 76 ---------- AGENTS.md | 22 --- CLAUDE.md | 1 - CONTRIBUTING.md | 5 +- README.md | 4 +- docs/architecture.md | 9 +- docs/spec.md | 116 ++++++-------- issues/0-workout-timer-pwa-spec.md | 143 ------------------ issues/1-interval-sequence-model.md | 96 ------------ issues/10-ios-background-audio.md | 81 ---------- issues/11-settings-ui-home.md | 61 -------- issues/12-ci-workflow.md | 70 --------- issues/13-netlify-site-and-domain.md | 57 ------- issues/14-countdown-end-to-end.md | 50 ------ issues/15-tabata-rounds.md | 43 ------ issues/16-cues.md | 46 ------ issues/17-persistence.md | 49 ------ issues/18-settings-screen.md | 45 ------ issues/19-pwa-install-offline.md | 48 ------ ...clock-accuracy-and-background-behaviour.md | 80 ---------- issues/20-wake-lock-audio-session.md | 47 ------ issues/21-e2e-safety-net.md | 42 ----- issues/22-remaining-modes.md | 44 ------ issues/23-chrono-mvp.md | 90 ----------- issues/24-pwa.md | 58 ------- issues/25-countdown-with-config.md | 33 ---- issues/26-rounds.md | 33 ---- issues/3-audio-and-haptic-cue-model.md | 78 ---------- ...4-pwa-offline-install-wakelock-research.md | 100 ------------ issues/5-toolchain-baseline.md | 66 -------- issues/6-screen-layout-and-config-ui.md | 77 ---------- issues/7-persistence-model.md | 83 ---------- issues/8-testing-strategy.md | 75 --------- issues/9-assemble-handoff-spec.md | 45 ------ issues/README.md | 32 ---- scripts/generate-icons.py | 83 ---------- src/domain/clock.test.ts | 2 +- src/domain/clock.ts | 2 +- src/layouts/AppLayout.astro | 6 +- src/pages/index.astro | 2 +- 44 files changed, 250 insertions(+), 2127 deletions(-) delete mode 100644 .github/workflows/deploy-dev.yml delete mode 100644 .github/workflows/deploy-prod.yml delete mode 100644 .github/workflows/qa.yml delete mode 100644 AGENTS.md delete mode 120000 CLAUDE.md delete mode 100644 issues/0-workout-timer-pwa-spec.md delete mode 100644 issues/1-interval-sequence-model.md delete mode 100644 issues/10-ios-background-audio.md delete mode 100644 issues/11-settings-ui-home.md delete mode 100644 issues/12-ci-workflow.md delete mode 100644 issues/13-netlify-site-and-domain.md delete mode 100644 issues/14-countdown-end-to-end.md delete mode 100644 issues/15-tabata-rounds.md delete mode 100644 issues/16-cues.md delete mode 100644 issues/17-persistence.md delete mode 100644 issues/18-settings-screen.md delete mode 100644 issues/19-pwa-install-offline.md delete mode 100644 issues/2-clock-accuracy-and-background-behaviour.md delete mode 100644 issues/20-wake-lock-audio-session.md delete mode 100644 issues/21-e2e-safety-net.md delete mode 100644 issues/22-remaining-modes.md delete mode 100644 issues/23-chrono-mvp.md delete mode 100644 issues/24-pwa.md delete mode 100644 issues/25-countdown-with-config.md delete mode 100644 issues/26-rounds.md delete mode 100644 issues/3-audio-and-haptic-cue-model.md delete mode 100644 issues/4-pwa-offline-install-wakelock-research.md delete mode 100644 issues/5-toolchain-baseline.md delete mode 100644 issues/6-screen-layout-and-config-ui.md delete mode 100644 issues/7-persistence-model.md delete mode 100644 issues/8-testing-strategy.md delete mode 100644 issues/9-assemble-handoff-spec.md delete mode 100644 issues/README.md delete mode 100644 scripts/generate-icons.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad70745..3e92895 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,5 @@ name: CI -# Thin orchestrator: owns the triggers and the order, nothing else. -# The work lives in qa.yml / deploy-dev.yml / deploy-prod.yml, called as reusable workflows so -# `needs:` still gates them and the pull-request context (PR number, comments) is preserved. - on: pull_request: branches: @@ -21,13 +17,108 @@ concurrency: jobs: qa: - uses: ./.github/workflows/qa.yml + name: qa + runs-on: ubuntu-latest + env: + PUBLIC_DEPLOY_CONTEXT: ${{ github.event_name == 'pull_request' && 'preview' || 'production' }} + PUBLIC_PR_NUMBER: ${{ github.event.pull_request.number || '' }} + PUBLIC_COMMIT_SHA: ${{ github.sha }} + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Types, lint and format + run: pnpm check + + - name: Unit tests + shell: bash + run: | + set -o pipefail + NO_COLOR=1 pnpm test:coverage | tee coverage-output.txt + { + echo "## Coverage" + echo + echo '```text' + awk ' + /^ % Coverage report/ { printing=1; next } + /^=============================== Coverage summary/ { exit } + printing { print } + ' coverage-output.txt + echo '```' + echo + echo '```text' + awk '/^=============================== Coverage summary/ { printing=1 } printing { print }' coverage-output.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Dependency patch drift + run: pnpm check:dependencies - deploy-dev: + - name: Security audit + run: pnpm check:audit + + - name: Upload coverage + uses: actions/upload-artifact@v7 + with: + name: coverage + path: coverage + if-no-files-found: ignore + + - name: Build + run: pnpm build + + - name: Upload build + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist + + deploy: + name: deploy-dev needs: qa - if: github.event_name == 'pull_request' - uses: ./.github/workflows/deploy-dev.yml + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest permissions: contents: read pull-requests: write - secrets: inherit + steps: + - name: Download build + uses: actions/download-artifact@v8 + with: + name: dist + path: dist + + - name: Deploy preview to Netlify + id: netlify + run: | + url=$(npx netlify-cli deploy \ + --dir=dist \ + --no-build \ + --alias="pr-${{ github.event.number }}" \ + --site="$NETLIFY_SITE_ID" \ + --auth="$NETLIFY_AUTH_TOKEN" \ + --json | jq -r '.deploy_url') + echo "url=$url" >> "$GITHUB_OUTPUT" + echo "Preview: $url" >> "$GITHUB_STEP_SUMMARY" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ vars.NETLIFY_SITE_ID }} + + - name: Comment the preview URL + uses: marocchino/sticky-pull-request-comment@v3 + with: + header: netlify-preview + message: | + ๐Ÿš€ Preview deploy: ${{ steps.netlify.outputs.url }} diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml deleted file mode 100644 index b82ce45..0000000 --- a/.github/workflows/deploy-dev.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Deploy dev - -on: - workflow_call: - -permissions: - contents: read - pull-requests: write - -jobs: - deploy-dev: - # Forks cannot read the Netlify credentials, so there is nothing to deploy. - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - steps: - - name: Download build - uses: actions/download-artifact@v8 - with: - name: dist - path: dist - - - name: Deploy preview to Netlify - id: netlify - run: | - url=$(npx netlify-cli deploy \ - --dir=dist \ - --no-build \ - --alias="pr-${{ github.event.number }}" \ - --site="$NETLIFY_SITE_ID" \ - --auth="$NETLIFY_AUTH_TOKEN" \ - --json | jq -r '.deploy_url') - echo "url=$url" >> "$GITHUB_OUTPUT" - echo "Preview: $url" >> "$GITHUB_STEP_SUMMARY" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ vars.NETLIFY_SITE_ID }} - - - name: Comment the preview URL - uses: marocchino/sticky-pull-request-comment@v3 - with: - header: netlify-preview - message: | - ๐Ÿš€ Preview deploy: ${{ steps.netlify.outputs.url }} diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml deleted file mode 100644 index 4af41e9..0000000 --- a/.github/workflows/deploy-prod.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Deploy prod - -on: - workflow_call: - -permissions: - contents: read - -jobs: - deploy-prod: - runs-on: ubuntu-latest - steps: - - name: Download build - uses: actions/download-artifact@v8 - with: - name: dist - path: dist - - - name: Deploy to Netlify - run: | - npx netlify-cli deploy \ - --dir=dist \ - --no-build \ - --prod \ - --site="$NETLIFY_SITE_ID" \ - --auth="$NETLIFY_AUTH_TOKEN" - echo "Production: https://timer.marcosgilf.com" >> "$GITHUB_STEP_SUMMARY" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ vars.NETLIFY_SITE_ID }} diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index ca1c963..ef61d2f 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -9,14 +9,99 @@ permissions: contents: read concurrency: - group: deploy-production + group: deploy cancel-in-progress: false jobs: qa: - uses: ./.github/workflows/qa.yml + name: qa + runs-on: ubuntu-latest + env: + PUBLIC_DEPLOY_CONTEXT: production + PUBLIC_PR_NUMBER: "" + PUBLIC_COMMIT_SHA: ${{ github.sha }} + steps: + - name: Checkout + uses: actions/checkout@v7 - deploy-prod: + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Types, lint and format + run: pnpm check + + - name: Unit tests + shell: bash + run: | + set -o pipefail + NO_COLOR=1 pnpm test:coverage | tee coverage-output.txt + { + echo "## Coverage" + echo + echo '```text' + awk ' + /^ % Coverage report/ { printing=1; next } + /^=============================== Coverage summary/ { exit } + printing { print } + ' coverage-output.txt + echo '```' + echo + echo '```text' + awk '/^=============================== Coverage summary/ { printing=1 } printing { print }' coverage-output.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Dependency patch drift + run: pnpm check:dependencies + + - name: Security audit + run: pnpm check:audit + + - name: Upload coverage + uses: actions/upload-artifact@v7 + with: + name: coverage + path: coverage + if-no-files-found: ignore + + - name: Build + run: pnpm build + + - name: Upload build + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist + + deploy: + name: deploy-prod needs: qa - uses: ./.github/workflows/deploy-prod.yml - secrets: inherit + runs-on: ubuntu-latest + steps: + - name: Download build + uses: actions/download-artifact@v8 + with: + name: dist + path: dist + + - name: Deploy to Netlify + run: | + npx netlify-cli deploy \ + --dir=dist \ + --no-build \ + --prod \ + --site="$NETLIFY_SITE_ID" \ + --auth="$NETLIFY_AUTH_TOKEN" + echo "Production: https://timer.marcosgilf.com" >> "$GITHUB_STEP_SUMMARY" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ vars.NETLIFY_SITE_ID }} diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml deleted file mode 100644 index 0a7d3b7..0000000 --- a/.github/workflows/qa.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: QA - -on: - workflow_call: - -permissions: - contents: read - -jobs: - qa: - runs-on: ubuntu-latest - env: - PUBLIC_DEPLOY_CONTEXT: ${{ github.event_name == 'pull_request' && 'preview' || 'production' }} - PUBLIC_PR_NUMBER: ${{ github.event.pull_request.number || '' }} - PUBLIC_COMMIT_SHA: ${{ github.sha }} - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Setup pnpm - uses: pnpm/action-setup@v6 - - - name: Setup Node - uses: actions/setup-node@v7 - with: - node-version-file: .nvmrc - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Types, lint and format - run: pnpm check - - - name: Unit tests - shell: bash - run: | - set -o pipefail - NO_COLOR=1 pnpm test:coverage | tee coverage-output.txt - { - echo "## Coverage" - echo - echo '```text' - awk ' - /^ % Coverage report/ { printing=1; next } - /^=============================== Coverage summary/ { exit } - printing { print } - ' coverage-output.txt - echo '```' - echo - echo '```text' - awk '/^=============================== Coverage summary/ { printing=1 } printing { print }' coverage-output.txt - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - - - name: Dependency patch drift - run: pnpm check:dependencies - - - name: Security audit - run: pnpm check:audit - - - name: Upload coverage - uses: actions/upload-artifact@v7 - with: - name: coverage - path: coverage - if-no-files-found: ignore - - - name: Build - run: pnpm build - - - name: Upload build - uses: actions/upload-artifact@v7 - with: - name: dist - path: dist diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index c5ff429..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,22 +0,0 @@ -## Development - -When starting the dev server, use background mode: - -``` -astro dev --background -``` - -Manage the background server with `astro dev stop`, `astro dev status`, and `astro dev logs`. - -## Documentation - -Full documentation: https://docs.astro.build - -Consult these guides before working on related tasks: - -- [Adding pages, dynamic routes, or middleware](https://docs.astro.build/en/guides/routing/) -- [Working with Astro components](https://docs.astro.build/en/basics/astro-components/) -- [Using React, Vue, Svelte, or other framework components](https://docs.astro.build/en/guides/framework-components/) -- [Adding or managing content](https://docs.astro.build/en/guides/content-collections/) -- [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/) -- [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/) diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30580ed..ed5348a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,10 +33,7 @@ Both run automatically โ€” `pre-commit` formats and type-checks staged files, `p ## How this project is planned -Decisions live in [`issues/`](./issues), a local markdown issue tracker, and are folded into -[`docs/spec.md`](./docs/spec.md). [`CONTEXT.md`](./CONTEXT.md) is the glossary โ€” use its vocabulary in -code and in pull requests. If a change contradicts a decision recorded in `issues/`, say so in the -pull request rather than quietly diverging. +Decisions live in [`docs/spec.md`](./docs/spec.md); open work and discussion live in [GitHub Issues](https://github.com/marcosgilf/timer/issues). [`CONTEXT.md`](./CONTEXT.md) is the glossary โ€” use its vocabulary in code and in pull requests. If a change contradicts the spec, say so in the pull request rather than quietly diverging. ## Pull requests diff --git a/README.md b/README.md index 6125446..273a438 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,7 @@ registry/advisory state and can block unrelated local work. ## How this project is planned -Decisions are made one at a time as tickets in [`issues/`](./issues) โ€” a local markdown issue tracker -using the [wayfinder](https://github.com/mattpocock/skills) method โ€” and folded into -[`docs/spec.md`](./docs/spec.md), with the code structure in [`docs/architecture.md`](./docs/architecture.md). [`CONTEXT.md`](./CONTEXT.md) is the glossary. +Decisions live in [`docs/spec.md`](./docs/spec.md), with code structure in [`docs/architecture.md`](./docs/architecture.md). [`CONTEXT.md`](./CONTEXT.md) is the glossary. Future work lives in [GitHub Issues](https://github.com/marcosgilf/timer/issues): [countdown](https://github.com/marcosgilf/timer/issues/10) โ†’ [rounds](https://github.com/marcosgilf/timer/issues/11). Two branches are kept as primary sources and are deliberately not merged: diff --git a/docs/architecture.md b/docs/architecture.md index 6913242..8b17357 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,7 +41,7 @@ Applied here: The lean principle applied to the code you are tempted to write *right now*. Build for the behaviour in front of you, not the one you can imagine. -- A ticket ships **only what its own acceptance criteria demand**. If Countdown is the slice, Home +- A slice ships **only what its own acceptance criteria demand**. If Countdown is the slice, Home lists Countdown โ€” not six buttons where five do nothing. Placeholders are inventory: they look like progress, they need maintaining, and they lie to the user about what works. - **No abstraction until the second case exists.** An interface with one implementation, a factory @@ -50,8 +50,8 @@ in front of you, not the one you can imagine. - **No dependency for what a few lines do.** Reach for the platform first: ``, form controls, CSS, `localStorage`, `AudioContext`. Every dependency is bytes to precache, a supply chain, and an upgrade you will owe. -- **No code for a future requirement.** If it is genuinely coming it will be a ticket; write it then, - when the requirement is real and its shape is known. +- **No code for a future requirement.** If it is genuinely coming, write it when the requirement is + real and its shape is known. - Deliberate shortcuts that cut a real corner get a `ponytail:` comment naming the ceiling and the upgrade path, so the trade-off is visible instead of forgotten. @@ -130,7 +130,6 @@ src/ โ”œโ”€โ”€ pages/ routes, and the screen composition for each โ””โ”€โ”€ styles/ design tokens and global CSS docs/ spec, architecture -issues/ decision tickets (wayfinder) and build tickets ``` **`infra/` is reserved for deployment infrastructure as code** (Terraform, Netlify/DNS config) at the @@ -166,5 +165,5 @@ Rules that keep the boundaries honest: - **No state-management library** โ€” state is three numbers (`Clock`) plus a `Routine`; the domain derives the rest. - **No component framework** โ€” plain Astro and DOM. React arrives only if a slice proves it - mandatory, and that proof goes in a ticket. + mandatory, and that proof goes in the spec. - **No design system beyond Pico** โ€” classless semantic CSS covers everything except the digits. diff --git a/docs/spec.md b/docs/spec.md index b36f5e2..cef2121 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,12 +1,10 @@ # Routine timer PWA โ€” implementation spec -Handoff document for the map [Routine timer PWA โ€” spec](../issues/0-workout-timer-pwa-spec.md). -Everything an implementer needs to build v1 is here; the tickets are the audit trail, not required -reading. Terms are used exactly as defined in [CONTEXT.md](../CONTEXT.md) โ€” "interval" and "timer" +Everything an implementer needs to build v1 is here. Terms are used exactly as defined in [CONTEXT.md](../CONTEXT.md) โ€” "interval" and "timer" are banned as domain nouns. **The standing rule** (map Notes, restated by -[iOS background audio](../issues/10-ios-background-audio.md)): the app must be **fully correct with +iOS background audio): the app must be **fully correct with zero platform extras**. Every capability โ€” audio session, Wake Lock, vibration, install, offline โ€” is feature-detected and a no-op when absent. No polyfills, no UA sniffing, no keepalive hacks. @@ -14,11 +12,11 @@ Assets that are not on `main`, and must not be merged: - **Prototype**: branch `prototype/timer-ui`, `src/pages/prototype.astro`, run with `pnpm prototype` โ€” the visual source of truth for the Running screen - ([Screen layout and config UI](../issues/6-screen-layout-and-config-ui.md)). + (Screen layout and config UI). - **Research**: branch `research/pwa-platform`, `research/pwa-platform.md` (commit `3144d27`), read with `git show research/pwa-platform:research/pwa-platform.md` โ€” citations and version numbers behind the platform facts - ([PWA offline, install and Wake Lock](../issues/4-pwa-offline-install-wakelock-research.md)). + (PWA offline, install and Wake Lock). --- @@ -58,7 +56,7 @@ countdown. Each stays specified below and returns as its own slice. ## 1. Domain model -Decided by [Interval sequence model โ€” one engine for six modes](../issues/1-interval-sequence-model.md). +Decided by Interval sequence model โ€” one engine for six modes. **One model, six presets.** A Mode is default configuration plus a label โ€” never its own code path. Only Crono is structurally different (unbounded). @@ -105,14 +103,14 @@ Rules: **Clamp silently. Never block Start, never show error text.** Phase duration `0:01`โ€“`99:59`, rounds `1`โ€“99, seconds roll into minutes (`0:75` โ†’ `1:15`). Clamping lives in the pure layer, not in the DOM, so it is unit-tested -([Testing strategy](../issues/8-testing-strategy.md)). +(Testing strategy). --- ## 2. The engine โ€” `phaseAt` and Clock arithmetic -Decided by [Interval sequence model](../issues/1-interval-sequence-model.md) and -[Clock accuracy and background behaviour](../issues/2-clock-accuracy-and-background-behaviour.md). +Decided by Interval sequence model and +Clock accuracy and background behaviour. ### Derive, don't expand @@ -140,7 +138,7 @@ const elapsed = (c: Clock, now: number) => (c.pausedAt ?? now) - c.startedAt - c - **Time source is `Date.now()` only.** `performance.now()` does not advance while the device is suspended on either platform โ€” confirmed as fact, not guess, by - [PWA research](../issues/4-pwa-offline-install-wakelock-research.md) (WebKit `MonotonicTime` / + PWA research (WebKit `MonotonicTime` / `mach_absolute_time`, Chromium `CLOCK_MONOTONIC`). Sleeping mid-routine is normal on a gym floor. Accepted trade-off: an NTP or manual clock step mid-Routine corrupts elapsed โ€” rare, recoverable by pausing. @@ -161,7 +159,7 @@ const elapsed = (c: Clock, now: number) => (c.pausedAt ?? now) - c.startedAt - c On `visibilitychange` โ†’ visible: recompute from the Clock and **jump** to the correct Phase. Missed cues are dropped, never replayed โ€” stale beeps are noise. If the Routine completed while away, land on the Done screen (its end cue may fire). With phase-length cue scheduling -([iOS background audio](../issues/10-ios-background-audio.md)) the drop rule now only covers a real +(iOS background audio) the drop rule now only covers a real device suspend, where nothing helps. ### Transport controls @@ -180,8 +178,8 @@ because nothing accumulates. ## 3. Cue model and scheduling -Decided by [Audio and haptic cue model](../issues/3-audio-and-haptic-cue-model.md), with the -scheduling horizon revised by [iOS background audio](../issues/10-ios-background-audio.md). +Decided by Audio and haptic cue model, with the +scheduling horizon revised by iOS background audio. **Cues are data, sinks are dumb.** `cuesFor(routine)` is a pure function returning `{ at, kind }[]`; sinks (tone, vibration, later speech) render them. The pure part is unit-tested; sinks are trivial @@ -212,7 +210,7 @@ timers. Cues are booked on `AudioContext.currentTime`, which is immune to thrott clock (`Date.now()`) and the cue clock (`AudioContext.currentTime`) are two views of one timeline, correlated once at start. -Horizon is **one Phase, not one second** ([iOS background audio](../issues/10-ios-background-audio.md)): +Horizon is **one Phase, not one second** (iOS background audio): a ~1s horizon does not survive a 5-minute pomodoro break with the screen off. At each Phase start, schedule every cue of that Phase. A Phase is the natural unit โ€” its cues are fully known when it begins, and it bounds how much must be cancelled. Scheduling the whole Routine up front would mean @@ -247,8 +245,8 @@ matrix.** Persisted as preferences (ยง5); surfaced on the Settings screen (ยง4). ## 4. UI โ€” screens, states, layout -Decided by [Screen layout and config UI](../issues/6-screen-layout-and-config-ui.md) (prototype, -branch `prototype/timer-ui`) and [Where cue and app settings live in the UI](../issues/11-settings-ui-home.md). +Decided by Screen layout and config UI (prototype, +branch `prototype/timer-ui`) and Where cue and app settings live in the UI. ### Chosen layout: variant 3, "bar" @@ -260,7 +258,7 @@ only. ### Shell -Every screen **including Home** ([ticket 11](../issues/11-settings-ui-home.md)) uses the same top +Every screen **including Home** uses the same top nav: left slot / centred title / right slot. Content starts at the **top** of the page, never vertically centred. @@ -303,8 +301,8 @@ Mode-dependent behaviour: so every Mode ends the same way. - During `prepare`, `โญ` skips the countdown in every Mode โ€” it never finishes. - Single-Phase Routines hide the controls they cannot use. -- **No mute control here** ([ticket 11](../issues/11-settings-ui-home.md)) โ€” the phone's volume - buttons work face-down on the floor, need no aiming and cost no pixels. +- **No mute control here** โ€” the phone's volume buttons work face-down on the floor, need no aiming + and cost no pixels. - Conditional one-line notes: "tap to enable sound" if the `AudioContext` is still suspended (ยง3), and "Screen may sleep โ€” keep it awake for sound." where Wake Lock is absent (ยง6). @@ -328,16 +326,14 @@ No theme override โ€” `prefers-color-scheme` only, and nothing is stored for it. ### No React, anywhere The whole prototype is one `.astro` file: plain DOM, CSS custom properties, `rAF`, native -``. Nothing in the UI justifies an island -([ticket 6](../issues/6-screen-layout-and-config-ui.md)), and nothing in the PWA layer does either โ€” -registration, update prompt, Wake Lock, audio session and haptics are all plain DOM APIs -([ticket 4](../issues/4-pwa-offline-install-wakelock-research.md)). +``. Nothing in the UI justifies an island, and nothing in the PWA layer +does either โ€” registration, update prompt, Wake Lock, audio session and haptics are all plain DOM APIs. --- ## 5. Persistence -Decided by [Persistence model in localStorage](../issues/7-persistence-model.md). +Decided by Persistence model in localStorage. ### Two keys, different lifetimes @@ -393,8 +389,8 @@ start because storage is odd is worse than one that forgot your settings. ## 6. PWA โ€” offline, install, Wake Lock -Decided by [PWA offline, install and Wake Lock โ€” platform research](../issues/4-pwa-offline-install-wakelock-research.md) -and [iOS background audio](../issues/10-ios-background-audio.md). +Decided by PWA offline, install and Wake Lock โ€” platform research +and iOS background audio. ### The progressive-enhancement layer table @@ -467,17 +463,16 @@ that, and the display recomputes from `Date.now()` on resume. **No special case ## 7. Toolchain -Decided by [Toolchain baseline ported from b2b-wrk-esi](../issues/5-toolchain-baseline.md) +Decided by Toolchain baseline ported from b2b-wrk-esi (commit `938e0e9`, already on `main`). - **Astro 7 + TypeScript, static output.** `output: 'static'` is the Astro 7 default and is deliberately not written in the config; it becomes a real decision only if an adapter appears. - **pnpm**, Node `>=22.12.0`. `pnpm-workspace.yaml` kept (pnpm uses it for `allowBuilds`). `.npmrc` sets `min-release-age=7`. -- **React is not installed** and no ticket has justified it (ยง4). Adding it later is - `astro add react`. +- **React is not installed** and nothing has justified it (ยง4). Adding it later is `astro add react`. - **oxlint** (`.oxlintrc.json`, + `env.browser`, `ignorePatterns: [dist, .astro]`) and **oxfmt** - (`.oxfmtrc.json`, `**/*.md` ignored so tickets are never rewritten). oxlint **does** lint `.astro` + (`.oxfmtrc.json`, `**/*.md` ignored). oxlint **does** lint `.astro` (frontmatter and `