From 843ac8480503be24547f105e61c69bd4a4d7452d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=BChl?= Date: Wed, 8 Apr 2026 16:05:50 +0200 Subject: [PATCH] docs: phase 5 planning artifacts Co-Authored-By: Claude Opus 4.6 --- .planning/ROADMAP.md | 12 +-- .planning/phases/05-polish/05-01-PLAN.md | 105 +++++++++++++++++++++++ .planning/phases/05-polish/05-02-PLAN.md | 46 ++++++++++ .planning/phases/05-polish/05-CONTEXT.md | 93 ++++++++++++++++++++ 4 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 .planning/phases/05-polish/05-01-PLAN.md create mode 100644 .planning/phases/05-polish/05-02-PLAN.md create mode 100644 .planning/phases/05-polish/05-CONTEXT.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index c1751a6..09d5177 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -16,7 +16,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 2: Plugin Scaffold + Data Layer** - Symfony bundle, dashboard widget, aggregation API with PHPUnit tests - [x] **Phase 3: Core Heatmap Rendering** - d3.js calendar grid with color mapping, labels, tooltips, theme integration, and JS tests - [x] **Phase 4: Heatmap Interaction** - Click-through navigation, project/activity filtering, interaction tests (completed 2026-04-08) -- [ ] **Phase 5: Polish** - Streak indicator, summary stats, weekend styling +- [x] **Phase 5: Polish** - Streak indicator, summary stats, weekend styling (completed 2026-04-08) ## Phase Details @@ -92,12 +92,12 @@ Plans: 1. A streak indicator shows the current number of consecutive days with tracked time 2. A summary stats row displays total hours, average hours/day, and the busiest day 3. Weekend days are visually distinct from weekdays (subtle border or opacity difference) -**Plans**: TBD +**Plans**: phase-5/PLAN.md **UI hint**: yes Plans: -- [ ] 05-01: TBD -- [ ] 05-02: TBD +- [x] 05-01: Streak, stats, weekend styling, week-start preference +- [x] 05-02: Tests for polish features ## Progress @@ -109,5 +109,5 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 | 1. Dev Environment | 2/2 | Done | 2026-04-08 | | 2. Plugin Scaffold + Data Layer | 2/2 | Done | 2026-04-08 | | 3. Core Heatmap Rendering | 3/3 | Done | 2026-04-08 | -| 4. Heatmap Interaction | 2/2 | Complete | 2026-04-08 | -| 5. Polish | 0/2 | Not started | - | +| 4. Heatmap Interaction | 2/2 | Complete | 2026-04-08 | +| 5. Polish | 2/2 | Done | 2026-04-08 | diff --git a/.planning/phases/05-polish/05-01-PLAN.md b/.planning/phases/05-polish/05-01-PLAN.md new file mode 100644 index 0000000..213c2ad --- /dev/null +++ b/.planning/phases/05-polish/05-01-PLAN.md @@ -0,0 +1,105 @@ +# Plan 05-01: Streak, Stats, Weekend Styling, Week Start + +**Phase:** 5 — Polish +**Requirements:** POLI-01, POLI-02, POLI-03 + start-of-week preference +**Estimated tasks:** 6 + +## Goal + +Add streak indicator, summary stats row, weekend cell styling, and configurable week start to the heatmap widget. + +## Tasks + +### Task 1: Add week start to PHP widget data + +**File:** `Widget/HeatmapWidget.php` +**Action:** In `getData()`, read `$user->getFirstDayOfWeek()` and include it in the returned array as `'weekStart'`. + +**File:** `Resources/views/widget/heatmap.html.twig` +**Action:** Add `data-week-start="{{ data.weekStart }}"` to the `#heatmap-container` div. + +### Task 2: Make week start configurable in TypeScript + +**File:** `assets/src/heatmap.ts` +**Changes:** +- In `init()`, read `data-week-start` attribute from container (default: `'monday'`) +- Pass `weekStart` string to `renderHeatmap()` via a new optional parameter +- In `generateCells()`, replace hardcoded `timeMonday` with the appropriate d3 time interval based on `weekStart`: + - `'sunday'` → `timeSunday` (import from d3-time) + - `'monday'` → `timeMonday` (existing) +- Update `DAY_LABELS` to rotate based on week start +- Update `dayOfWeek` calculation to match (row 0 = first day of week) + +### Task 3: Add weekend cell styling + +**File:** `assets/src/heatmap.ts` +**Changes:** +- In `generateCells()`, add `isWeekend: boolean` to `DayCell` interface (Saturday=6, Sunday=0 in JS `getDay()`) +- In the cell rendering `.attr('class', ...)`, append `heatmap-weekend` class for weekend cells + +**File:** `Resources/public/heatmap.css` +**Add:** +```css +.heatmap-weekend { + opacity: 0.8; +} + +.heatmap-weekend:hover { + opacity: 0.65; +} +``` + +### Task 4: Add streak calculation + +**File:** `assets/src/heatmap.ts` +**Add function** `calculateStreak(days: DayEntry[]): number`: +- Sort days by date descending +- Starting from today (or yesterday if today has no entry), count consecutive calendar days with entries +- Return the count +- A day with `hours > 0` counts as tracked + +### Task 5: Add summary stats calculation + +**File:** `assets/src/heatmap.ts` +**Add function** `calculateStats(days: DayEntry[]): { totalHours: number, avgHours: number, busiestDay: { date: string, hours: number } | null }`: +- `totalHours`: sum of all hours, rounded to 1 decimal +- `avgHours`: total hours / number of days with entries, rounded to 1 decimal +- `busiestDay`: day entry with maximum hours + +### Task 6: Render streak and stats below heatmap + +**File:** `assets/src/heatmap.ts` +**In `init()`**, after `renderHeatmap()` resolves: +- Call `calculateStreak()` and `calculateStats()` with the fetched data +- Create a stats row div below the SVG area with class `heatmap-stats` +- Content: `🔥 N days | Total: Xh | Avg: Xh/day | Busiest: Mon, Mar 15 — Xh` +- When filter changes and data reloads, recalculate and update stats + +**File:** `Resources/public/heatmap.css` +**Add:** +```css +.heatmap-stats { + display: flex; + gap: 16px; + padding: 8px 0 0; + font-size: 0.8125rem; + color: var(--tblr-secondary, #6c757d); + flex-wrap: wrap; +} + +.heatmap-stats .stat-value { + color: var(--tblr-body-color); + font-weight: 600; +} +``` + +## Verification + +- `npm run build` succeeds +- Widget shows streak count, three stat values, weekend cells with reduced opacity +- Changing week start preference changes grid layout and labels +- Filter change recalculates stats + +## Commit + +`feat: add streak, stats, weekend styling, week-start preference` diff --git a/.planning/phases/05-polish/05-02-PLAN.md b/.planning/phases/05-polish/05-02-PLAN.md new file mode 100644 index 0000000..1e2d95e --- /dev/null +++ b/.planning/phases/05-polish/05-02-PLAN.md @@ -0,0 +1,46 @@ +# Plan 05-02: Tests for Polish Features + +**Phase:** 5 — Polish +**Requirements:** POLI-01, POLI-02, POLI-03 (test coverage) +**Estimated tasks:** 3 + +## Goal + +Add Vitest tests for streak calculation, summary stats, weekend styling, and week start configurability. + +## Tasks + +### Task 1: Streak calculation tests + +**File:** `assets/test/stats.test.ts` (new) +**Tests:** +- Streak of 3 consecutive days ending today → returns 3 +- Gap in the middle breaks streak → returns days after gap +- No entries today but entries yesterday → streak counts from yesterday +- No entries at all → returns 0 +- Single day entry (today) → returns 1 + +### Task 2: Summary stats tests + +**File:** `assets/test/stats.test.ts` (same file) +**Tests:** +- Total hours sums correctly +- Average hours divides by days-with-entries (not total calendar days) +- Busiest day picks the max-hours entry +- Empty data returns zeroes and null busiest day + +### Task 3: Weekend and week start rendering tests + +**File:** `assets/test/heatmap.test.ts` (extend existing) +**Tests:** +- Weekend cells (Sat/Sun) have `heatmap-weekend` class +- Weekday cells do NOT have `heatmap-weekend` class +- Stats row renders below heatmap with streak and stat values + +## Verification + +- `npm test` passes all new and existing tests + +## Commit + +`test: add tests for streak, stats, weekend styling` diff --git a/.planning/phases/05-polish/05-CONTEXT.md b/.planning/phases/05-polish/05-CONTEXT.md new file mode 100644 index 0000000..e0e7db9 --- /dev/null +++ b/.planning/phases/05-polish/05-CONTEXT.md @@ -0,0 +1,93 @@ +# Phase 5: Polish - Context + +**Gathered:** 2026-04-08 +**Status:** Ready for planning + + +## Phase Boundary + +Add streak indicator, summary statistics row, and weekend visual distinction to the heatmap widget. Also integrate user's start-of-week preference from Kimai. + + + + +## Implementation Decisions + +### Streak Indicator +- Count consecutive calendar days (including weekends) with tracked time, counting backward from today +- Display as a simple text line above or below the heatmap: "Current streak: N days" +- If today has no entries yet, check from yesterday backward (user may still be tracking today) +- Computed client-side from the fetched heatmap data — no new API endpoint + +### Summary Stats Row +- Rendered below the heatmap SVG, inside the same Tabler card +- Three stats: Total Hours, Avg Hours/Day (days with tracked time only), Busiest Day +- Computed client-side from the fetched heatmap data array +- Styled as inline stat items using Tabler's existing typography classes +- "Busiest Day" shows date + hours (e.g., "Mar 15 — 8.5h") + +### Weekend Styling +- Weekend cells (Saturday/Sunday) get reduced opacity (0.85) compared to weekday cells +- Applied via CSS class `.heatmap-weekend` with opacity rule +- Empty weekend cells also get the distinction so the grid pattern is visible +- No border changes — keep existing rounded rect style + +### Start-of-Week Preference +- Read user's `first_weekday` from Kimai (exposed via User entity or system config) +- Pass as `data-week-start` attribute on the container div (0=Sunday, 1=Monday, etc.) +- Use in d3 grid layout to set which day is row 0 +- Adjust day-of-week labels to match +- Default to Monday if not available (current behavior) + +### Testing Strategy +- Vitest tests for streak calculation (consecutive days, gaps, weekend inclusion, today edge case) +- Vitest tests for summary stats computation (totals, averages, busiest day) +- Vitest tests for weekend cell class assignment +- CSS changes don't need unit tests — visual verification + +### Claude's Discretion +- Exact Tabler CSS classes for the stats row layout +- How to extract `first_weekday` from Kimai's User entity (check source) +- Whether streak resets on weekends or not (decision: it does — every calendar day counts) +- d3 time interval function for configurable week start (d3.timeSunday vs d3.timeMonday etc.) + + + + +## Existing Code Insights + +### Reusable Assets +- `renderHeatmap(container, data, config)` — main render, needs extension for weekend classes and stats +- `init()` — fetches data and calls render, will also compute streak/stats after data arrives +- `HeatmapData.days` array — source for all client-side computations +- `generateCells()` helper — can add weekend detection here +- `HeatmapWidget::getData()` — can add `weekStart` to template data + +### Established Patterns +- d3 selections for SVG element class assignment +- Data attributes on container div for passing server config to JS +- Tabler card structure with widget template embedding +- CSS classes added to cells based on data state (`.heatmap-empty`) + +### Integration Points +- `heatmap.html.twig` — add `data-week-start` attribute, stats container div +- `heatmap.css` — add `.heatmap-weekend`, `.heatmap-stats` styles +- `heatmap.ts` — extend `renderHeatmap` for weekends, add streak/stats functions +- `HeatmapWidget.php` — pass user's week start preference to template + + + + +## Specific Ideas + +- Memory note: heatmap must respect Kimai user's start-of-week preference (not hardcoded Monday) +- Stats should feel lightweight — not a full stats panel, just quick context below the heatmap + + + + +## Deferred Ideas + +None — all three requirements (POLI-01, POLI-02, POLI-03) plus start-of-week fit within polish scope. + +