# Project Research Summary **Project:** Kimai Heatmap Plugin **Domain:** Time-tracking dashboard widget (Symfony bundle + d3.js visualization) **Researched:** 2026-04-08 **Confidence:** MEDIUM ## Executive Summary This is a Kimai 2.x plugin that adds a GitHub-style activity heatmap to the dashboard. The product pattern is well-established: calendar grid, color intensity for activity levels, tooltips, click-through navigation. The technology choices are straightforward -- a Symfony bundle for the backend (matching Kimai's framework), d3.js sub-modules for the SVG visualization, TypeScript for type safety, and esbuild for bundling. No additional database is needed; the plugin reads from Kimai's existing timesheet table. The recommended approach is a strict 4-phase build: plugin scaffold first (prove the widget appears on the dashboard), then data layer (aggregation API), then visualization (d3 heatmap rendering), then interactivity (filters, toggles, click navigation). This ordering is dictated by hard dependencies -- you cannot render a heatmap without data, and you cannot build data queries without a working plugin scaffold. The Nix dev environment setup is a prerequisite phase that should be timeboxed to one day. The primary risks are: (1) Kimai's plugin API is under-documented and shifts between releases -- the widget interface, DI tags, and asset conventions all need verification against the target Kimai version before writing code; (2) timezone-incorrect day aggregation will produce wrong data silently -- aggregation must happen server-side in the user's configured timezone; (3) the Nix + PHP + Kimai dev environment setup can consume days if not timeboxed. All three are manageable with the mitigations outlined below. ## Key Findings ### Recommended Stack The plugin is a standard Symfony bundle running inside Kimai 2.x. The frontend is a self-contained d3.js visualization bundled with esbuild and shipped as a single JS file in `Resources/public/`. No integration with Kimai's Webpack Encore build is needed or desired. **Core technologies:** - **PHP 8.2+ / Symfony 6.4 LTS**: Must match Kimai's runtime exactly. Verify against Kimai's `composer.json`. - **d3.js sub-modules** (d3-scale, d3-selection, d3-time, d3-time-format, d3-scale-chromatic, d3-shape): Selective imports keep bundle size to ~50-80KB vs ~500KB for full d3. - **TypeScript**: d3 v7 ships types. Catches data shape bugs at build time. esbuild handles `.ts` natively. - **esbuild**: Single-command bundling of d3 modules into one JS file. Simpler than Webpack Encore for a single widget. - **Vitest + jsdom**: ESM-native testing for d3 code. Jest struggles with d3 v7's ESM-only modules. - **PHPUnit**: Matches Kimai's own test framework. - **Nix flake**: Reproducible dev env with PHP 8.2, Composer, Node 22, SQLite. ### Expected Features **Must have (table stakes):** - Calendar grid layout (weeks x days) with color intensity mapping - Tooltip on hover showing date, hours, entry count - Day-of-week and month labels - Empty state rendering (no-data days visible, not invisible) - Click-through to Kimai timesheet filtered by date - Kimai theme integration via CSS variables - Default trailing 12-month range **Should have (differentiators):** - Toggle between hours/day and entry count (different questions answered) - Project/activity filter dropdown - Configurable time range (3/6/12 months) - Streak indicator and summary stats row **Defer indefinitely:** - Hour-of-day matrix, export/share, goal setting, animations, multi-user, mobile layout, custom color picker -- all anti-features for a personal tracking widget. ### Architecture Approach The architecture follows a clean separation: thin Twig template (HTML shell + controls), XHR data fetching from a dedicated API controller, and client-side d3 rendering. The widget registers via Kimai's `WidgetInterface` + `kimai.widget` DI tag. Data flows from Kimai's timesheet table through a `HeatmapService` (Doctrine QueryBuilder, GROUP BY date, user-scoped), to a `HeatmapController` (JSON API), to the d3 module (SVG rendering). **Major components:** 1. **KimaiHeatmapBundle** -- Symfony bundle class implementing `PluginInterface`, auto-discovered by Kimai 2. **HeatmapWidget** -- `WidgetInterface` implementation, registers on dashboard, renders Twig template 3. **HeatmapController** -- API endpoint (`/api/plugins/heatmap/data`) returning aggregated JSON 4. **HeatmapService** -- Server-side aggregation (hours/count per day, timezone-aware, user-scoped) 5. **d3 heatmap module** -- TypeScript, calendar grid rendering, event handling, theme integration ### Critical Pitfalls 1. **Kimai plugin API instability** -- Pin to a specific Kimai version. Write an integration test that boots the Symfony kernel with the plugin loaded. Subscribe to Kimai releases for breaking change awareness. 2. **Timezone mismatch (PHP vs JS day boundaries)** -- Aggregate by day on the PHP side using `$user->getTimezone()`. Send date strings to the frontend, never raw timestamps. Compare output against Kimai's own reports. 3. **Widget system misunderstanding** -- Use `WidgetInterface` + `kimai.widget` DI tag from the start. Do not build a standalone controller page. Study existing Kimai widgets before coding. 4. **Asset loading failures** -- Ship prebuilt JS in `Resources/public/`, loaded via `