--- phase: 04-heatmap-interaction verified: 2026-04-08T15:37:00Z status: human_needed score: 3/4 must-haves verified gaps: - truth: "A dropdown allows filtering the heatmap to show data for a single project or activity" status: partial reason: "Only project filtering is implemented. Activity filtering was explicitly descoped in 04-CONTEXT.md ('Projects only -- no activity filter') but the roadmap SC and INTR-01 both specify 'project or activity'. No later phase addresses this." artifacts: - path: "assets/src/heatmap.ts" issue: "No activity filter option in dropdown, no activity query param in fetch" - path: "Service/HeatmapService.php" issue: "No getUserActivities() method" - path: "Controller/HeatmapController.php" issue: "No ?activity= query parameter handling" missing: - "getUserActivities() service method analogous to getUserProjects()" - "Activity filter option in dropdown (or combined project/activity dropdown)" - "Controller support for ?activity=N query parameter" - "HeatmapService::getDailyAggregation() support for activityId parameter" human_verification: - test: "Click a heatmap day cell and verify navigation to Kimai timesheet filtered to that date" expected: "Browser navigates to /en/timesheet/?daterange=YYYY-MM-DD%20-%20YYYY-MM-DD" why_human: "Requires running Kimai instance with the plugin loaded to verify URL construction and Kimai's timesheet page accepting the daterange parameter" - test: "Select a project from the filter dropdown and verify heatmap re-renders" expected: "Heatmap cells update to show only data for the selected project, color scale adjusts" why_human: "Requires running Kimai with real time entries across multiple projects to verify visual filtering" - test: "Verify filter dropdown appearance and positioning" expected: "Dropdown appears to the right of the heatmap SVG, styled with Tabler form-select classes, responsive layout" why_human: "Visual layout verification requires browser rendering" --- # Phase 4: Heatmap Interaction Verification Report **Phase Goal:** Users can click through to daily details and filter the heatmap by project or activity **Verified:** 2026-04-08T15:37:00Z **Status:** human_needed **Re-verification:** No -- initial verification ## Goal Achievement ### Observable Truths | # | Truth | Status | Evidence | |---|-------|--------|----------| | 1 | Clicking a day cell navigates to Kimai's timesheet view filtered to that specific date | VERIFIED | `heatmap.ts` lines 217-224: `onCellClick` constructs URL with `daterange` param and assigns to `window.location.href`. Tests in `interaction.test.ts` verify URL format. | | 2 | A dropdown allows filtering the heatmap to show data for a single project or activity | PARTIAL | Project filtering fully implemented (dropdown, fetch with `?project=N`, re-render). Activity filtering intentionally omitted per 04-CONTEXT.md line 25. Roadmap SC and INTR-01 both specify "or activity". | | 3 | Filtering updates the heatmap in place without a full page reload | VERIFIED | `heatmap.ts` lines 256-271: `select.addEventListener('change', ...)` triggers `fetch()` then `renderHeatmap()` on the existing `svgArea` div. Tests in `filter.test.ts` verify re-render. | | 4 | JavaScript tests verify click navigation and tooltip interaction behavior | VERIFIED | `interaction.test.ts` has 7 tests (callback, URL format, project in URL, fallback). `filter.test.ts` has 9 tests (dropdown rendering, fetch params, re-render, empty state). All 52 tests pass. | **Score:** 3/4 truths fully verified (1 partial) ### Required Artifacts | Artifact | Expected | Status | Details | |----------|----------|--------|---------| | `Service/HeatmapService.php` | getUserProjects() method | VERIFIED | Lines 54-68: queries distinct projects with IDENTITY join, returns `[{id, name}]` | | `Widget/HeatmapWidget.php` | Injects service, getData returns projects | VERIFIED | Lines 11-12: constructor injection. Lines 40-47: getData returns projects array. No `final` keyword. | | `Resources/views/widget/heatmap.html.twig` | data-projects and data-timesheet-url attributes | VERIFIED | Lines 9-10: both data attributes present with correct Twig expressions | | `assets/src/heatmap.ts` | Click handler, filter dropdown, re-fetch | VERIFIED | Lines 198-201: click handler. Lines 236-276: filter dropdown. Lines 256-271: re-fetch on change. | | `assets/src/types.ts` | ProjectOption interface | VERIFIED | Lines 22-25: `export interface ProjectOption { id: number; name: string; }` | | `Resources/public/heatmap.css` | Click affordance, flex layout | VERIFIED | Lines 15-21: cursor:pointer, transition, pointer-events. Lines 38-58: flex wrapper layout. | | `Resources/public/heatmap.js` | Rebuilt bundle | VERIFIED | 81,943 bytes, contains KimaiHeatmap IIFE | | `assets/test/interaction.test.ts` | Click navigation tests | VERIFIED | 7 tests covering onCellClick callback, URL construction, project filter in URL | | `assets/test/filter.test.ts` | Filter dropdown tests | VERIFIED | 9 tests covering rendering, options, fetch params, re-render, empty state | ### Key Link Verification | From | To | Via | Status | Details | |------|----|-----|--------|---------| | `heatmap.ts` | Kimai timesheet | `window.location.href` with daterange | WIRED | Line 223: `window.location.href = url` with `encodeURIComponent(daterange)` | | `heatmap.ts` | HeatmapController::data() | `fetch` with `?project=N` | WIRED | Line 259: `fetch(fetchUrl)` where `fetchUrl = baseUrl?project=val` | | `HeatmapWidget.php` | `HeatmapService::getUserProjects()` | getData() calls service | WIRED | Line 45: `$this->service->getUserProjects($user)` | | `interaction.test.ts` | `heatmap.ts` | imports renderHeatmap and init | WIRED | Line 3: `import { renderHeatmap, init } from '../src/heatmap'` | | `filter.test.ts` | `heatmap.ts` | imports init | WIRED | Line 2: `import { init } from '../src/heatmap'` | ### Data-Flow Trace (Level 4) | Artifact | Data Variable | Source | Produces Real Data | Status | |----------|---------------|--------|--------------------|--------| | `heatmap.ts` (init) | projects | `data-projects` attr from Twig | Server-rendered from DB query via getUserProjects() | FLOWING | | `heatmap.ts` (init) | HeatmapData | `fetch(baseUrl)` | API endpoint queries TimesheetRepository | FLOWING | | `heatmap.ts` (filter) | HeatmapData | `fetch(baseUrl?project=N)` | API endpoint filters by projectId | FLOWING | ### Behavioral Spot-Checks | Behavior | Command | Result | Status | |----------|---------|--------|--------| | All tests pass | `npx vitest run` | 52 passed (26 unique + symlink dupes), 0 failed | PASS | | Bundle builds | `ls Resources/public/heatmap.js` | 81,943 bytes | PASS | | No TODOs/FIXMEs in phase code | grep across modified files | No matches | PASS | ### Requirements Coverage | Requirement | Source Plan | Description | Status | Evidence | |-------------|------------|-------------|--------|----------| | HEAT-07 | 04-01 | Clicking a day cell navigates to Kimai timesheet view filtered to that date | SATISFIED | `heatmap.ts` onCellClick with window.location.href, daterange param | | INTR-01 | 04-01 | Dropdown filter to view heatmap for a specific project or activity | PARTIAL | Project filtering implemented. Activity filtering omitted (intentional descope in 04-CONTEXT.md but not reflected in requirement text). | | TEST-04 | 04-02 | JavaScript tests for tooltip and click interaction behavior | SATISFIED | interaction.test.ts (7 tests) + filter.test.ts (9 tests) all passing | ### Anti-Patterns Found | File | Line | Pattern | Severity | Impact | |------|------|---------|----------|--------| | None found | - | - | - | - | ### Human Verification Required ### 1. Click Navigation End-to-End **Test:** Click a heatmap day cell in a running Kimai instance **Expected:** Browser navigates to `/en/timesheet/?daterange=YYYY-MM-DD%20-%20YYYY-MM-DD` showing time entries for that date **Why human:** Requires running Kimai to verify the timesheet page accepts and applies the daterange parameter correctly ### 2. Project Filter Visual Behavior **Test:** Select a project from the filter dropdown with multiple projects having time data **Expected:** Heatmap re-renders showing only that project's data, color scale recalculates, cells update **Why human:** Requires running Kimai with real multi-project time data to verify visual filtering ### 3. Layout and Styling **Test:** View the widget on the Kimai dashboard **Expected:** Filter dropdown appears to the right of the heatmap, cells show pointer cursor on hover with opacity change, layout is clean **Why human:** Visual layout verification requires browser rendering with Tabler CSS ### Gaps Summary One partial gap identified: the roadmap success criteria and INTR-01 both specify filtering by "project **or activity**" but only project filtering was implemented. This was an intentional design decision documented in 04-CONTEXT.md ("Projects only -- no activity filter"), but it represents a deviation from the stated roadmap contract. No later phase addresses activity filtering. The gap is flagged as partial rather than failed because: - Project filtering is fully functional and tested - The descope was a conscious decision, not an oversight - The core interaction value (filtering the heatmap) is delivered for the primary dimension (projects) All other success criteria are fully met with solid test coverage (16 new tests). --- _Verified: 2026-04-08T15:37:00Z_ _Verifier: Claude (gsd-verifier)_