| phase |
verified |
status |
score |
gaps |
human_verification |
| 04-heatmap-interaction |
2026-04-08T15:37:00Z |
human_needed |
3/4 must-haves verified |
| truth |
status |
reason |
artifacts |
missing |
| A dropdown allows filtering the heatmap to show data for a single project or activity |
partial |
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. |
| path |
issue |
| assets/src/heatmap.ts |
No activity filter option in dropdown, no activity query param in fetch |
|
| path |
issue |
| Service/HeatmapService.php |
No getUserActivities() method |
|
| path |
issue |
| Controller/HeatmapController.php |
No ?activity= query parameter handling |
|
|
| 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 |
|
|
|
| test |
expected |
why_human |
| Click a heatmap day cell and verify navigation to Kimai timesheet filtered to that date |
Browser navigates to /en/timesheet/?daterange=YYYY-MM-DD%20-%20YYYY-MM-DD |
Requires running Kimai instance with the plugin loaded to verify URL construction and Kimai's timesheet page accepting the daterange parameter |
|
| test |
expected |
why_human |
| Select a project from the filter dropdown and verify heatmap re-renders |
Heatmap cells update to show only data for the selected project, color scale adjusts |
Requires running Kimai with real time entries across multiple projects to verify visual filtering |
|
| test |
expected |
why_human |
| Verify filter dropdown appearance and positioning |
Dropdown appears to the right of the heatmap SVG, styled with Tabler form-select classes, responsive layout |
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)